From 38c1abba1d5dc43a66814db268f4eff986a7aa9c Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 12 Oct 2026 16:35:41 +0000 Subject: [PATCH] Each service its own version: ipx, ipx-directory, the page (#207) The workspace stops holding one version. ipx keeps 0.12.1-dev and its vX.Y.Z tags; the directory starts at 0.1.0-dev, tagged directory-vX.Y.Z, and the page at 0.1.0-dev in package.json, tagged web-vX.Y.Z, which web/build.mjs gives the script for the logo's tooltip beside ipx's. crates/feed is a library, not released. CLAUDE.md's release steps are for one service at a time. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 38 ++++++++++++++++++++++++----------- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- crates/directory/CHANGELOG.md | 2 ++ crates/directory/Cargo.toml | 2 +- crates/directory/src/main.rs | 2 +- crates/feed/Cargo.toml | 3 ++- crates/ipx/CHANGELOG.md | 2 ++ crates/ipx/Cargo.toml | 2 +- package.json | 1 + tests/ui/app.spec.js | 2 ++ web/CHANGELOG.md | 3 +++ web/build.mjs | 5 ++++- web/src/dialogs.ts | 3 ++- 14 files changed, 51 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index de76158..21e650a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -266,18 +266,32 @@ say, such as what was wrong before or what it cost to find out, it goes in the c body, where `git log` and `git blame` find it beside the change. (There was a long-form `docs/history.md` until 0.7.0; it grew too large to be useful and was removed. It is in git.) -Cutting a release, of the three together while they share one version: in each changelog, first read `[Unreleased]` against the last release, not line by line, and rewrite -it as what changed between the two. Lines go in as the work happens, so they record the way there: -a feature added and then reworked in the same release is one line saying what it is now, under -Added; something added and taken out again before the release, or a fix for a bug no release ever -had, is not there at all; and "instead of" and "no longer" compare with the last release, not with -last week. Issue numbers stay, all of them, on the line that survives. Then rename `[Unreleased]` to -`## [X.Y.Z] - YYYY-MM-DD` and open a new empty `[Unreleased]` above it, set `version` in the root `Cargo.toml` (`[workspace.package]`) to `X.Y.Z` (dropping `-dev`), tag the commit -`vX.Y.Z`, and update the compare links at the bottom of each changelog. A service with nothing -under `[Unreleased]` gets no entry for that release. Then, in the next commit, -set `version` to the next patch with `-dev` (after 0.9.0, `0.9.1-dev`), so a build between releases says so -in the logo's tooltip instead of claiming to be the last release. The release that follows can -still be a minor or major one; `-dev` only says the work comes after `X.Y.Z`. +Each service has its own version and is released on its own, when its changelog has something to +release: + +| service | version in | changelog | tag | +|---|---|---|---| +| ipx | `crates/ipx/Cargo.toml` | `crates/ipx/CHANGELOG.md` | `vX.Y.Z`, as every release before the split | +| ipx-directory | `crates/directory/Cargo.toml` | `crates/directory/CHANGELOG.md` | `directory-vX.Y.Z` | +| ipx-web, the page | `package.json` | `web/CHANGELOG.md` | `web-vX.Y.Z` | + +`crates/feed` is a library the two Rust services build in, not released (`0.0.0`); a change to it +is a line in the changelog of each service it changes. The page shows ipx's version and its own in +the logo's tooltip; the directory logs its own at start; each binary answers `--version`. + +Cutting a release of one: first read its `[Unreleased]` against its last release, not line by +line, and rewrite it as what changed between the two. Lines go in as the work happens, so they +record the way there: a feature added and then reworked in the same release is one line saying +what it is now, under Added; something added and taken out again before the release, or a fix for +a bug no release ever had, is not there at all; and "instead of" and "no longer" compare with the +last release, not with last week. Issue numbers stay, all of them, on the line that survives. Then +rename `[Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` and open a new empty `[Unreleased]` above it, +set the service's version to `X.Y.Z` (dropping `-dev`), tag the commit with the service's tag, and +update the compare links at the bottom of its changelog. Then, in the next commit, set its version +to the next patch with `-dev` (after 0.9.0, `0.9.1-dev`), so a build between releases says so +instead of claiming to be the last release. The release that follows can still be a minor or major +one; `-dev` only says the work comes after `X.Y.Z`. A service never released is at `0.1.0-dev`, +working towards its first release, `0.1.0`. **Traces first, logs second.** Anything that does work (a service, a handler, a job, a call to another service) gets spans, and passes `traceparent` on to whatever it calls, so one trace in diff --git a/Cargo.lock b/Cargo.lock index 479ee92..0846053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1856,7 +1856,7 @@ dependencies = [ [[package]] name = "ipx-directory" -version = "0.12.1-dev" +version = "0.1.0-dev" dependencies = [ "anyhow", "axum", @@ -1881,7 +1881,7 @@ dependencies = [ [[package]] name = "ipx-feed" -version = "0.12.1-dev" +version = "0.0.0" dependencies = [ "anyhow", "atom_syndication", diff --git a/Cargo.toml b/Cargo.toml index ea0dbca..a1a34c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,9 @@ members = ["crates/ipx", "crates/feed", "crates/directory"] resolver = "3" -# One version for the whole workspace: what a release sets, and what the logo's tooltip shows. +# Each service has its own version, in its own Cargo.toml (ipx, ipx-directory) or package.json +# (the page), released and tagged on its own; see CLAUDE.md. [workspace.package] -version = "0.12.1-dev" edition = "2024" [workspace.dependencies] diff --git a/crates/directory/CHANGELOG.md b/crates/directory/CHANGELOG.md index e141bb3..edf9dad 100644 --- a/crates/directory/CHANGELOG.md +++ b/crates/directory/CHANGELOG.md @@ -3,6 +3,8 @@ The directory of public podcasts that ipx servers share (docs/directory.md). ipx's changelog is [crates/ipx/CHANGELOG.md](../ipx/CHANGELOG.md), the page's [web/CHANGELOG.md](../../web/CHANGELOG.md). +Its version is in `crates/directory/Cargo.toml`, and a release is tagged `directory-vX.Y.Z`. + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/crates/directory/Cargo.toml b/crates/directory/Cargo.toml index 4b4cd78..882ecc2 100644 --- a/crates/directory/Cargo.toml +++ b/crates/directory/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ipx-directory" -version.workspace = true +version = "0.1.0-dev" edition.workspace = true [[bin]] diff --git a/crates/directory/src/main.rs b/crates/directory/src/main.rs index d7c1e67..0f38daa 100644 --- a/crates/directory/src/main.rs +++ b/crates/directory/src/main.rs @@ -98,7 +98,7 @@ async fn serve(path: &std::path::Path) -> Result<()> { let dropped = dir.set_tokens(tokens).await?; let listener = tokio::net::TcpListener::bind(&cfg.bind).await.with_context(|| format!("listening on {}", cfg.bind))?; - tracing::info!(bind = %cfg.bind, tokens = cfg.tokens.len(), dropped, "directory started"); + tracing::info!(version = env!("CARGO_PKG_VERSION"), bind = %cfg.bind, tokens = cfg.tokens.len(), dropped, "directory started"); tokio::spawn(ipx_directory::refresher(dir.clone())); tokio::spawn(ipx_directory::reloader(dir.clone(), path.to_owned())); axum::serve(listener, ipx_directory::router(dir)) diff --git a/crates/feed/Cargo.toml b/crates/feed/Cargo.toml index 506fb59..5f656c0 100644 --- a/crates/feed/Cargo.toml +++ b/crates/feed/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "ipx-feed" -version.workspace = true +version = "0.0.0" +publish = false edition.workspace = true [dependencies] diff --git a/crates/ipx/CHANGELOG.md b/crates/ipx/CHANGELOG.md index 288169c..2f737cf 100644 --- a/crates/ipx/CHANGELOG.md +++ b/crates/ipx/CHANGELOG.md @@ -5,6 +5,8 @@ The daemon: the API, scans, downloads, DeadAir and the database. The page has it [crates/directory/CHANGELOG.md](../directory/CHANGELOG.md). Until 0.12.0 this was the changelog of all of ipx, the page included; they were split with the services (#207). +Its version is in `crates/ipx/Cargo.toml`, and a release is tagged `vX.Y.Z`. + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/crates/ipx/Cargo.toml b/crates/ipx/Cargo.toml index 52b0b22..ca6c614 100644 --- a/crates/ipx/Cargo.toml +++ b/crates/ipx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ipx" -version.workspace = true +version = "0.12.1-dev" edition.workspace = true [dependencies] diff --git a/package.json b/package.json index a900298..e95897f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "ipx-web", + "version": "0.1.0-dev", "private": true, "description": "Builds the ipx web pages from web/src (web/build.mjs, run by build.rs) and tests them. The Rust tests cover the server.", "scripts": { diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index a15591b..4dc2667 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1444,6 +1444,8 @@ test('/api/status gives a dashboard the counts, with the shared token as a cooki const s = await r.json(); for (const k of ['feeds', 'pending', 'downloaded']) expect(typeof s[k], k).toBe('number'); expect(s.version).toMatch(/^\d+\.\d+\.\d+/); + // The logo names ipx's version and the page's, each its own (#207). + await expect(page.locator('#applogo')).toHaveAttribute('title', new RegExp(`^iPX ${s.version}, page \\d+\\.\\d+\\.\\d+`)); const none = await page.request.get('/api/status', { headers: { cookie: '' } }); expect(none.status()).toBe(401); }); diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index 7aec20a..bd30407 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -4,6 +4,8 @@ The page, and the container that serves it (Caddy). ipx's changelog is [crates/ipx/CHANGELOG.md](../crates/ipx/CHANGELOG.md), which was the page's too until 0.12.0, and the directory's [crates/directory/CHANGELOG.md](../crates/directory/CHANGELOG.md). +Its version is in `package.json`, and a release is tagged `web-vX.Y.Z`. + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). @@ -11,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- The page has a version of its own, which the logo's tooltip shows beside ipx's (#207). - The page is served by its own container, ipx-web (Caddy), which passes the API to ipx on the same address, so a change to the page deploys without rebuilding ipx (#203). - Admin, Directory sets the directory's address and this server's token, and whether to share, and says how the last share went (#205). - A feed's settings have a Private switch, which keeps it out of the directory; on and locked for a feed whose address or login shows it is private (#205). diff --git a/web/build.mjs b/web/build.mjs index 999008b..256d32f 100644 --- a/web/build.mjs +++ b/web/build.mjs @@ -17,6 +17,8 @@ import swc from '@swc/core'; import html from '@swc/html'; const here = path.dirname(fileURLToPath(import.meta.url)); +// The page's own version (package.json), given to its script in place of __WEB_VERSION__. +const WEB_VERSION = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json'), 'utf8')).version; // The files are one script, concatenated in this order, not modules: they share one top-level // scope, as the single inline script did, and code that runs at load needs what came before it. @@ -57,7 +59,8 @@ export function buildStyle({ minify = true } = {}) { export function buildPage(name, { minify = true } = {}) { const { script, src: files } = PAGES[name]; const src = files.map(f => fs.readFileSync(path.join(here, 'src', f + '.ts'), 'utf8')).join('\n') - .replaceAll('__HLS_JS__', HLS_URL); + .replaceAll('__HLS_JS__', HLS_URL) + .replaceAll('__WEB_VERSION__', WEB_VERSION); const js = swc.transformSync(src, { filename: name + '.ts', jsc: { diff --git a/web/src/dialogs.ts b/web/src/dialogs.ts index a8bf73d..31c5d41 100644 --- a/web/src/dialogs.ts +++ b/web/src/dialogs.ts @@ -719,7 +719,8 @@ $('#signout').onclick=async()=>{ await api('/api/logout',{method:'POST'}); locat api('/api/me').then(u=>{ S.me=u; $('#who').textContent=u.name+(u.admin?' ยท admin':''); - $('#applogo').title=`iPX ${u.version}`; + // ipx's version and the page's, each released on its own. + $('#applogo').title=`iPX ${u.version}, page __WEB_VERSION__`; // Whether the link to Admin shows, now and before the first frame of the next visit. document.documentElement.toggleAttribute('data-admin',!!u.admin); try{ localStorage.setItem('ipx.admin',u.admin?'1':'0'); }catch{}