Add hybrid versioning scripts, version shown in app footer

This commit is contained in:
2026-01-16 16:55:13 +00:00
parent 966006db6a
commit 5bd9780ac0
8 changed files with 214 additions and 11 deletions

8
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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",

45
scripts/bump-version.js Normal file
View File

@@ -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`);

View File

@@ -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}`);
}

View File

@@ -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,6 +220,10 @@ const MedPlanAssistant = () => {
>
{t('disclaimerModalFooterLink')}
</Button>
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground" title={`Version: ${APP_VERSION}${APP_VERSION.endsWith('-dirty') ? ' (uncommitted changes)' : ''}`}>
v{APP_VERSION}
</span>
<a
href={PROJECT_REPOSITORY_URL}
target="_blank"
@@ -231,6 +235,7 @@ const MedPlanAssistant = () => {
</a>
</div>
</div>
</div>
</footer>
</div>
</div>

View File

@@ -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

View File

@@ -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"
}