AGPL-3.0-only, a source link in the app, and everything on dev since #5 #6
@@ -256,9 +256,12 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing the APK to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
|
||||
@@ -278,9 +278,12 @@ jobs:
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
@@ -412,9 +415,12 @@ jobs:
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
@@ -473,11 +479,21 @@ jobs:
|
||||
# No tag arm any more. A `v*` tag does not reach this workflow at all — it
|
||||
# triggers release.yml, which writes a changelog and builds nothing.
|
||||
case "${GITHUB_REF_NAME}" in
|
||||
main) export RELEASE_TAG=stable
|
||||
main) RELEASE_TAG="$(sh packaging/channel-tag.sh stable)"
|
||||
export RELEASE_NOTES="Stable build from ${GITHUB_SHA}" ;;
|
||||
*) export RELEASE_TAG=dev
|
||||
*) RELEASE_TAG="$(sh packaging/channel-tag.sh dev)"
|
||||
# TEMPORARY one-shot bridge (Scribe #2184). Desktop apps installed
|
||||
# before the rename have .../download/dev/latest.json compiled in,
|
||||
# so the manifest is also written to the old `dev` release; its
|
||||
# URLs name dev-rolling assets, so those apps update once into a
|
||||
# build that reads the new tag. Delete this line together with the
|
||||
# old `dev` release and tag.
|
||||
export BRIDGE_TAG=dev
|
||||
export RELEASE_NOTES="Development build from ${GITHUB_SHA}" ;;
|
||||
esac
|
||||
# Assigned bare above so a failing channel-tag.sh fails this step;
|
||||
# `export X="$(...)"` would swallow its exit status.
|
||||
export RELEASE_TAG
|
||||
export PRUNE_OLD_ASSETS=true
|
||||
APP_VERSION="$version" DISPLAY_VERSION="$display" \
|
||||
bash desktop/packaging/write-manifest.sh
|
||||
|
||||
+16
-9
@@ -409,18 +409,25 @@ the lockfile format and the picked versions identical to what CI would have
|
||||
chosen. Commit the result in the same change as the `Cargo.toml` edit — a
|
||||
manifest change pushed without it fails the gate.
|
||||
|
||||
## Pushing: `dev` is both a branch and a tag
|
||||
## Channel releases: `dev-rolling` and `stable`
|
||||
|
||||
`git push origin dev` fails in this repo:
|
||||
The two update channels are releases on fixed tags, because Fabled-Git has no
|
||||
`/releases/latest/download/<asset>` route and the updater needs a permanent URL.
|
||||
The **channel** is still called `dev` everywhere a person sees it; its **release
|
||||
tag** is `dev-rolling`. `packaging/channel-tag.sh` is the one mapping CI reads.
|
||||
`desktop/src-tauri/src/update.rs` and `desktop/packaging/install.sh` carry their
|
||||
own copy because neither can run it — change all three together.
|
||||
|
||||
```
|
||||
error: src refspec dev matches more than one
|
||||
```
|
||||
The tag used to be `dev`, which shadowed the branch of the same name: once a clone
|
||||
had fetched it, `git push origin dev` failed with
|
||||
`error: src refspec dev matches more than one` (Scribe #2184). Never name a channel
|
||||
tag after a branch; `tests/test_channel_tag.py` and the `update.rs` tests fail if
|
||||
one is.
|
||||
|
||||
The rolling update channel is a release on a **fixed tag named `dev`** (the tag
|
||||
never moves — Fabled-Git has no `/releases/latest/download/<asset>` route, so the
|
||||
updater needs a permanent URL). Once that tag is fetched locally, the short name
|
||||
`dev` resolves to both `refs/heads/dev` and `refs/tags/dev`. Fully qualify it:
|
||||
**Transitional, from 2026-09-10:** the old `dev` release still exists so desktop
|
||||
apps installed from it can update across — the manifest job writes `latest.json`
|
||||
to it too (`BRIDGE_TAG=dev` in `desktop.yml`). Until that release and its tag are
|
||||
deleted, fully qualify pushes:
|
||||
|
||||
```
|
||||
git push origin refs/heads/dev:refs/heads/dev
|
||||
|
||||
@@ -93,7 +93,16 @@ say "Finding the latest ThoughtSync build on the $channel channel…"
|
||||
# are pruned to the current build, so the tag alone names the newest build on that
|
||||
# channel — which is exactly what an installer wants and what the in-app updater
|
||||
# already reads.
|
||||
json="$(curl -fsSL "$API/releases/tags/$channel" 2>/dev/null)" ||
|
||||
# The channel's RELEASE TAG, which is not always its name: `dev` publishes on the
|
||||
# tag `dev-rolling`, because a tag called `dev` shadowed the branch of the same name
|
||||
# and broke `git push origin dev` (Scribe #2184). Same mapping as
|
||||
# packaging/channel-tag.sh — inlined because this script is fetched alone through a
|
||||
# pipe and has nothing to source.
|
||||
case "$channel" in
|
||||
dev) tag=dev-rolling ;;
|
||||
*) tag="$channel" ;;
|
||||
esac
|
||||
json="$(curl -fsSL "$API/releases/tags/$tag" 2>/dev/null)" ||
|
||||
die "the $channel channel has nothing published yet."
|
||||
|
||||
# Pull asset URLs straight out of the release JSON (no jq). Anchored on the closing
|
||||
|
||||
@@ -32,8 +32,9 @@ set -euo pipefail
|
||||
: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required (the tag, e.g. v0.1.0)}"
|
||||
|
||||
# The release to publish to. Defaults to the pushed tag (the versioned, stable
|
||||
# case). M10.9 also calls this with RELEASE_TAG=dev to maintain the rolling
|
||||
# development channel — a release whose tag never moves, because Forgejo has no
|
||||
# case). The channel publish steps call this with the channel's fixed tag —
|
||||
# `stable`, or `dev-rolling` for the dev channel (packaging/channel-tag.sh) — to
|
||||
# maintain a rolling release whose tag never moves, because Forgejo has no
|
||||
# `/releases/latest/download/<asset>` route for an updater to point at.
|
||||
RELEASE_TAG="${RELEASE_TAG:-$GITHUB_REF_NAME}"
|
||||
RELEASE_PRERELEASE="${RELEASE_PRERELEASE:-false}"
|
||||
@@ -123,7 +124,11 @@ first_id() { grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE
|
||||
# every green push to dev, `stable` on every merge to main. Each says so, because a
|
||||
# release that prunes its own assets behaves differently from a versioned one and a
|
||||
# reader deserves to know which they are looking at.
|
||||
if [ "$TAG" = "dev" ]; then
|
||||
# Keyed on the TAG, which for the dev channel is `dev-rolling`, not `dev` — a tag
|
||||
# named `dev` shadowed the branch (Scribe #2184). CHANNEL_LABEL is the name a person
|
||||
# reads in the release title, so the rename does not leak into it.
|
||||
if [ "$TAG" = "dev-rolling" ]; then
|
||||
CHANNEL_LABEL='dev (rolling)'
|
||||
INSTALL_TAIL='sh -s -- --channel dev'
|
||||
# Backticks BARE, not `\``. The heredoc below is unquoted, so there the backslash
|
||||
# is the shell's — it suppresses command substitution and never reaches the JSON.
|
||||
@@ -131,18 +136,20 @@ if [ "$TAG" = "dev" ]; then
|
||||
# body as `\``, which is not a legal JSON escape: Forgejo answers 422.
|
||||
CHANNEL_NOTE='\n\nThis is the rolling **dev** channel: republished on every green push to `dev`, and pruned to the current build.'
|
||||
elif [ "$TAG" = "stable" ]; then
|
||||
CHANNEL_LABEL='stable'
|
||||
# install.sh defaults to stable, so no flag.
|
||||
INSTALL_TAIL='sh'
|
||||
CHANNEL_NOTE='\n\nThis is the rolling **stable** channel: republished on every merge to `main`, and pruned to the current build. No tag is required for a build to arrive here.'
|
||||
else
|
||||
CHANNEL_LABEL="$TAG"
|
||||
INSTALL_TAIL='sh'
|
||||
CHANNEL_NOTE=''
|
||||
fi
|
||||
|
||||
echo "==> Creating release for $TAG"
|
||||
BODY=$(cat <<JSON
|
||||
{"tag_name":"$TAG","name":"ThoughtSync $TAG","draft":false,"prerelease":$RELEASE_PRERELEASE,
|
||||
"body":"ThoughtSync $TAG.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/thoughtsync/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
|
||||
{"tag_name":"$TAG","name":"ThoughtSync $CHANNEL_LABEL","draft":false,"prerelease":$RELEASE_PRERELEASE,
|
||||
"body":"ThoughtSync $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/thoughtsync/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
|
||||
JSON
|
||||
)
|
||||
|
||||
@@ -158,7 +165,7 @@ JSON
|
||||
# whether it has a JSON encoder; this script cannot assume python3 is on PATH in
|
||||
# every image that sources it.
|
||||
if [ -n "${RELEASE_BODY_JSON:-}" ]; then
|
||||
BODY="{\"tag_name\":\"$TAG\",\"name\":\"ThoughtSync $TAG\",\"draft\":false,\"prerelease\":$RELEASE_PRERELEASE,\"body\":\"$RELEASE_BODY_JSON\"}"
|
||||
BODY="{\"tag_name\":\"$TAG\",\"name\":\"ThoughtSync $CHANNEL_LABEL\",\"draft\":false,\"prerelease\":$RELEASE_PRERELEASE,\"body\":\"$RELEASE_BODY_JSON\"}"
|
||||
fi
|
||||
# 409 = a release for this tag already exists (re-run) — fall through to lookup.
|
||||
release="$(ALLOW_CODES=409 api POST "$API/releases" -H "Content-Type: application/json" -d "$BODY")"
|
||||
|
||||
@@ -161,6 +161,41 @@ printf '{\n "version_name": "%s",\n "version_code": "%s"\n}\n' \
|
||||
"$DISPLAY_VERSION" "$APP_VERSION" > "$work/thoughtsync-desktop.json"
|
||||
replace_asset "$work/thoughtsync-desktop.json" "thoughtsync-desktop.json"
|
||||
|
||||
# --- TEMPORARY: the one-shot bridge off a retired channel tag ------------------
|
||||
#
|
||||
# The dev channel's tag was renamed `dev` -> `dev-rolling`, because a tag named `dev`
|
||||
# shadowed the branch and broke `git push origin dev` (Scribe #2184). Desktop apps
|
||||
# installed before the rename have `.../download/dev/latest.json` compiled in, so
|
||||
# without this they would never be offered another build.
|
||||
#
|
||||
# BRIDGE_TAG names the old release; the SAME manifest is written there too. Its URLs
|
||||
# are absolute and name `dev-rolling` assets, so an old app updates once into a build
|
||||
# that reads the new tag, and never reads this one again. Only latest.json goes
|
||||
# across: the sidecar is read by CI and the image build, which already follow the
|
||||
# new tag.
|
||||
#
|
||||
# A missing bridge release is logged and skipped, not fatal — by then it has been
|
||||
# deleted on purpose. A failed WRITE to one that exists fails the job: a bridge that
|
||||
# silently stops is an installed base stranded without a word. Remove this block,
|
||||
# the BRIDGE_TAG line in desktop.yml, and the old release and tag together.
|
||||
if [ -n "${BRIDGE_TAG:-}" ]; then
|
||||
bridge_release="$(curl -sS "${AUTH[@]}" "$API/releases/tags/$BRIDGE_TAG" || true)"
|
||||
bridge_id="$(printf '%s' "$bridge_release" | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+' || true)"
|
||||
if [ -z "$bridge_id" ]; then
|
||||
echo "==> No release tagged $BRIDGE_TAG — nothing to bridge (retired?)."
|
||||
else
|
||||
# A subshell, so replace_asset's globals point at the bridge release for this
|
||||
# one call only and the prune below still works on $RELEASE_TAG.
|
||||
(
|
||||
release_id="$bridge_id"
|
||||
RELEASE_TAG="$BRIDGE_TAG"
|
||||
assets="$(curl -sS "${AUTH[@]}" "$API/releases/$release_id/assets")"
|
||||
replace_asset "$work/latest.json" "latest.json"
|
||||
)
|
||||
echo "==> Bridged: $BRIDGE_TAG/latest.json now advertises the same build."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> Done. $RELEASE_TAG now advertises $DISPLAY_VERSION ($APP_VERSION) for ${#entries[@]} platform(s)."
|
||||
|
||||
# --- prune superseded builds from a rolling channel ---------------------------
|
||||
|
||||
@@ -84,8 +84,21 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// The release tag holding this channel's builds — not the channel's name.
|
||||
///
|
||||
/// `dev` used to be both, and a tag named `dev` shadowed the branch of the same
|
||||
/// name, so `git push origin dev` failed as ambiguous (Scribe #2184). The channel
|
||||
/// is still `dev` everywhere a person sees it and in the stored pref; only the tag
|
||||
/// moved. Mirrors `packaging/channel-tag.sh`, which is what CI publishes with.
|
||||
fn release_tag(self) -> &'static str {
|
||||
match self {
|
||||
Channel::Stable => "stable",
|
||||
Channel::Dev => "dev-rolling",
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_url(self) -> String {
|
||||
format!("{FEED_BASE}/{}/latest.json", self.as_str())
|
||||
format!("{FEED_BASE}/{}/latest.json", self.release_tag())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,10 +428,23 @@ mod tests {
|
||||
assert_eq!(effective(&db), Channel::Dev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_channel_feed_is_named_like_a_branch() {
|
||||
// A release tag spelled like a branch makes `git push origin <branch>`
|
||||
// ambiguous once the tag is fetched (Scribe #2184). `dev` and `main` are the
|
||||
// branches this repo pushes.
|
||||
for channel in [Channel::Stable, Channel::Dev] {
|
||||
assert!(
|
||||
!["dev", "main"].contains(&channel.release_tag()),
|
||||
"{channel:?} publishes on a tag named like a branch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_channel_has_its_own_fixed_feed() {
|
||||
assert!(Channel::Stable.feed_url().ends_with("/stable/latest.json"));
|
||||
assert!(Channel::Dev.feed_url().ends_with("/dev/latest.json"));
|
||||
assert!(Channel::Dev.feed_url().ends_with("/dev-rolling/latest.json"));
|
||||
assert_ne!(Channel::Stable.feed_url(), Channel::Dev.feed_url());
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ real values are already known.
|
||||
so a build dropped there survives container recreation — and survives an image
|
||||
upgrade, which is the point of an override.
|
||||
|
||||
Both files are published to the rolling `dev` release on every green Android
|
||||
build:
|
||||
Both files are published to the rolling dev-channel release (tag `dev-rolling`)
|
||||
on every green Android build:
|
||||
|
||||
```sh
|
||||
REPO=https://git.fabledsword.com/bvandeusen/thoughtsync
|
||||
@@ -64,7 +64,7 @@ TOKEN=... # a Fabled-Git token with read access; the instance is private
|
||||
|
||||
for f in thoughtsync.apk thoughtsync-android.json; do
|
||||
curl -fsSL -H "Authorization: token $TOKEN" \
|
||||
-o "/tmp/$f" "$REPO/releases/download/dev/$f"
|
||||
-o "/tmp/$f" "$REPO/releases/download/dev-rolling/$f"
|
||||
done
|
||||
|
||||
# Copy the sidecar LAST. A sidecar that does not match the APK beside it is not a
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# The release tag that holds a channel's builds.
|
||||
#
|
||||
# channel-tag.sh <dev|stable>
|
||||
#
|
||||
# A channel and its release tag are two different names, and for `dev` they differ.
|
||||
# The channel is what a person picks and sees (`install.sh --channel dev`, the app's
|
||||
# settings); the tag is where CI publishes and where every reader fetches from.
|
||||
#
|
||||
# `dev` used to be both. A tag named `dev` shadowed the branch named `dev`, so once
|
||||
# a clone had fetched tags, `git push origin dev` failed with "src refspec dev
|
||||
# matches more than one" (Scribe #2184). The tag is `dev-rolling` now, matching
|
||||
# roundtable-android. Never spell a channel tag like a branch — tests/test_channel_tag.py
|
||||
# fails if one is.
|
||||
#
|
||||
# The ONE mapping CI reads. Two copies exist because their readers cannot run this:
|
||||
# `desktop/src-tauri/src/update.rs` (compiled into the app) and
|
||||
# `desktop/packaging/install.sh` (fetched alone through a pipe). Change all three.
|
||||
set -eu
|
||||
|
||||
case "${1:-}" in
|
||||
dev) echo dev-rolling ;;
|
||||
stable) echo stable ;;
|
||||
*)
|
||||
echo "channel-tag.sh: unknown channel '${1:-}' (expected dev or stable)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -38,7 +38,10 @@ esac
|
||||
|
||||
SERVER="${GITHUB_SERVER_URL:-https://git.fabledsword.com}"
|
||||
REPO="${GITHUB_REPOSITORY:-bvandeusen/thoughtsync}"
|
||||
BASE="$SERVER/$REPO/releases/download/$channel"
|
||||
# The channel's release tag, not its name — `dev` lives on `dev-rolling`
|
||||
# (packaging/channel-tag.sh, Scribe #2184).
|
||||
tag="$(sh "$(dirname "$0")/channel-tag.sh" "$channel")"
|
||||
BASE="$SERVER/$REPO/releases/download/$tag"
|
||||
|
||||
mkdir -p "$dest"
|
||||
|
||||
|
||||
@@ -96,16 +96,21 @@ fetch() {
|
||||
# merge to `main` (run 4857): exit 1, no output, 0.16 seconds, on the one channel that
|
||||
# had no APK published yet. Its three neighbours hid it until then.
|
||||
published_for() {
|
||||
# The channel's release TAG, not its name — `dev` lives on `dev-rolling`
|
||||
# (packaging/channel-tag.sh, Scribe #2184). `|| exit 2` rather than letting an
|
||||
# empty tag through: a lookup against `.../download//latest.json` would find
|
||||
# nothing and read as "nothing published", which this guard PASSES.
|
||||
_tag="$(sh "$ROOT/packaging/channel-tag.sh" "$2")" || exit 2
|
||||
case "$1" in
|
||||
desktop)
|
||||
# What the UPDATER reads. The manifest is the thing that decides whether a
|
||||
# client is offered a build, so it is the authority on what is published.
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/latest.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/latest.json" \
|
||||
| grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
android)
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
|
||||
| grep -oE '"version_code"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 \
|
||||
| grep -oE '[0-9]+$'; } || true
|
||||
;;
|
||||
@@ -117,10 +122,11 @@ published_for() {
|
||||
# compares identity — for Android those are different fields, and conflating them
|
||||
# would make every build look like a change (the code is build-time; it always moves).
|
||||
published_name() {
|
||||
_tag="$(sh "$ROOT/packaging/channel-tag.sh" "$2")" || exit 2
|
||||
case "$1" in
|
||||
desktop) published_for desktop "$2" ;;
|
||||
android)
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
|
||||
| grep -oE '"version_name"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""The channel -> release tag mapping — `packaging/channel-tag.sh`.
|
||||
|
||||
A channel's release tag must never be spelled like a branch. The dev channel's tag
|
||||
was `dev`, which shadowed the `dev` branch: once a clone had fetched tags,
|
||||
`git push origin dev` failed as an ambiguous refspec (Scribe #2184).
|
||||
|
||||
These run the script itself rather than reading it, so a mapping that is correct
|
||||
on the page but broken in execution still fails here.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parent.parent / "packaging" / "channel-tag.sh"
|
||||
|
||||
# The branches this repo pushes. A tag with either name makes the push ambiguous.
|
||||
BRANCHES = {"dev", "main"}
|
||||
|
||||
|
||||
def tag(channel: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
["sh", str(SCRIPT), channel],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("channel", ["dev", "stable"])
|
||||
def test_no_channel_tag_is_spelled_like_a_branch(channel: str) -> None:
|
||||
result = tag(channel)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout.strip() not in BRANCHES
|
||||
|
||||
|
||||
def test_the_dev_channel_publishes_on_dev_rolling() -> None:
|
||||
# Pinned exactly, not just "not a branch": update.rs and install.sh carry
|
||||
# their own copy of this value, and a silent rename here would strand them.
|
||||
assert tag("dev").stdout.strip() == "dev-rolling"
|
||||
|
||||
|
||||
def test_stable_keeps_its_tag() -> None:
|
||||
assert tag("stable").stdout.strip() == "stable"
|
||||
|
||||
|
||||
def test_an_unknown_channel_fails_instead_of_naming_a_tag() -> None:
|
||||
# Every caller captures the output as a URL segment. An empty string on
|
||||
# success would build `.../download//latest.json` and read as "nothing
|
||||
# published" — so an unknown channel must exit non-zero and print nothing.
|
||||
result = tag("nightly")
|
||||
assert result.returncode != 0
|
||||
assert result.stdout == ""
|
||||
Reference in New Issue
Block a user