From ce1ffd0bba3d74a3ee19702b6fd33c721deb6484 Mon Sep 17 00:00:00 2001 From: Daniels-Main Date: Sat, 20 Jun 2026 00:42:29 +0200 Subject: [PATCH] Bump to 0.6.1 and fix in-app updater endpoint - Repoint the Tauri updater at the GitHub Releases manifest (releases/latest/download/latest.json), which tauri-action already publishes. The old strand.danielss.dev/updates host only serves the static landing page, so every update check 404d ("Could not fetch a valid release JSON"). - Add scripts/bump-version.mjs (pnpm version:set) to keep tauri.conf.json, Cargo.toml, Cargo.lock, and both package.json versions in lockstep, so a tag can never outrun the config version Tauri names artifacts from. - Bump 0.5.0 -> 0.6.1 across all manifests. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/strand-tauri/tauri.conf.json | 4 +- package.json | 5 +- scripts/bump-version.mjs | 135 ++++++++++++++++++++++++++++ ui/package.json | 2 +- website/README.md | 6 +- 7 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 scripts/bump-version.mjs diff --git a/Cargo.lock b/Cargo.lock index 22a1cf5..03e7e45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5136,7 +5136,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strand-core" -version = "0.5.0" +version = "0.6.1" dependencies = [ "git2", "gix", @@ -5149,7 +5149,7 @@ dependencies = [ [[package]] name = "strand-tauri" -version = "0.5.0" +version = "0.6.1" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index a048f98..4a8c4fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.package] -version = "0.5.0" +version = "0.6.1" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Daniel Schwarz"] diff --git a/crates/strand-tauri/tauri.conf.json b/crates/strand-tauri/tauri.conf.json index dc34d28..a92b3f0 100644 --- a/crates/strand-tauri/tauri.conf.json +++ b/crates/strand-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Strand", - "version": "0.5.0", + "version": "0.6.1", "identifier": "dev.danielss.strand", "build": { "frontendDist": "../../ui/dist", @@ -51,7 +51,7 @@ "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDg0RkNCRkQyQTk4MUNFNUQKUldSZHpvR3Awci84aENYTEMrTjRFUXU5d2twUUg3UDc4bVVMZ1ZSNFYvdTZwUXlQN2hjWW1nRnkK", "endpoints": [ - "https://strand.danielss.dev/updates/{{target}}/{{arch}}/{{current_version}}" + "https://github.com/danielss-dev/strand/releases/latest/download/latest.json" ] } } diff --git a/package.json b/package.json index 04cce82..b83e490 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "strand", "private": true, - "version": "0.5.0", + "version": "0.6.1", "description": "Fast, friendly Git client (Tauri 2 + Rust + React).", "scripts": { "dev": "pnpm --filter strand-ui dev", @@ -10,7 +10,8 @@ "site": "pnpm --filter strand-website serve", "tauri": "tauri", "tauri:dev": "tauri dev", - "tauri:build": "tauri build" + "tauri:build": "tauri build", + "version:set": "node scripts/bump-version.mjs" }, "devDependencies": { "@tauri-apps/cli": "^2" diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs new file mode 100644 index 0000000..85bf213 --- /dev/null +++ b/scripts/bump-version.mjs @@ -0,0 +1,135 @@ +#!/usr/bin/env node +// Bump the Strand version across every manifest that must stay in lockstep. +// +// Tauri names the release artifacts (and the updater `latest.json`) from the +// version in `tauri.conf.json`, NOT from the git tag — so a tag that outruns +// the config produces mislabeled installers and a manifest that never offers +// the update (see docs/packaging.md). This script keeps all four sources of +// truth — plus the Cargo lockfile — on the same number. +// +// node scripts/bump-version.mjs 0.6.0 # set an explicit version +// node scripts/bump-version.mjs patch # 0.5.0 -> 0.5.1 +// node scripts/bump-version.mjs minor # 0.5.0 -> 0.6.0 +// node scripts/bump-version.mjs major # 0.5.0 -> 1.0.0 +// node scripts/bump-version.mjs minor --dry-run +// +// `website/package.json` is intentionally NOT touched — the landing site +// versions independently of the app. + +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +// X.Y.Z with an optional `-prerelease` and `+build` suffix. +const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; + +function fail(msg) { + console.error(`bump-version: ${msg}`); + process.exit(1); +} + +const args = process.argv.slice(2); +const dryRun = args.includes('--dry-run'); +const target = args.find((a) => !a.startsWith('--')); + +if (!target) { + fail('usage: node scripts/bump-version.mjs [--dry-run]'); +} + +// The root package.json version is the single source we read the current +// number from; every file is asserted to match it before we change anything. +const rootPkgPath = join(repoRoot, 'package.json'); +const current = JSON.parse(readFileSync(rootPkgPath, 'utf8')).version; +if (!SEMVER.test(current)) fail(`current version "${current}" in package.json is not valid semver`); + +function computeNext(spec) { + if (SEMVER.test(spec)) return spec; + const m = current.match(SEMVER); + let [major, minor, patch] = [Number(m[1]), Number(m[2]), Number(m[3])]; + switch (spec) { + case 'major': return `${major + 1}.0.0`; + case 'minor': return `${major}.${minor + 1}.0`; + case 'patch': return `${major}.${minor}.${patch + 1}`; + default: + return fail(`"${spec}" is neither a valid semver version nor one of major|minor|patch`); + } +} + +const next = computeNext(target); +if (!SEMVER.test(next)) fail(`computed version "${next}" is not valid semver`); + +// Each edit is a (file, transform) pair. `transform` returns the new contents +// or throws if the expected current version isn't found — we never want a +// silent partial bump that leaves the manifests disagreeing. +function jsonVersion(label) { + // Replace only the FIRST `"version": "..."` — in every JSON file here that's + // the top-level field (it precedes any dependency blocks). Preserves exact + // formatting, key order, and trailing newline. + return (text, file) => { + const re = /(^\s*"version"\s*:\s*")[^"]*(")/m; + if (!re.test(text)) throw new Error(`no top-level "version" field in ${file} (${label})`); + return text.replace(re, `$1${next}$2`); + }; +} + +function tomlWorkspaceVersion(text, file) { + // The only bare `version = "..."` line in the workspace manifest is under + // [workspace.package]; workspace dependencies use inline `{ version = ... }`. + const re = /^version\s*=\s*"[^"]*"/m; + if (!re.test(text)) throw new Error(`no [workspace.package] version line in ${file}`); + return text.replace(re, `version = "${next}"`); +} + +function lockfileCrates(crates) { + return (text, file) => { + let out = text; + for (const name of crates) { + const re = new RegExp(`(name = "${name}"\\r?\\nversion = ")[^"]*(")`); + if (!re.test(out)) throw new Error(`no [[package]] entry for "${name}" in ${file}`); + out = out.replace(re, `$1${next}$2`); + } + return out; + }; +} + +const edits = [ + ['package.json', jsonVersion('root package')], + ['ui/package.json', jsonVersion('ui package')], + ['crates/strand-tauri/tauri.conf.json', jsonVersion('tauri config')], + ['Cargo.toml', tomlWorkspaceVersion], + ['Cargo.lock', lockfileCrates(['strand-core', 'strand-tauri'])], +]; + +console.log(`Bumping ${current} -> ${next}${dryRun ? ' (dry run, no files written)' : ''}\n`); + +let changed = 0; +for (const [rel, transform] of edits) { + const abs = join(repoRoot, rel); + let text; + try { + text = readFileSync(abs, 'utf8'); + } catch { + fail(`cannot read ${rel}`); + } + let updated; + try { + updated = transform(text, rel); + } catch (e) { + fail(e.message); + } + const path = relative(repoRoot, abs).replace(/\\/g, '/'); + if (updated === text) { + console.log(` = ${path} (already ${next})`); + continue; + } + if (!dryRun) writeFileSync(abs, updated); + console.log(` ${dryRun ? '~' : '✓'} ${path}`); + changed++; +} + +console.log( + `\n${dryRun ? 'Would update' : 'Updated'} ${changed} file(s).` + + (dryRun ? '' : `\n\nNext: commit, then tag the release to match:\n git commit -am "Bump version to ${next}"\n git tag v${next} && git push origin v${next}`), +); diff --git a/ui/package.json b/ui/package.json index 60a8e5a..ee2feec 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "strand-ui", "private": true, - "version": "0.5.0", + "version": "0.6.1", "type": "module", "scripts": { "dev": "node scripts/clean-stale-js.mjs && vite", diff --git a/website/README.md b/website/README.md index 74b0a8f..a099c33 100644 --- a/website/README.md +++ b/website/README.md @@ -42,6 +42,8 @@ Preview locally with `pnpm site` from the repo root (serves on - [ ] Point "Get a commercial license" at `COMMERCIAL.md` / a purchase flow once that exists (currently links to the repo). - [ ] Add an `og:image` (1200×630) for link unfurls. -- [ ] Host the Tauri updater manifest (`latest.json`) on the same domain — - `tauri.conf.json` expects `strand.danielss.dev`. +- [x] Tauri updater manifest (`latest.json`) is served from GitHub Releases + (`releases/latest/download/latest.json`), which `tauri-action` publishes + automatically — `tauri.conf.json` points the updater there. No custom + `/updates` route on `strand.danielss.dev` is needed. - [ ] Keep the perf numbers in §02 in sync with `docs/perf-baseline.md`.