Test builds: tool/dev_release.sh builds Linux here and publishes to a dev prerelease that dev builds update from

X.Y.Z-dev.N builds read the dev prerelease's manifest as well as the
stable one and take the newer, so a tester gets each test build through
the normal update and lands back on stable at the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-11 02:29:40 +03:00
1 parent 1b0a3db439
commit ab133dd82f
9 files changed
+228 -23

No files matched your search

+35 -1
View File
@@ -28,6 +28,11 @@ final _log = createLogger('update');
const updateManifestUrl =
'https://git.senkochan.moe/Foxyz/sync_replay_project/raw/branch/main/updates/manifest.json';
/// Test builds (`tool/dev_release.sh`): one Gitea prerelease, tag `dev`,
/// whose assets each run replaces. Only dev builds read it.
const devManifestUrl =
'https://git.senkochan.moe/Foxyz/sync_replay_project/releases/download/dev/manifest.json';
const appVersion = String.fromEnvironment('APP_VERSION',
defaultValue: '0.0.0-dev');
@@ -161,6 +166,34 @@ Future<UpdateManifest> fetchManifest(
}
}
/// A test build (`1.1.3-dev.4`); `0.0.0-dev` (a dev run) is not one.
bool isDevBuild(String version) => version.contains('-dev.');
/// The manifest to update from: the stable one, and for a [isDevBuild]
/// also the dev one, whichever is newer (either alone if the other fails).
/// A release outranks its own test builds, so testers return to stable.
Future<UpdateManifest> fetchLatestManifest(
{HttpClient? http,
String currentVersion = appVersion,
String url = updateManifestUrl,
String devUrl = devManifestUrl}) async {
final stable = fetchManifest(http: http, url: url);
if (!isDevBuild(currentVersion)) return stable;
stable.ignore(); // awaited below; don't let an early failure go uncaught
final dev = await fetchManifest(http: http, url: devUrl)
.then<UpdateManifest?>((m) => m, onError: (Object e) {
_log.warning('dev manifest: $e');
return null;
});
if (dev == null) return stable;
try {
final s = await stable;
return isNewerVersion(s.version, dev.version) ? dev : s;
} catch (_) {
return dev;
}
}
typedef DownloadProgress = void Function(int received, int? total);
Future<void> downloadFile(String url, File dest,
@@ -538,7 +571,8 @@ Future<int> runUpgrade(
stdout.writeln('$program $currentVersion (proto ${RelayConstants.protoVersion})');
late final UpdateManifest manifest;
try {
manifest = await fetchManifest(http: http, url: manifestUrl);
manifest = await fetchLatestManifest(
http: http, currentVersion: currentVersion, url: manifestUrl);
} catch (e) {
stdout.writeln('update check failed: $e');
return 1;
+29
View File
@@ -208,6 +208,35 @@ void main() {
}
});
test('dev builds also read the dev manifest, the newer wins', () async {
final versions = {'/stable': '1.1.2', '/dev': '1.1.3-dev.2'};
final http = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
http.listen((req) async {
final v = versions[req.uri.path];
if (v == null) {
req.response.statusCode = 404;
} else {
req.response.write(jsonEncode(
{'version': v, 'protocol': 1, 'url': 'http://x/$v.tar.gz'}));
}
await req.response.close();
});
final base = 'http://127.0.0.1:${http.port}';
Future<String> latest(String current) async => (await fetchLatestManifest(
currentVersion: current, url: '$base/stable', devUrl: '$base/dev'))
.version;
try {
expect(await latest('1.1.2'), '1.1.2'); // stable builds never see dev
expect(await latest('1.1.3-dev.1'), '1.1.3-dev.2');
versions['/stable'] = '1.1.3'; // the release outranks its test builds
expect(await latest('1.1.3-dev.2'), '1.1.3');
versions.remove('/stable');
expect(await latest('1.1.3-dev.1'), '1.1.3-dev.2');
} finally {
await http.close(force: true);
}
});
group('upgrade flow', () {
late Directory work;
late _Server server;