diff --git a/.gitignore b/.gitignore index 08124e2..83e231c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ yarn-error.log* /build /dist +# Version files (auto-generated by prebuild script) +/src/version.ts +/src/version.json + # IDE .vscode/ .idea/ @@ -36,6 +40,10 @@ yarn-error.log* /public/static/ /hint-report/ +# Temp files and custom exclude patterns ~* \#* _* + +# project specific +src/version.json diff --git a/docs/README.dev-notes.md b/docs/README.dev-notes.md index cbbb31b..e6ce134 100644 --- a/docs/README.dev-notes.md +++ b/docs/README.dev-notes.md @@ -8,6 +8,55 @@ - **Recharts 3.3.0** for data visualization - **Vite 5** as build tool +## Version Management + +The project uses a **hybrid versioning approach**: +- **Semver** (e.g., `0.2.1`) in `package.json` for npm ecosystem compatibility +- **Git hash** (e.g., `+a3f2b9c`) automatically appended at build time +- **Displayed version** in UI footer: `0.2.1+a3f2b9c` (or `0.2.1+a3f2b9c-dirty` if uncommitted changes) + +### Version Scripts + +```sh +# Bump version manually (updates package.json) +npm run version:patch # 0.2.1 → 0.2.2 (bug fixes) +npm run version:minor # 0.2.1 → 0.3.0 (new features) +npm run version:major # 0.2.1 → 1.0.0 (breaking changes) + +# Generate version.json with git info (runs automatically on build) +npm run prebuild + +# Build for production (automatically generates version) +npm run build +``` + +### How It Works + +1. **Manual semver bump**: Run `npm run version:patch|minor|major` when you want to increment the version +2. **Automatic git info**: On build, `scripts/generate-version.js` creates `src/version.json` with: + - `version`: Semver + git hash (e.g., `0.2.1+a3f2b9c`) + - `semver`: Just the semver (e.g., `0.2.1`) + - `commit`: Git commit hash (e.g., `a3f2b9c`) + - `branch`: Current git branch + - `buildDate`: ISO timestamp + - `gitDate`: Commit date +3. **Fallback handling**: If `version.json` is missing (fresh clone before first build), `src/constants/defaults.ts` generates a fallback version from `package.json` + +### Version Display + +The version is displayed in the UI footer (bottom right) next to the GitHub repository link. The format is: +- `0.2.1+a3f2b9c` (clean working directory) +- `0.2.1+a3f2b9c-dirty` (uncommitted changes present) +- `0.2.1-dev` (fallback when version.json is missing) + +### Files + +- `scripts/generate-version.js` - Generates version.json from git + package.json +- `scripts/bump-version.js` - Manually bumps semver in package.json +- `src/version.json` - Auto-generated, ignored by git +- `src/version.json.template` - Fallback template (committed to repo) +- `src/constants/defaults.ts` - Exports `APP_VERSION` and `BUILD_INFO` + ## Initial Setup (Fresh Clone) ```sh diff --git a/package.json b/package.json index d55da39..f2d059f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "med-plan-assistant", - "version": "0.2.1", + "version": "0.2.2", "private": true, "dependencies": { "@radix-ui/react-label": "^2.1.8", @@ -25,6 +25,11 @@ }, "scripts": { "start": "vite", + "prebuild": "node scripts/generate-version.js", + "version:bump": "node scripts/bump-version.js", + "version:patch": "node scripts/bump-version.js patch", + "version:minor": "node scripts/bump-version.js minor", + "version:major": "node scripts/bump-version.js major", "start:wsl2": "HOST=0.0.0.0 vite", "kill": "lsof -ti:3000 | xargs kill -9 2>/dev/null && echo 'Cleared port 3000' || echo 'Port 3000 was not in use'", "build": "vite build", diff --git a/scripts/bump-version.js b/scripts/bump-version.js new file mode 100644 index 0000000..1ee27f2 --- /dev/null +++ b/scripts/bump-version.js @@ -0,0 +1,45 @@ +// scripts/bump-version.js - Manual version bump script +const fs = require('fs'); +const path = require('path'); + +const args = process.argv.slice(2); +const bumpType = args[0] || 'patch'; // patch, minor, major + +if (!['patch', 'minor', 'major'].includes(bumpType)) { + console.error('Usage: npm run version:bump [patch|minor|major]'); + console.error(' patch: 0.2.1 → 0.2.2 (bug fixes)'); + console.error(' minor: 0.2.1 → 0.3.0 (new features)'); + console.error(' major: 0.2.1 → 1.0.0 (breaking changes)'); + process.exit(1); +} + +const packagePath = path.join(__dirname, '../package.json'); +const pkg = require(packagePath); + +const [major, minor, patch] = pkg.version.split('.').map(Number); + +let newVersion; +switch (bumpType) { + case 'major': + newVersion = `${major + 1}.0.0`; + break; + case 'minor': + newVersion = `${major}.${minor + 1}.0`; + break; + case 'patch': + default: + newVersion = `${major}.${minor}.${patch + 1}`; + break; +} + +const oldVersion = pkg.version; +pkg.version = newVersion; + +fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + '\n'); + +console.log(`✓ Bumped version: ${oldVersion} → ${newVersion}`); +console.log(`\nNext steps:`); +console.log(` 1. Review the change: git diff package.json`); +console.log(` 2. Commit: git add package.json && git commit -m "Bump version to ${newVersion}"`); +console.log(` 3. (Optional) Tag: git tag v${newVersion}`); +console.log(` 4. Build to see full version: npm run build`); diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 0000000..cb8e052 --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,63 @@ +// scripts/generate-version.js - Generate version info from git +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const pkg = require('../package.json'); + +try { + const gitHash = execSync('git rev-parse --short HEAD').toString().trim(); + const gitBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); + const gitDate = execSync('git log -1 --format=%cd --date=format:%Y-%m-%d').toString().trim(); + const isDirty = execSync('git status --porcelain').toString().trim() !== ''; + + const version = { + version: `${pkg.version}+${gitHash}${isDirty ? '-dirty' : ''}`, + semver: pkg.version, + commit: gitHash, + branch: gitBranch, + buildDate: new Date().toISOString(), + gitDate: gitDate, + }; + + fs.writeFileSync( + path.join(__dirname, '../src/version.json'), + JSON.stringify(version, null, 2) + ); + + // Also generate version.ts for better Vite/TypeScript compatibility + const versionTs = `// Auto-generated by scripts/generate-version.js +export const VERSION_INFO = ${JSON.stringify(version, null, 2)} as const; +`; + fs.writeFileSync( + path.join(__dirname, '../src/version.ts'), + versionTs + ); + + console.log(`✓ Generated version: ${version.version}`); + console.log(` Semver: ${version.semver}, Commit: ${version.commit}, Branch: ${version.branch}`); +} catch (error) { + console.warn('⚠ Could not generate git version, using package.json fallback'); + const version = { + version: pkg.version, + semver: pkg.version, + commit: 'unknown', + branch: 'unknown', + buildDate: new Date().toISOString(), + gitDate: 'unknown', + }; + + fs.writeFileSync( + path.join(__dirname, '../src/version.json'), + JSON.stringify(version, null, 2) + ); + + const versionTs = `// Auto-generated by scripts/generate-version.js +export const VERSION_INFO = ${JSON.stringify(version, null, 2)} as const; +`; + fs.writeFileSync( + path.join(__dirname, '../src/version.ts'), + versionTs + ); + + console.log(`✓ Fallback version: ${version.version}`); +} diff --git a/src/App.tsx b/src/App.tsx index 3293d3b..74b93be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,7 @@ import Settings from './components/settings'; import LanguageSelector from './components/language-selector'; import DisclaimerModal from './components/disclaimer-modal'; import { Button } from './components/ui/button'; -import { PROJECT_REPOSITORY_URL } from './constants/defaults'; +import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults'; // Custom Hooks import { useAppState } from './hooks/useAppState'; @@ -220,15 +220,20 @@ const MedPlanAssistant = () => { > {t('disclaimerModalFooterLink')} - - - +
+ + v{APP_VERSION} + + + + +
diff --git a/src/constants/defaults.ts b/src/constants/defaults.ts index 60c58d9..599b411 100644 --- a/src/constants/defaults.ts +++ b/src/constants/defaults.ts @@ -8,8 +8,28 @@ * @license MIT */ +import packageJson from '../../package.json'; +// Direct import of version.json - Vite handles this natively with import assertions +// This file is generated by prebuild script with git info +// @ts-ignore +import versionJsonDefault from '../version.json' assert { type: 'json' }; + +// Use the imported version.json, or fall back to -dev version +const versionInfo = versionJsonDefault && Object.keys(versionJsonDefault).length > 0 && versionJsonDefault.version && !versionJsonDefault.version.includes('unknown') + ? versionJsonDefault + : { + version: `${packageJson.version}-dev`, + semver: packageJson.version, + commit: 'unknown', + branch: 'unknown', + buildDate: new Date().toISOString(), + gitDate: 'unknown', + }; + export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v7'; export const PROJECT_REPOSITORY_URL = 'https://git.11001001.org/cbaoth/med-plan-assistant'; +export const APP_VERSION = versionInfo.version; +export const BUILD_INFO = versionInfo; // Pharmacokinetic Constants (from research literature) // MW ratio: 135.21 (d-amphetamine) / 455.60 (LDX dimesylate) = 0.29677 diff --git a/src/version.json.template b/src/version.json.template new file mode 100644 index 0000000..e89ab3b --- /dev/null +++ b/src/version.json.template @@ -0,0 +1,8 @@ +{ + "version": "0.0.0-dev", + "semver": "0.0.0", + "commit": "unknown", + "branch": "unknown", + "buildDate": "1970-01-01T00:00:00.000Z", + "gitDate": "unknown" +}