Add hybrid versioning scripts, version shown in app footer
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -17,6 +17,10 @@ yarn-error.log*
|
|||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
# Version files (auto-generated by prebuild script)
|
||||||
|
/src/version.ts
|
||||||
|
/src/version.json
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
@@ -36,6 +40,10 @@ yarn-error.log*
|
|||||||
/public/static/
|
/public/static/
|
||||||
/hint-report/
|
/hint-report/
|
||||||
|
|
||||||
|
# Temp files and custom exclude patterns
|
||||||
~*
|
~*
|
||||||
\#*
|
\#*
|
||||||
_*
|
_*
|
||||||
|
|
||||||
|
# project specific
|
||||||
|
src/version.json
|
||||||
|
|||||||
@@ -8,6 +8,55 @@
|
|||||||
- **Recharts 3.3.0** for data visualization
|
- **Recharts 3.3.0** for data visualization
|
||||||
- **Vite 5** as build tool
|
- **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)
|
## Initial Setup (Fresh Clone)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "med-plan-assistant",
|
"name": "med-plan-assistant",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
@@ -25,6 +25,11 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"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",
|
"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'",
|
"kill": "lsof -ti:3000 | xargs kill -9 2>/dev/null && echo 'Cleared port 3000' || echo 'Port 3000 was not in use'",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
45
scripts/bump-version.js
Normal file
45
scripts/bump-version.js
Normal 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`);
|
||||||
63
scripts/generate-version.js
Normal file
63
scripts/generate-version.js
Normal 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}`);
|
||||||
|
}
|
||||||
25
src/App.tsx
25
src/App.tsx
@@ -19,7 +19,7 @@ import Settings from './components/settings';
|
|||||||
import LanguageSelector from './components/language-selector';
|
import LanguageSelector from './components/language-selector';
|
||||||
import DisclaimerModal from './components/disclaimer-modal';
|
import DisclaimerModal from './components/disclaimer-modal';
|
||||||
import { Button } from './components/ui/button';
|
import { Button } from './components/ui/button';
|
||||||
import { PROJECT_REPOSITORY_URL } from './constants/defaults';
|
import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults';
|
||||||
|
|
||||||
// Custom Hooks
|
// Custom Hooks
|
||||||
import { useAppState } from './hooks/useAppState';
|
import { useAppState } from './hooks/useAppState';
|
||||||
@@ -220,15 +220,20 @@ const MedPlanAssistant = () => {
|
|||||||
>
|
>
|
||||||
{t('disclaimerModalFooterLink')}
|
{t('disclaimerModalFooterLink')}
|
||||||
</Button>
|
</Button>
|
||||||
<a
|
<div className="flex items-center gap-3">
|
||||||
href={PROJECT_REPOSITORY_URL}
|
<span className="text-xs text-muted-foreground" title={`Version: ${APP_VERSION}${APP_VERSION.endsWith('-dirty') ? ' (uncommitted changes)' : ''}`}>
|
||||||
target="_blank"
|
v{APP_VERSION}
|
||||||
rel="noopener noreferrer"
|
</span>
|
||||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md hover:bg-accent text-foreground hover:text-accent-foreground transition-colors"
|
<a
|
||||||
title={t('footerProjectRepo')}
|
href={PROJECT_REPOSITORY_URL}
|
||||||
>
|
target="_blank"
|
||||||
<GitBranch size={18} />
|
rel="noopener noreferrer"
|
||||||
</a>
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md hover:bg-accent text-foreground hover:text-accent-foreground transition-colors"
|
||||||
|
title={t('footerProjectRepo')}
|
||||||
|
>
|
||||||
|
<GitBranch size={18} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -8,8 +8,28 @@
|
|||||||
* @license MIT
|
* @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 LOCAL_STORAGE_KEY = 'medPlanAssistantState_v7';
|
||||||
export const PROJECT_REPOSITORY_URL = 'https://git.11001001.org/cbaoth/med-plan-assistant';
|
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)
|
// Pharmacokinetic Constants (from research literature)
|
||||||
// MW ratio: 135.21 (d-amphetamine) / 455.60 (LDX dimesylate) = 0.29677
|
// MW ratio: 135.21 (d-amphetamine) / 455.60 (LDX dimesylate) = 0.29677
|
||||||
|
|||||||
8
src/version.json.template
Normal file
8
src/version.json.template
Normal 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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user