feat : versionnage semver, migrations contenu, bandeau mise à jour admin

- CHANGELOG.md : structure semver (1.0.0 / 1.1.0 / 1.2.0) remplace le journal non versionné
- public/version.txt : généré à chaque push depuis la première entrée CHANGELOG
- scripts/push.sh : extrait la version CHANGELOG avant git add
- src/UpdateChecker.php : compare version déployée vs version Gitea (raw file), cache 1 h
- templates/layout.php : bandeau alerte admin (nouvelle version / migrations en attente)
- templates/admin.php : dashboard moteur Folio (version déployée / disponible)
- scripts/migrate_content.php + migration_001 : ajout # titre dans les articles existants
- templates/maintenance.php : page HTTP 503 pendant une migration
- src/helpers.php : extractMarkdownTitle(), normalisation \r\n dans lineDiff()
- templates/wizard/step1.php : suppression champ titre, plan TOC dynamique
- public/assets/js/wizard.js : scope titleEl, scrollToCursor, buildToc, handlers externalisés

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 22:45:35 +02:00
parent c503f1dd66
commit 1dbe6d8dd3
13 changed files with 565 additions and 219 deletions
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
// Runner de migrations de contenu (fichiers articles).
// Analogie avec database/migrate.php, mais pour les fichiers data/.
//
// Usage : php scripts/migrate_content.php [/chemin/vers/data]
//
// - Lit data/.content_migrations.json pour savoir ce qui a déjà tourné.
// - Active data/.maintenance pendant l'exécution (→ page 503 aux visiteurs).
// - Applique les scripts scripts/content/migration_*.php non encore appliqués.
$baseDir = dirname(__DIR__);
$dataDir = $argv[1] ?? ($baseDir . '/data');
if (!is_dir($dataDir)) {
fwrite(STDERR, "Répertoire data introuvable : $dataDir\n");
exit(1);
}
$trackFile = $dataDir . '/.content_migrations.json';
$maintenanceFlag = $dataDir . '/.maintenance';
$applied = [];
if (file_exists($trackFile)) {
$applied = json_decode((string) file_get_contents($trackFile), true) ?? [];
}
$files = glob(__DIR__ . '/content/migration_*.php') ?: [];
sort($files);
$pending = array_values(array_filter($files, fn ($f) => !isset($applied[basename($f)])));
if (empty($pending)) {
echo " (aucune migration de contenu en attente)\n";
exit(0);
}
file_put_contents($maintenanceFlag, date('Y-m-d H:i:s'));
echo "→ Mode maintenance activé\n";
$count = 0;
$errors = 0;
foreach ($pending as $file) {
$name = basename($file);
echo "$name ... ";
try {
require $file;
$applied[$name] = date('Y-m-d H:i:s');
file_put_contents(
$trackFile,
json_encode($applied, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n"
);
echo "\n";
$count++;
} catch (Throwable $e) {
echo "" . $e->getMessage() . "\n";
$errors++;
break;
}
}
if (file_exists($maintenanceFlag)) {
unlink($maintenanceFlag);
}
echo "→ Mode maintenance désactivé\n";
echo "$count migration(s) appliquée(s)" . ($errors ? ", $errors erreur(s)" : '') . ".\n";
exit($errors > 0 ? 1 : 0);