This monorepo contains two independently-released packages. Each has its own flow:
| Package | Trigger | Destination |
|---|---|---|
Launcher (asyar-launcher/) |
Push a v* tag |
GitHub Releases + asyar.org updater feed |
SDK (asyar-sdk/) |
Push a sdk-v* tag |
npm: asyar-sdk + GitHub Releases |
When a release carries changes to both packages, publish the SDK before the
launcher. The launcher's release script pins its asyar-sdk dependency to
whatever version sits in asyar-sdk/package.json on disk — it never asks npm.
So bumping the SDK locally and then releasing the launcher first would pin the
launcher to a version that does not exist on npm yet, and every extension install
that resolves asyar-sdk would fail.
Check whether the SDK actually changed since its last published tag:
git diff --stat "sdk-v$(node -p "require('./asyar-sdk/package.json').version")"..HEAD -- asyar-sdk/Empty output means the published SDK is current and you can release the launcher on its own. Any output means the SDK goes first.
Two things in the SDK reach extension authors directly, so they are worth checking before deciding the order:
asyar-sdk/cli/— theasyarCLI that authors run to build and package. A packaging fix here does nothing for them until it is on npm.asyar-sdk/package.jsonexports— a new subpath (e.g../vite) is invisible to consumers until published.scaffoldService.tsprobes npm withnpm view asyar-sdk exportsat scaffold time and falls back to an older template, so a new subpath starts being used the moment it is published — no launcher release required.
Preview first. Add
--dry-runto any release command to print exactly what it would do (version, files, git commands) without changing anything or touching the network. The working tree must be clean before a real release — the script aborts with✖ Working tree is not clean.otherwise, so commit your work first.
The launcher uses a release helper script (a thin wrapper over the shared scripts/release-lib.mjs) that keeps every version reference in sync and never depends on npm.
From the monorepo root:
pnpm --filter asyar-launcher run release <patch|minor|major|beta> # add --dry-run to previewOr from inside asyar-launcher/:
cd asyar-launcher
pnpm run release <patch|minor|major|beta>| Keyword | Bump | Example |
|---|---|---|
patch |
x.y.Z | 0.1.0 → 0.1.1 |
minor |
x.Y.0 | 0.1.0 → 0.2.0 |
major |
X.0.0 | 1.0.0 → 2.0.0 |
beta |
numeric pre-release | 0.1.0 → 0.1.0-1, 0.1.0-1 → 0.1.0-2 |
The script always produces Windows-MSI-compatible version strings — pre-release suffixes are always numeric (
0.1.0-1, never0.1.0-beta). No manual care needed.
- Duplicate-tag guard: aborts immediately if
vX.Y.Zalready exists on the remote, so a version is never released twice - Local SDK version: reads the SDK version from
asyar-sdk/package.jsonon disk — no npm call (the workspace always builds the local SDK, so a release can't be blocked by npm being down) - Version sync: bumps
asyar-launcher/package.json(its own version + the declaredasyar-sdkdependency),src-tauri/Cargo.toml,Cargo.lock, thescaffoldService.tsoffline fallback, and the rootpnpm-lock.yaml - Git operations: commits on a new
release/vX.Y.Zbranch, opens a PR, and pushes thevX.Y.Ztag (it does not push straight tomain) - CI trigger: the
v*tag triggers.github/workflows/release-launcher.yml - Build matrix: macOS arm64 + x64 (per-arch, not a universal binary), Windows x64/arm64, Linux amd64/arm64 — all 6 required
- GitHub Release: published directly (not a draft) with auto-generated notes
- Pre-release detection: tags with a hyphen (e.g.,
v0.1.0-1) are automatically marked "Pre-release" - Updater notification: asyar.org
/api/releasesis notified so the auto-update feed picks up the new version
The Rust
SUPPORTED_SDK_VERSIONis not set by the release script —src-tauri/build.rsinjects it at compile time from the localasyar-sdk/package.json, so it can never drift.
Linux builds also publish the standalone summon helpers asyar-summon_amd64
and asyar-summon_aarch64. install.sh installs the matching helper beside the
AppImage, and re-running it refreshes both files. The versioned
org.asyar.Launcher1 interface and --show-on-start argument must remain
backward compatible because the AppImage updater does not update the helper.
The tag triggers the build from the release/vX.Y.Z branch, so the installers always carry the correct version even before the PR is merged. After CI is green:
- Merge the release PR. This lands the version bump on
mainso the next release calculates from the right number.
That's it — the GitHub Release and the asyar.org updater feed are published automatically.
The SDK uses a release helper script mirroring the launcher's, with one distinguishing detail: SDK tags are prefixed sdk-v* (the launcher owns v*).
From the monorepo root:
pnpm release:sdk <patch|minor|major|beta>
# equivalent to: pnpm --filter asyar-sdk run release <patch|minor|major|beta>Or from inside asyar-sdk/:
cd asyar-sdk
pnpm run release <patch|minor|major|beta>Same semantics as the launcher's flow:
| Keyword | Bump | Example |
|---|---|---|
patch |
x.y.Z | 2.7.0 → 2.7.1 |
minor |
x.Y.0 | 2.7.0 → 2.8.0 |
major |
X.0.0 | 2.7.0 → 3.0.0 |
beta |
numeric pre-release | 2.7.0 → 2.7.0-1, 2.7.0-1 → 2.7.0-2 |
- Duplicate-tag guard: aborts if
sdk-vX.Y.Zalready exists on the remote - Version bump: updates
asyar-sdk/package.json - Lockfile sync: runs
pnpm installat the monorepo root - Git operations: commits on a new
release/sdk-vX.Y.Zbranch, opens a PR, and pushes thesdk-vX.Y.Ztag - CI trigger: the
sdk-v*tag fires.github/workflows/release-sdk.yml - Build + publish: SDK is built (
pnpm run build:all) and published to npm — the publish step is idempotent (it skips if that version is already on npm, so a re-run is always safe) - GitHub Release: created with auto-generated release notes; tags containing a hyphen (e.g.,
sdk-v2.8.0-1) are marked Pre-release
After CI is green, merge the release PR to land the version bump on main.
The SDK release workflow needs NPM_TOKEN configured at Settings → Secrets and variables → Actions with publish rights for asyar-sdk. Generate an "Automation" token at npmjs.com/settings/Xoshbin/tokens.
Every launcher release also pushes an updated Cask to a personal tap,
Xoshbin/homebrew-asyar, so users can:
brew tap Xoshbin/asyar
brew trust --tap xoshbin/asyar # required once — see note below
brew install --cask asyarThis is a self-owned tap, not the official homebrew-cask repo — submitting there
later requires clearing Homebrew's notability bar, which isn't worth blocking on yet.
Homebrew 6.0+ tap trust: any tap other than
homebrew/core/homebrew/caskis refused by default ("Refusing to load cask ... from untrusted tap") until the user runsbrew trust --tap xoshbin/asyar(orbrew trust --cask xoshbin/asyar/asyarfor just the cask). This is enforced byHOMEBREW_REQUIRE_TAP_TRUST, which is on by default as of brew 6.0 — not something we configure. Any public-facing install instructions (website, README, discussion replies) must include thebrew trustline, or first-time installers hit a dead end. See docs.brew.sh/Tap-Trust.
- Create an empty public GitHub repo named exactly
homebrew-asyarunder theXoshbinaccount (thehomebrew-prefix is what makesbrew tap Xoshbin/asyarresolve to it). - Seed it with
Casks/asyar.rb— grab the current version of that file from the most recentUpdate Homebrew CaskCI run, or generate one by hand for the current release (see the step's heredoc in.github/workflows/release-launcher.ymlfor the exact template). - Generate a fine-grained GitHub PAT scoped to only the
homebrew-asyarrepo withContents: Read and writepermission. - Add it as a secret named
HOMEBREW_TAP_TOKENon theXoshbin/asyarrepo (Settings → Secrets and variables → Actions).
The secret is in place, so the Update Homebrew Cask step in release-launcher.yml
fires on every future v* release automatically. If the tap ever needs re-seeding
(e.g. a new repo after a rename), redo steps 1–2; the step self-skips instead of
failing if HOMEBREW_TAP_TOKEN is ever missing.
- Every launcher release today is a numeric pre-release (
v0.1.1-N); since this is a personal tap, the Cask tracks those too — there's no separate "stable" filter. Alivecheckblock forbrew bump-cask-pr-style automation isn't included because Homebrew'sgithub_lateststrategy only resolves GitHub's/releases/latest, which excludes prereleases entirely — it would find nothing. auto_updates trueis set because Asyar has its own updater (the asyar.org feed). Brew is the install channel; the app's own updater is the stay current channel. This also means plainbrew upgradeskips it — that's intentional, not a bug.- Only the two macOS
.dmgartifacts are needed (Homebrew Cask is macOS-only); Windows/Linux package managers (winget, Flatpak, AUR, …) are tracked separately — winget is covered below; Flatpak/AUR aren't wired up yet.
Every stable launcher release also opens/updates a manifest PR against the official
microsoft/winget-pkgs repo, so Windows users
can:
winget install Xoshbin.AsyarThis targets the real community repo, not a personal fork like the Homebrew tap — there's no equivalent of "just push to your own repo" here. The first submission has to clear Microsoft's manifest validation + moderator review before this can be automated at all, and winget-pkgs won't accept a package whose only available version is a pre-release (see "Pre-release gating" below).
The MSI installers are not code-signed.
build-windowsin the workflow only applies the Tauri updater signature (TAURI_SIGNING_PRIVATE_KEY) — that verifies auto-updates, it isn't an Authenticode/EV certificate. Expect winget/Windows to show a SmartScreen "unknown publisher" warning on install until the MSI is properly code-signed. This is a pre-existing gap, not something introduced by the winget submission — flagging it here since winget is the first channel where end users will actually notice it.
Every Asyar release today is a numeric pre-release (v0.1.1-N, hyphenated). winget-pkgs
policy requires a package's default manifest to track a stable release — pre-release-only
packages aren't accepted as standalone entries (the convention for beta channels is a
separate identifier, e.g. Xoshbin.Asyar.Beta, which isn't set up). So the Update winget manifest step in release-launcher.yml checks the version and skips quietly (no failure)
for any hyphenated tag. The winget channel goes live automatically the first time a real
patch/minor/major release ships (a clean vX.Y.Z tag, no beta keyword) — no code
change needed when that happens.
- Wait for the first stable release.
wingetcreate/Komac need at least one version merged into winget-pkgs to diff against for future updates, and winget-pkgs won't accept a pre-release-only submission (see above) — so this whole channel is blocked until the first non-hyphenatedvX.Y.Ztag exists. - Fork
microsoft/winget-pkgsunder theXoshbinaccount (vedantmgoyal9/winget-releaser's defaultfork-useris the repo owner, i.e.Xoshbin, so no extra config is needed once the fork exists). - Submit the first manifest manually — this can't be automated, since the action only
updates an existing package.
wingetcreateis Windows-only (it needs native Windows APIs to read the MSI, so it can't run on this Mac or in a Linux CI runner); either run it on a Windows machine/VM:(it will prompt for the remaining fields — Publisherwingetcreate new ` --urls "https://github.com/Xoshbin/asyar/releases/download/v<VERSION>/asyar_<VERSION>_x64_en-US.msi|x64" "https://github.com/Xoshbin/asyar/releases/download/v<VERSION>/asyar_<VERSION>_arm64_en-US.msi|arm64" ` --version <VERSION>
Xoshbin, PackageIdentifierXoshbin.Asyar, LicenseAGPL-3.0, Homepagehttps://asyar.org) — or open a manifest PR by hand againstmanifests/x/Xoshbin/Asyar/<VERSION>/following an existing package's layout as a template. Either way it goes through winget-pkgs' normal validation + moderator review like any other new package, so budget for review turnaround time. - Once that PR merges, generate a classic GitHub PAT (Settings → Developer settings →
Personal access tokens → Tokens (classic)) with the
public_reposcope. Fine-grained PATs aren't supported bywinget-releaser(Komac can create the manifest/commit with one, but can't open the PR). - Add it as a secret named
WINGET_TOKENon theXoshbin/asyarrepo (Settings → Secrets and variables → Actions).
Once WINGET_TOKEN is set and a stable release exists in winget-pkgs, the Update winget manifest step fires on every future stable release automatically; it keeps skipping quietly
on pre-releases and on any release before step 5 is done.
- Unlike the Homebrew step (a
run:bash block that canexit 0mid-script), submission goes through thevedantmgoyal9/winget-releaser@v2marketplace action, which does its own MSI inspection via Komac — not worth reimplementing in bash. The "skip quietly" behavior is instead a separateCheck winget eligibilitystep whose output gates the submission step'sif:. - The step lives in this same
publishjob rather than a second workflow triggered onon: release, even though that's the triggerwinget-releaser's own docs show. TheCreate GitHub Releasestep above publishes using the defaultGITHUB_TOKEN, and GitHub does not fire a fresh workflow run off events created by that token — a separateon: release-triggered workflow would simply never start. Running as a step here sidesteps that entirely. - Only the Windows MSI artifacts matter here — macOS/Linux users aren't affected by winget at all.
- Not verified end-to-end. Unlike the Homebrew Cask (confirmed with a real local
brew install --caskon this machine), there's no Windows box here to actually test-install a winget package, and the first real submission needs a stable release + manual review that hasn't happened yet. What was checked: the workflow YAML passesactionlint, the action repo/tag (vedantmgoyal9/winget-releaser@v2) and its required inputs were confirmed against its current README, and theXoshbin/Asyarmanifest path (manifests/x/Xoshbin/Asyar/) doesn't already exist in winget-pkgs (checked via the GitHub API). Treat the first real submission as the actual test.
Closes the original ask in discussion #315 ("can I install Asyar from the CLI?"). macOS and Linux users can run:
curl -fsSL https://raw.githubusercontent.com/Xoshbin/asyar/main/install.sh | shThis downloads and runs install.sh from the repo root, which detects the
OS/arch, queries the GitHub Releases API for the newest release, downloads the matching
asset, and installs it — no Homebrew, no brew trust step, no manual .dmg/.AppImage
handling. To pin a specific version instead of the newest one:
ASYAR_VERSION=0.1.1-38 curl -fsSL https://raw.githubusercontent.com/Xoshbin/asyar/main/install.sh | sh- macOS + Linux only, no
install.ps1. Windows already has an ad hoc PowerShell snippet from discussion #315 and a real package-manager path (winget, once a stable release ships). macOS + Linux had no package-manager-free CLI path before this — Homebrew exists but is macOS-only and needs the extrabrew truststep (see above). If a Windows one-liner is wanted later, it's a separateinstall.ps1, not part of this script. - Linux: AppImage only, no
.debdetection. The release also publishes.debassets, but adding distro detection (aptvs. not), sudo escalation fordpkg -i, and a different install location would roughly double the script's surface for marginal gain — the AppImage is distro-agnostic by design (the same reason discussion #315's original answer picked it), needs no root, and already covers bothaarch64/amd64. It's installed to~/.local/bin/asyar(override withASYAR_INSTALL_DIR), with a PATH check that prints theexport PATH=...line to add if~/.local/binisn't already on it. - No update-checking, always (re)installs. Asyar already has its own auto-updater, so this
script's job is first-install, not staying current — the same reasoning the Homebrew section
gives for
auto_updates true. If an install already exists (/Applications/asyar.appon macOS,~/.local/bin/asyaron Linux) the script prints its detected version and replaces it rather than prompting; acurl | shpipe has no interactive terminal to prompt against safely, and re-running to force a clean reinstall is itself a reasonable use case. - GitHub's
/releases/latestisn't used. Every Asyar release so far is a numeric pre-release (v0.1.1-N, hyphenated tag), and/releases/latestexplicitly excludes pre-releases — it 404s today. The script queries the plain release list withper_page=1and takes the newest entry instead, with nojqdependency (asset URLs are pulled out of the JSON withgrep/sed, matched against the confirmed real asset names — macOS:asyar_{version}_aarch64.dmg/asyar_{version}_x64.dmg; Linux:asyar_{version}_aarch64.AppImage/asyar_{version}_amd64.AppImage— note the arch-naming inconsistency between the two: macOS usesx64, Linux AppImage usesamd64, and the Linux.debassets usearm64rather thanaarch64). - Written in POSIX
sh, not bash — the documented invocation pipes intosh, which isdashon Debian/Ubuntu, not bash. No arrays, no[[ ]], no bashisms. Checked withshellcheck -s sh install.sh(clean);shfmtisn't installed in this repo so there's no auto-formatter for it, matching the "no existing shell lint convention" state noted when this was scoped.
Every step is re-runnable without burning a version number. If a CI run fails
(npm publish, a build target, the website notify), fix the cause and re-run the
workflow with the same tag: GitHub → Actions → the workflow →
Run workflow (workflow_dispatch) → enter the existing tag. A concurrency
guard prevents two runs of the same tag from colliding, npm publish is
idempotent, and the asyar.org notify upserts by version — so re-running is always
safe.
| Scenario | What's safe | Recovery |
|---|---|---|
SDK npm publish fails (bad token / version exists / network) |
Tag + bump on the release branch/PR; npm unchanged | Fix the cause → re-run release-sdk.yml via Run workflow with the same tag. Publish is idempotent: it skips if the version is already on npm. |
| One launcher build target fails (flaky notarization / runner down) | Other artifacts built; nothing published yet | Re-run release-launcher.yml via Run workflow with the same tag — no new version. The concurrency guard prevents races. |
| Website notify fails (asyar.org down / bad token) | GitHub Release already created; updater feed stale | The job fails loudly (red). Re-run via Run workflow; the notify endpoint upserts by version, so it's safe to repeat. |
| Homebrew cask push fails (bad token / tap repo down) | GitHub Release + website notify already done; tap just stale | Re-run via Run workflow with the same tag — the step overwrites Casks/asyar.rb unconditionally, so it's safe to repeat. If HOMEBREW_TAP_TOKEN isn't set yet, the step no-ops instead of failing. |
| winget manifest step fails (bad token / Komac error / stale fork) | GitHub Release + website notify + Homebrew already done; winget-pkgs untouched or mid-PR | Check the Xoshbin winget-pkgs fork for an already-open PR for this version first (avoid duplicates), then re-run via Run workflow with the same tag. Skips quietly (not a failure) if WINGET_TOKEN isn't set yet or the tag is a pre-release. |
install.sh fails for a user (asset match broken, network, GitHub API rate limit) |
Not part of the release pipeline — nothing published is affected, no version to re-tag | Fix install.sh directly on main; no tag/version bump needed, since the raw URL always serves current main. If it's GitHub's unauthenticated API rate limit (60 req/hr per IP), the workaround is downloading the matching asset from the Releases page directly instead of re-running the script. |
| Tag pushed by mistake / wrong version | — | git push --delete origin <tag>, delete the GitHub release, then re-release. The duplicate-tag guard otherwise blocks a duplicate version. |
| Launcher release while npm is down | N/A | Not a failure mode — the launcher reads the SDK version from local asyar-sdk/package.json. |
| A release script half-finished locally (Ctrl-C / error mid-run) | The script cleans up its leftover local branch/tag on the next run | Run it again (--dry-run first to preview); the duplicate-tag guard blocks an accidental re-release of the same version. |
Notes
- Launcher publish intentionally requires all 6 targets — the auto-updater feed must never be half-published. The improvement is one-click recovery, not relaxing that invariant.
- The build runs from the tag (the release branch's bump commit), so released binaries always carry the right version even before the PR merges. Merge the release PR so
maincarries the bump and the next release calculates correctly.
This table is also kept standalone at
docs/how-to/release-recovery.mdfor quick reference during an incident.
| To release… | Run |
|---|---|
| The launcher | pnpm --filter asyar-launcher run release patch (or minor / major / beta; add --dry-run to preview) — tag v* |
| The SDK | pnpm release:sdk patch (or minor / major / beta; add --dry-run to preview) — tag sdk-v* |
| Re-run a failed release | GitHub → Actions → the workflow → Run workflow → same tag |