Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 2 additions & 2 deletions crates/strand-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
]
}
}
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
Expand Down
135 changes: 135 additions & 0 deletions scripts/bump-version.mjs
Original file line number Diff line number Diff line change
@@ -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 <version|major|minor|patch> [--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}`),
);
2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
6 changes: 4 additions & 2 deletions website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Loading