diff --git a/.githooks/bump-version.mjs b/.githooks/bump-version.mjs new file mode 100644 index 0000000..180584b --- /dev/null +++ b/.githooks/bump-version.mjs @@ -0,0 +1,54 @@ +// Pre-commit version bump. The app version lives in mykindlebox/client/ +// package.json (shown in the sidebar footer); the server package is kept on +// the same number. Every commit that touches the app bumps the patch number +// (1.4.2 → 1.4.3) and stages the change, so each commit is a distinct version. +// +// A hand-made minor/major bump (`npm version minor --no-git-tag-version` in +// client/) is kept as-is instead of being bumped again, and copied to the +// server package. +// +// Skipped entirely when nothing under mykindlebox/ is staged (docs-only +// commits) or SKIP_VERSION_BUMP=1 is set. +import { execFileSync } from 'node:child_process' +import { readFileSync, writeFileSync } from 'node:fs' + +const PACKAGES = ['mykindlebox/client', 'mykindlebox/server'] + +function git(...args) { + return execFileSync('git', args, { encoding: 'utf8' }) +} + +function versionAt(ref) { + try { + return JSON.parse(git('show', `${ref}:mykindlebox/client/package.json`)).version + } catch { + return null // no HEAD yet, or the file didn't exist + } +} + +if (process.env.SKIP_VERSION_BUMP === '1') process.exit(0) + +const staged = git('diff', '--cached', '--name-only').split('\n').filter(Boolean) +if (!staged.some((file) => file.startsWith('mykindlebox/'))) process.exit(0) + +const headVersion = versionAt('HEAD') +const stagedVersion = versionAt('') // `git show :path` reads the index +const manualBump = Boolean(headVersion && stagedVersion && headVersion !== stagedVersion) + +const [major, minor, patch] = (stagedVersion ?? '0.0.0').split('.').map(Number) +const next = manualBump ? stagedVersion : `${major}.${minor}.${patch + 1}` + +// Also brings the server (and lockfiles) along after a client-only manual bump. +for (const dir of PACKAGES) { + for (const name of ['package.json', 'package-lock.json']) { + const path = `${dir}/${name}` + const json = JSON.parse(readFileSync(path, 'utf8')) + if (json.version === next && (!json.packages?.[''] || json.packages[''].version === next)) continue + json.version = next + if (json.packages?.['']) json.packages[''].version = next + writeFileSync(path, `${JSON.stringify(json, null, 2)}\n`) + git('add', path) + } +} + +console.log(`MyKindlebox version ${manualBump ? 'set' : 'bumped'} to ${next}`) diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..c3df700 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +# Bumps the app's patch version on every commit that touches mykindlebox/. +# Enabled per clone with: git config core.hooksPath .githooks +exec node "$(git rev-parse --show-toplevel)/.githooks/bump-version.mjs" diff --git a/CLAUDE.md b/CLAUDE.md index c7540b9..97fb8d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,7 @@ Deployment: `docker compose up --build` from `mykindlebox/` serves the app on po ### Conventions - The REST API speaks **snake_case** at the boundary; Drizzle columns are camelCase. `server/utils/case.ts` converts both directions — don't hand-write conversions. +- **Versioning**: the app version is `mykindlebox/client/package.json`'s `version` (server package kept in sync), shown at the bottom of the sidebar and the mobile More menu. The checked-in pre-commit hook `.githooks/bump-version.mjs` bumps the patch number on every commit that touches `mykindlebox/` — don't bump patch versions by hand. For a minor/major release, run `npm version minor --no-git-tag-version` (or `major`) in `client/` before committing; the hook keeps it and syncs the server. Each clone must enable the hook once with `git config core.hooksPath .githooks`. - The client talks to the API over **relative URLs** (`/api`, `/uploads`). Never reintroduce a hardcoded `http://localhost:3001`; it breaks LAN/PWA access from other devices. ## What MyKindlebox is diff --git a/mykindlebox/client/package-lock.json b/mykindlebox/client/package-lock.json index 772d00c..084ff44 100644 --- a/mykindlebox/client/package-lock.json +++ b/mykindlebox/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "client", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "client", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { "@tanstack/react-query": "^5.101.0", "react": "^19.2.6", diff --git a/mykindlebox/client/package.json b/mykindlebox/client/package.json index a7d4484..c14f5a3 100644 --- a/mykindlebox/client/package.json +++ b/mykindlebox/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/mykindlebox/client/src/components/Layout.jsx b/mykindlebox/client/src/components/Layout.jsx index e1713cd..1f057b5 100644 --- a/mykindlebox/client/src/components/Layout.jsx +++ b/mykindlebox/client/src/components/Layout.jsx @@ -1,6 +1,9 @@ import { useEffect, useRef, useState } from 'react' import { NavLink, Outlet, useLocation } from 'react-router-dom' import QuickSearch from './QuickSearch' +// Vite inlines just this field; it's bumped by the pre-commit hook +// (.githooks/bump-version.mjs) on every commit that touches the app. +import { version as APP_VERSION } from '../../package.json' // Stroke icons shared by the sidebar and the mobile bottom nav. Kept inline // (no icon library) to match the other hand-rolled SVGs in the app. @@ -173,6 +176,9 @@ function MoreMenu() { {item.label} ))} +
+ MyKindlebox v{APP_VERSION} +
)} @@ -207,6 +213,7 @@ function Layout() { ))} +v{APP_VERSION}
{/* min-w-0 lets wide children (tables, the pedigree tree) scroll inside