From 4e4c3472507178f9c84ddc66566fbd0cb987faf4 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Fri, 21 Nov 2025 11:12:11 +0100 Subject: [PATCH] chore(package): add auto-version script --- .husky/post-commit | 32 +++++++ scripts/auto-version.ts | 185 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100755 .husky/post-commit create mode 100644 scripts/auto-version.ts diff --git a/.husky/post-commit b/.husky/post-commit new file mode 100755 index 0000000..b496eba --- /dev/null +++ b/.husky/post-commit @@ -0,0 +1,32 @@ +#!/bin/sh +# Auto-version hook: incrémente la version après certains commits + +# Récupérer le dernier message de commit +commit_msg=$(git log -1 --pretty=%B) + +# Ignorer si le commit contient [skip version] ou si c'est un commit de version +if echo "$commit_msg" | grep -qE "\[skip version\]|chore: bump version"; then + exit 0 +fi + +# Vérifier si le commit devrait déclencher une mise à jour de version +# Types pris en charge: +# - feat: → minor bump +# - fix:, perf:, security:, patch:, refactor: → patch bump +# - feat!:, refactor!:, etc. (avec !) → major bump +# - breaking change ou BREAKING CHANGE → major bump +# Ignorés: chore:, docs:, style:, test:, ci:, build: +if echo "$commit_msg" | grep -qiE "^feat:|^fix:|^perf:|^security:|^patch:|^refactor:|^[a-z]+!:|breaking change"; then + # Lancer le script en mode hook (silent + ajout auto au staging) + pnpm tsx scripts/auto-version.ts --silent --hook + + # Vérifier si package.json a changé + if ! git diff --quiet package.json; then + echo "" + echo "📦 Version mise à jour automatiquement" + echo "💡 Pour commit: git add package.json && git commit -m 'chore: bump version'" + fi +fi + +exit 0 + diff --git a/scripts/auto-version.ts b/scripts/auto-version.ts new file mode 100644 index 0000000..1997d99 --- /dev/null +++ b/scripts/auto-version.ts @@ -0,0 +1,185 @@ +import { execSync } from 'child_process'; +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +interface Version { + major: number; + minor: number; + patch: number; +} + +function parseVersion(version: string): Version { + const [major, minor, patch] = version.split('.').map(Number); + return { major, minor, patch }; +} + +function formatVersion(v: Version): string { + return `${v.major}.${v.minor}.${v.patch}`; +} + +function getLastVersionTag(): string | null { + try { + const tag = execSync('git describe --tags --abbrev=0', { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + return tag; + } catch { + return null; + } +} + +function getCommitsSinceTag(tag: string | null): string[] { + try { + const range = tag ? `${tag}..HEAD` : 'HEAD'; + const commits = execSync(`git log ${range} --pretty=format:"%s"`, { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }) + .trim() + .split('\n') + .filter(Boolean); + return commits; + } catch { + return []; + } +} + +function determineVersionBump(commits: string[]): 'major' | 'minor' | 'patch' { + let hasBreaking = false; + let hasFeature = false; + let hasPatch = false; + + for (const commit of commits) { + const lowerCommit = commit.toLowerCase(); + + // Breaking changes (major bump) + if ( + lowerCommit.includes('breaking change') || + lowerCommit.includes('breaking:') || + lowerCommit.match(/^[a-z]+!:/) || // feat!:, refactor!:, etc. + lowerCommit.includes('!') + ) { + hasBreaking = true; + } + + // Features (minor bump) + if (lowerCommit.startsWith('feat:')) { + hasFeature = true; + } + + // Patch bumps: fixes, performance improvements, security fixes, refactorings + if ( + lowerCommit.startsWith('fix:') || + lowerCommit.startsWith('perf:') || + lowerCommit.startsWith('security:') || + lowerCommit.startsWith('patch:') || + lowerCommit.startsWith('refactor:') + ) { + hasPatch = true; + } + } + + if (hasBreaking) return 'major'; + if (hasFeature) return 'minor'; + if (hasPatch) return 'patch'; + + // Par défaut, patch si on a des commits mais aucun type spécifique + return commits.length > 0 ? 'patch' : 'patch'; +} + +function incrementVersion( + current: Version, + type: 'major' | 'minor' | 'patch' +): Version { + switch (type) { + case 'major': + return { major: current.major + 1, minor: 0, patch: 0 }; + case 'minor': + return { major: current.major, minor: current.minor + 1, patch: 0 }; + case 'patch': + return { + major: current.major, + minor: current.minor, + patch: current.patch + 1, + }; + } +} + +function main() { + const silent = process.argv.includes('--silent'); + const hookMode = process.argv.includes('--hook'); + + try { + const packagePath = join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')); + const currentVersion = parseVersion(packageJson.version); + + const lastTag = getLastVersionTag(); + const commits = getCommitsSinceTag(lastTag); + + if (commits.length === 0) { + if (!silent) { + console.log('✅ Aucun nouveau commit depuis la dernière version'); + console.log(`Version actuelle: ${packageJson.version}`); + } + return; + } + + const bumpType = determineVersionBump(commits); + const newVersion = incrementVersion(currentVersion, bumpType); + const newVersionString = formatVersion(newVersion); + + // Si la version n'a pas changé, ne rien faire + if (newVersionString === packageJson.version) { + return; + } + + if (!silent) { + console.log(`📊 Analyse des commits depuis ${lastTag || 'le début'}:`); + console.log(` - ${commits.length} commit(s) trouvé(s)`); + console.log(` - Type de bump détecté: ${bumpType}`); + console.log(` - Version actuelle: ${packageJson.version}`); + console.log(` - Nouvelle version: ${newVersionString}`); + + // Afficher les commits pertinents + console.log('\n📝 Commits analysés:'); + commits.slice(0, 10).forEach((commit) => { + console.log(` - ${commit}`); + }); + if (commits.length > 10) { + console.log(` ... et ${commits.length - 10} autre(s) commit(s)`); + } + } + + // Mettre à jour package.json + packageJson.version = newVersionString; + writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n'); + + if (!silent) { + console.log(`\n✅ Version mise à jour dans package.json`); + console.log( + `\n💡 Prochaines étapes:` + + `\n 1. git add package.json` + + `\n 2. git commit -m "chore: bump version to ${newVersionString}"` + + `\n 3. git tag v${newVersionString}` + ); + } else if (hookMode) { + // En mode hook, ajouter package.json au staging + try { + execSync('git add package.json', { stdio: 'ignore' }); + } catch { + // Ignorer les erreurs en mode hook + } + } + } catch (error) { + if (!silent) { + console.error('❌ Erreur lors de la mise à jour de version:', error); + } + if (!hookMode) { + process.exit(1); + } + } +} + +main();