feat: versionnement automatique basé sur git (build number + hash)

- build.rs génère version dynamique : X.Y.Z+build.N-abc1234
- Version injectée dans l'API (IMAGOD_BUILD_VERSION) et pages HTML
- Suffixe -dirty si modifications non commitées
- replace_all_versions() consomme le suffixe build metadata
- Suppression auto-changelog — reste manuel pour contrôle qualité
This commit is contained in:
2026-06-25 13:27:13 -04:00
parent e867dadb1f
commit 3254c33ae5
5 changed files with 79 additions and 13 deletions
+71 -5
View File
@@ -3,14 +3,21 @@
//! Exécuté à chaque `cargo build`. Garantit que :
//! 1. changelog.html est généré depuis CHANGELOG.md
//! 2. help.html est généré depuis ROADMAP.md
//! 3. Le badge version dans index.html suit la version Cargo.toml
//! 3. Le badge version dans index.html suit la version calculée
//! 4. La version est auto-générée : base Cargo.toml + compteur commits git
//! 5. CHANGELOG.md reçoit une entrée vide pour la nouvelle version si absente
use std::fs;
use std::path::Path;
use std::process::Command;
fn main() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let version = env!("CARGO_PKG_VERSION");
let base_version = env!("CARGO_PKG_VERSION");
// Version dynamique = version de base + numéro de build (compteur commits git)
let version = compute_version(base_version, manifest_dir);
let frontend_dir = manifest_dir.join("src/frontend");
// === changelog.html depuis CHANGELOG.md ===
@@ -23,7 +30,7 @@ fn main() {
let html = simple_md_to_html(&md);
let tmpl = fs::read_to_string(&changelog_tmpl).unwrap_or_default();
let page = tmpl
.replace("{{VERSION}}", version)
.replace("{{VERSION}}", &version)
.replace("{{CHANGELOG_CONTENT}}", &html);
fs::write(&changelog_out, &page).ok();
println!("cargo:rerun-if-changed=CHANGELOG.md");
@@ -40,7 +47,7 @@ fn main() {
let html = simple_md_to_html(&md);
let tmpl = fs::read_to_string(&help_tmpl).unwrap_or_default();
let page = tmpl
.replace("{{VERSION}}", version)
.replace("{{VERSION}}", &version)
.replace("{{HELP_CONTENT}}", &html);
fs::write(&help_out, &page).ok();
println!("cargo:rerun-if-changed=ROADMAP.md");
@@ -52,12 +59,64 @@ fn main() {
if index_path.exists() {
let mut content = fs::read_to_string(&index_path).unwrap_or_default();
// Remplacer tous les badges de version vX.Y.Z par la version courante
content = replace_all_versions(&content, version);
content = replace_all_versions(&content, &version);
fs::write(&index_path, &content).ok();
println!("cargo:rerun-if-changed=src/frontend/index.html");
}
println!("cargo:info=✅ Pages HTML générées pour imagod-web v{version}");
// Injecter la version dynamique dans le code Rust (utilisable via env!("IMAGOD_BUILD_VERSION"))
println!("cargo:rustc-env=IMAGOD_BUILD_VERSION={version}");
}
/// Calcule la version dynamique : base + compteur de commits git
fn compute_version(base: &str, manifest_dir: &Path) -> String {
// Récupérer le compteur de commits
let commit_count = Command::new("git")
.args(["rev-list", "--count", "HEAD"])
.current_dir(manifest_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "0".to_string());
// Récupérer le hash court du commit
let short_hash = Command::new("git")
.args(["rev-parse", "--short=7", "HEAD"])
.current_dir(manifest_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
// Vérifier si le working tree est dirty
let dirty = Command::new("git")
.args(["diff", "--quiet"])
.current_dir(manifest_dir)
.status()
.map(|s| !s.success())
.unwrap_or(false);
let suffix = if dirty { "-dirty" } else { "" };
format!("{}+build.{}-{}{}", base, commit_count, short_hash, suffix)
}
/// Module minimal pour la date du jour (zéro dépendance externe)
mod chrono_lite {
use std::process::Command;
pub fn today() -> String {
Command::new("date")
.args(["+%d %B %Y"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "Date inconnue".to_string())
}
}
/// Mini convertisseur Markdown → HTML (zéro dépendance)
@@ -293,6 +352,13 @@ fn replace_all_versions(html: &str, version: &str) -> String {
break;
}
}
// Consommer le suffixe build metadata (+ ou - suivi d'alphanum/dots/tirets)
while j < chars.len() && (chars[j] == '+' || chars[j] == '-') {
j += 1;
while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '.' || chars[j] == '-') {
j += 1;
}
}
if dots >= 1 {
// C'est une version — remplacer
result.push_str(&format!("v{version}"));
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog — imagod-web v0.2.0</title>
<title>Changelog — imagod-web v0.2.0+build.21-e867dad-dirty</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
@@ -37,7 +37,7 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
</head>
<body>
<header>
<h1>📋 imagod-web <span class="version-badge">v0.2.0</span></h1>
<h1>📋 imagod-web <span class="version-badge">v0.2.0+build.21-e867dad-dirty</span></h1>
<nav>
<a href="/">🖼️ Galerie</a>
<a href="/changelog" class="active">📋 Changelog</a>
@@ -107,6 +107,6 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
<h2>Légende des tags</h2>
</main>
<footer>imagod-web v0.2.0 — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
<footer>imagod-web v0.2.0+build.21-e867dad-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
</body>
</html>
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aide & Roadmap — imagod-web v0.2.0</title>
<title>Aide & Roadmap — imagod-web v0.2.0+build.21-e867dad-dirty</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
@@ -37,7 +37,7 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
</head>
<body>
<header>
<h1>❓ imagod-web <span class="version-badge">v0.2.0</span></h1>
<h1>❓ imagod-web <span class="version-badge">v0.2.0+build.21-e867dad-dirty</span></h1>
<nav>
<a href="/">🖼️ Galerie</a>
<a href="/changelog">📋 Changelog</a>
@@ -169,6 +169,6 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
<p>5. <strong>Corriger le backend Imago</strong> (thumbnails, upload, stockage)</p>
</main>
<footer>imagod-web v0.2.0 — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
<footer>imagod-web v0.2.0+build.21-e867dad-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
</body>
</html>
+1 -1
View File
@@ -465,7 +465,7 @@
<button class="btn" id="clear-btn" style="display:none">✕</button>
</div>
<button class="btn accent" id="header-upload-btn" onclick="document.getElementById('drop-zone').scrollIntoView({behavior:'smooth'}); document.getElementById('file-input').click()">📤 Upload</button>
<span class="version-badge" id="version-badge">v0.2.0</span>
<span class="version-badge" id="version-badge">v0.2.0+build.21-e867dad-dirty</span>
</div>
<!-- ─── Navigation ─── -->
+1 -1
View File
@@ -118,7 +118,7 @@ async fn health_check() -> &'static str {
/// Retourne la version de l'application et des infos utiles
async fn version() -> Json<serde_json::Value> {
Json(serde_json::json!({
"version": env!("CARGO_PKG_VERSION"),
"version": env!("IMAGOD_BUILD_VERSION"),
"name": env!("CARGO_PKG_NAME"),
"description": env!("CARGO_PKG_DESCRIPTION"),
"repository": env!("CARGO_PKG_REPOSITORY"),