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