From 498579f27bb146145886ab1ede0cd7dcd89c28b4 Mon Sep 17 00:00:00 2001 From: mathieu Date: Fri, 21 Aug 2026 14:50:34 +0200 Subject: [PATCH] =?UTF-8?q?Dis=20=C3=A0=20l'application=20quelle=20version?= =?UTF-8?q?=20elle=20est,=20et=20sous=20quelle=20licence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Page « À propos » (/a-propos) : manuel annoncé à venir, contact, site de l'auteur, version publiée avec sa date de build, et licence AGPL v3 avec le lien vers le dépôt — l'AGPL attend que les utilisateurs d'un service en réseau puissent en obtenir la source, ce lien n'est donc pas un ornement. L'application ne connaissait pas sa version : elle vivait dans le manifeste du paquet et dans les tags git, jamais dans le binaire. `build/publier-release.sh` pose désormais `-p:Version` et `-p:MaBibliDateBuild` ; l'API rend les deux par `GET /api/version`. ⚠️ L'horodatage est le TÉMOIN de l'injection, et rien ne le calcule côté MSBuild. Sans lui, la version lue serait le « 1.0.0 » que le SDK pose par défaut : il se lirait comme une vraie version alors qu'il ne désigne rien, et c'est exactement la valeur qu'on ira chercher pour diagnostiquer un appareil au cache dépareillé. Mieux vaut ne rien annoncer — un binaire compilé à la main se déclare « version de développement », ce qui est vrai. Autres décisions : - l'entrée du menu est DÉTACHÉE des six destinations, par un filet au-dessus sur téléphone et à gauche en rangée sur PC : « À propos » est une annexe, pas une septième destination. Ses règles vivent en feuille GLOBALE, comme tout le menu — une règle scopée n'atteint pas ce que rend un NavLink ; - le « mailto: » reste un lien même hors-ligne : il ne charge aucune page et passe la main au client de messagerie, qui sait mettre un message en attente. Le site et le dépôt, eux, basculent en boutons désactivés portant leur motif, comme les liens d'export des envies ; - la version est un sixième instantané hors-ligne : on la lit justement quand quelque chose ne va pas, et un appareil qu'on soupçonne est souvent celui qui n'a plus de réseau. L'écran dit alors que c'est la dernière version vue du serveur. Le point de rupture de 40 rem reste identique dans les deux feuilles, et /a-propos est inscrite dans la table de remontée des routes. Vérifié en exécution, l'API lancée : sans injection `{"numero":null,"publiee":false}`, avec `-p:Version=0.4.1 -p:MaBibliDateBuild=…` `{"numero":"0.4.1","publiee":true}`. Le rendu des écrans n'a PAS été vérifié en navigateur. 618 tests au vert (605 avant). Co-Authored-By: Claude Opus 5 --- MaBibli.Api/Endpoints/VersionEndpoints.cs | 38 +++++ MaBibli.Api/MaBibli.Api.csproj | 14 ++ MaBibli.Api/Program.cs | 1 + MaBibli.Client/Layout/MainLayout.razor | 13 ++ MaBibli.Client/Pages/APropos.razor | 150 ++++++++++++++++++++ MaBibli.Client/Services/CacheHorsLigne.cs | 11 ++ MaBibli.Client/Services/RemonteeRoutes.cs | 4 + MaBibli.Client/Services/ServiceLivresApi.cs | 25 ++++ MaBibli.Client/wwwroot/css/app.css | 23 +++ MaBibli.Shared/Dtos/VersionApplication.cs | 62 ++++++++ MaBibli.Tests/RemonteeRoutesTests.cs | 2 + MaBibli.Tests/VersionApplicationTests.cs | 95 +++++++++++++ README.md | 11 ++ 13 files changed, 449 insertions(+) create mode 100644 MaBibli.Api/Endpoints/VersionEndpoints.cs create mode 100644 MaBibli.Client/Pages/APropos.razor create mode 100644 MaBibli.Shared/Dtos/VersionApplication.cs create mode 100644 MaBibli.Tests/VersionApplicationTests.cs diff --git a/MaBibli.Api/Endpoints/VersionEndpoints.cs b/MaBibli.Api/Endpoints/VersionEndpoints.cs new file mode 100644 index 0000000..9296b5c --- /dev/null +++ b/MaBibli.Api/Endpoints/VersionEndpoints.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using MaBibli.Shared.Dtos; + +namespace MaBibli.Api.Endpoints; + +/// +/// Ce que l'application sait d'elle-même : sa version publiée et sa date de build. +/// +/// +/// C'est le serveur qui répond, et non le client Blazor, bien que les deux soient +/// compilés du même coup : lire ses propres attributs depuis le WebAssembly dépendrait de ce que +/// le trimmer veut bien conserver, alors qu'ici la lecture est certaine. La page « À propos » +/// affiche donc la version du binaire réellement déployé — ce qu'on veut savoir en diagnostiquant +/// un appareil. +/// +public static class VersionEndpoints +{ + // Lue une fois : les attributs d'un assembly ne changent pas en cours d'exécution. + private static readonly VersionApplication Version = Lire(typeof(VersionEndpoints).Assembly); + + public static IEndpointRouteBuilder MapVersionEndpoints(this IEndpointRouteBuilder routes) + { + routes.MapGet("/api/version", () => Results.Ok(Version)) + .WithName("VersionApplication") + .WithTags("Version") + .WithSummary("Version publiée et date de build, ou « rien » pour un binaire non publié.") + .Produces(); + + return routes; + } + + /// Extrait les deux valeurs de l'assembly, sans rien interpréter lui-même. + public static VersionApplication Lire(Assembly assembly) => + VersionApplication.Depuis( + assembly.GetCustomAttribute()?.InformationalVersion, + assembly.GetCustomAttributes() + .FirstOrDefault(a => a.Key == "MaBibliDateBuild")?.Value); +} diff --git a/MaBibli.Api/MaBibli.Api.csproj b/MaBibli.Api/MaBibli.Api.csproj index 4273be2..ff76f7f 100644 --- a/MaBibli.Api/MaBibli.Api.csproj +++ b/MaBibli.Api/MaBibli.Api.csproj @@ -16,6 +16,20 @@ + + + + + diff --git a/MaBibli.Api/Program.cs b/MaBibli.Api/Program.cs index d0acd93..6e39083 100644 --- a/MaBibli.Api/Program.cs +++ b/MaBibli.Api/Program.cs @@ -126,6 +126,7 @@ app.MapRevuesEndpoints(); app.MapBibliographieEndpoints(); app.MapIdentiteEndpoints(); app.MapCouverturesEndpoints(); +app.MapVersionEndpoints(); // ⚠️ Le fallback a son propre pipeline : il NE passe PAS par les StaticFileOptions posées // ci-dessus. Sans lui repasser les mêmes options, « / » — c'est-à-dire le start_url de la PWA, diff --git a/MaBibli.Client/Layout/MainLayout.razor b/MaBibli.Client/Layout/MainLayout.razor index a3ce45b..f7fc280 100644 --- a/MaBibli.Client/Layout/MainLayout.razor +++ b/MaBibli.Client/Layout/MainLayout.razor @@ -99,6 +99,19 @@ Revues Prêts Envies + + @* + « À propos » n'est PAS une septième destination : c'est une annexe, et elle est + visuellement détachée pour cela. Les six entrées ci-dessus sont la bibliothèque ; celle-ci + porte le manuel, le contact, la version et la licence — on y va une fois, pas tous les + jours. Mise sur le même rang, elle diluerait une navigation qu'on venait justement de + reprendre. Elle reste dans le menu plutôt qu'en pied de page : sur téléphone, un pied de + page vit sous une liste de trois cents livres. + + ⚠️ Elle reste active hors-ligne, comme les six autres : la version qu'on vient y lire est + justement ce qu'on cherche quand quelque chose ne va pas. + *@ + À propos diff --git a/MaBibli.Client/Pages/APropos.razor b/MaBibli.Client/Pages/APropos.razor new file mode 100644 index 0000000..1bd3b88 --- /dev/null +++ b/MaBibli.Client/Pages/APropos.razor @@ -0,0 +1,150 @@ +@page "/a-propos" +@inject ServiceLivresApi Api +@inject EtatReseau Reseau +@implements IDisposable + +@* + Ce que l'application dit d'elle-même : à qui écrire, où trouver de l'aide, quelle version + tourne réellement, et sous quelle licence elle est distribuée. + + ⚠️ Ce n'est pas une page d'ornement. Elle porte la version, c'est-à-dire la première chose + qu'on demande à quelqu'un dont l'application se comporte bizarrement — le projet a déjà connu + un appareil au cache dépareillé, où seule une version affichée aurait tranché. Et elle porte + le lien vers la source, que l'AGPL v3 attend qu'un service en réseau offre à ses utilisateurs. +*@ + +MaBibli — à propos + +

À propos

+ +
+

Manuel

+

+ Un manuel d'utilisation est à venir. En attendant, chaque écran porte ses + explications à l'endroit où elles servent. +

+
+ +
+

Contact

+ @* + ⚠️ Le « mailto: » reste un lien même hors-ligne, et c'est délibéré : il ne charge aucune + page, il passe la main au client de messagerie de l'appareil, lequel sait mettre un + message en attente d'envoi. Le désactiver empêcherait précisément d'écrire « ça ne marche + pas, je n'ai plus de réseau ». + *@ +

info@limonier.be

+ + @* + Le site, lui, est une vraie navigation vers l'extérieur : hors-ligne elle finirait sur la + page d'erreur du navigateur, hors de l'application. Un ne se désactivant pas, il + bascule en bouton inerte portant sa raison — même règle que les liens d'export des envies. + *@ + @if (Reseau.EnLigne) + { +

+ www.limonier.be +

+ } + else + { +

+ +

+ } +
+ +
+

Version

+ @if (_version is null) + { +

Chargement…

+ } + else if (_version.Publiee) + { +

+ @_version.Numero + — compilée @Quand(_version.DateBuild!.Value) +

+ + @if (!Reseau.EnLigne) + { +

+ Hors ligne : c'est la dernière version annoncée par le serveur, pas forcément + celle qui y tourne en ce moment. +

+ } + } + else + { + @* + ⚠️ Ne JAMAIS afficher ici le « 1.0.0 » que le SDK .NET pose par défaut : il se lirait + comme une vraie version. Mieux vaut ne rien annoncer que d'annoncer un faux numéro, + surtout sur la valeur qui sert à diagnostiquer. + *@ +

+ Version de développement : ce binaire n'a pas été produit par la chaîne de + publication, il n'a donc pas de numéro de version à annoncer. +

+ } +
+ +
+

Licence

+

+ MaBibli est distribuée sous licence GNU AGPL v3. +

+ @* + Le lien vers le dépôt n'est pas un ornement : l'AGPL demande que les utilisateurs d'un + service accessible par le réseau puissent en obtenir le code source. + *@ + @if (Reseau.EnLigne) + { +

+ Son code source est disponible : + @Depot +

+ } + else + { +

+ Son code source est disponible à l'adresse @Depot + +

+ } +
+ +@code { + /// Site de l'auteur, ouvert dans une autre fenêtre. + private const string Site = "https://www.limonier.be"; + + /// Dépôt du code — ce que l'AGPL attend qu'on rende accessible. + private const string Depot = "https://git.akbar.nohost.me/mathieu/mabibli"; + + private VersionApplication? _version; + + protected override async Task OnInitializedAsync() + { + Reseau.Change += SurChangementReseau; + _version = await Api.ObtenirVersionAsync(); + } + + /// + /// Au retour du réseau, la version se redemande : hors-ligne on affichait la dernière connue. + /// + private void SurChangementReseau() => _ = InvokeAsync(async () => + { + _version = await Api.ObtenirVersionAsync(); + StateHasChanged(); + }); + + private static string Quand(DateTimeOffset instant) + { + var local = instant.ToLocalTime(); + return $"le {local:dd/MM/yyyy} à {local:HH:mm}"; + } + + public void Dispose() => Reseau.Change -= SurChangementReseau; +} diff --git a/MaBibli.Client/Services/CacheHorsLigne.cs b/MaBibli.Client/Services/CacheHorsLigne.cs index d67d670..c8f3b96 100644 --- a/MaBibli.Client/Services/CacheHorsLigne.cs +++ b/MaBibli.Client/Services/CacheHorsLigne.cs @@ -19,6 +19,17 @@ public static class ClesCache public const string Utilisateur = "utilisateur"; + /// + /// La version publiée et sa date de build, telles que le serveur les a annoncées. + /// + /// + /// ⚠️ Elle vaut la peine d'être rangée précisément parce qu'on la lit quand quelque chose ne + /// va pas — et un appareil dont on soupçonne le cache est aussi bien celui qui n'a plus de + /// réseau. Ce qui est affiché hors-ligne est la dernière version vue du serveur, et + /// l'écran le dit : c'est un fait daté, pas une supposition. + /// + public const string Version = "version"; + /// /// La liste d'envies de l'utilisateur courant. /// diff --git a/MaBibli.Client/Services/RemonteeRoutes.cs b/MaBibli.Client/Services/RemonteeRoutes.cs index d983a71..4ebb81c 100644 --- a/MaBibli.Client/Services/RemonteeRoutes.cs +++ b/MaBibli.Client/Services/RemonteeRoutes.cs @@ -76,6 +76,10 @@ public static class RemonteeRoutes ("/auteurs", Racine), ("/prets", Racine), + + // « À propos » est une annexe du menu : elle remonte au catalogue, comme toute racine de + // branche. Elle n'a pas d'enfants, et n'en aura pas. + ("/a-propos", Racine), ("/not-found", Racine), ]; diff --git a/MaBibli.Client/Services/ServiceLivresApi.cs b/MaBibli.Client/Services/ServiceLivresApi.cs index d89d138..cb43f0e 100644 --- a/MaBibli.Client/Services/ServiceLivresApi.cs +++ b/MaBibli.Client/Services/ServiceLivresApi.cs @@ -53,6 +53,7 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat await ListerSeriesAsync(ct); await ListerRevuesAsync(ct); await ObtenirUtilisateurAsync(ct); + await ObtenirVersionAsync(ct); return reseau.EnLigne; } catch (OperationCanceledException) @@ -459,6 +460,30 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat return instantane?.Donnees ?? UtilisateurCourant.Anonyme; } + /// + /// Version publiée du serveur, avec repli sur la dernière connue de cet appareil. + /// + /// + /// Même forme que . Hors-ligne on rend l'instantané + /// plutôt que rien : c'est un fait daté (« voilà ce que le serveur annonçait »), et c'est + /// justement quand l'application se comporte mal qu'on veut ce numéro. L'écran distingue les + /// deux cas ; ici on ne fabrique aucune valeur. + /// + public async Task ObtenirVersionAsync(CancellationToken ct = default) + { + var (ok, version) = await EssayerAsync( + () => http.GetFromJsonAsync("api/version", Json, ct)); + + if (ok && version is not null) + { + await MemoriserAsync(ClesCache.Version, version); + return version; + } + + var instantane = await LireCacheAsync(ClesCache.Version); + return instantane?.Donnees ?? VersionApplication.Developpement; + } + /// /// Exécute un appel de lecture. Renvoie false — sans exception — quand le réseau /// manque, ce qui est la bascule vers le cache. Une annulation demandée par l'appelant, diff --git a/MaBibli.Client/wwwroot/css/app.css b/MaBibli.Client/wwwroot/css/app.css index 5cfcfe7..26c5c11 100644 --- a/MaBibli.Client/wwwroot/css/app.css +++ b/MaBibli.Client/wwwroot/css/app.css @@ -1486,6 +1486,19 @@ body { border-left-color: #f5d76e; } +/* + « À propos » est une annexe, pas une septième destination : un filet la sépare des six + entrées de la bibliothèque, et elle se porte en retrait. Elle reste un lien de plein droit — + même cible tactile, même état actif — parce qu'on la touche au pouce comme les autres. +*/ +.menu-lien-annexe { + margin-top: 0.35rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); + color: rgba(232, 237, 243, 0.8); + font-weight: 400; + font-size: 0.9rem; +} + /* À partir de 40rem la rangée tient sans se comprimer, et la bascule n'a plus de raison d'être. Le point de rupture est répété dans `MainLayout.razor.css` @@ -1526,6 +1539,16 @@ body { background: rgba(255, 255, 255, 0.12); border-bottom-color: #f5d76e; } + + /* En rangée, un filet au-dessus ne séparerait rien : le trait passe à GAUCHE, et la marge + supérieure disparaît sous peine de désaligner l'entrée du reste de la rangée. */ + .menu-lien-annexe { + margin-top: 0; + margin-left: 0.4rem; + padding-left: 0.9rem; + border-top: 0; + border-left: 1px solid rgba(255, 255, 255, 0.25); + } } /* --- Attente (lot L1) ------------------------------------------------- diff --git a/MaBibli.Shared/Dtos/VersionApplication.cs b/MaBibli.Shared/Dtos/VersionApplication.cs new file mode 100644 index 0000000..11b1e34 --- /dev/null +++ b/MaBibli.Shared/Dtos/VersionApplication.cs @@ -0,0 +1,62 @@ +namespace MaBibli.Shared.Dtos; + +/// +/// Version publiée de l'application, et date à laquelle son binaire a été produit. +/// +/// +/// +/// ⚠️ Mieux vaut ne rien afficher qu'un numéro faux. Sans injection par la chaîne de +/// publication, le SDK .NET pose 1.0.0 dans tout assembly : ce numéro se lirait comme une +/// vraie version alors qu'il ne désigne rien. Or c'est précisément la valeur qu'on ira lire pour +/// diagnostiquer un appareil dont le cache est dépareillé — s'y tromper coûterait le diagnostic. +/// +/// +/// D'où le témoin retenu : l'horodatage de build. Il n'existe que si +/// build/publier-release.sh (dépôt mabibli_ynh) l'a posé, et le script pose les deux +/// valeurs ensemble. Sans lui, la version lue est celle du SDK et n'est pas rendue : l'application +/// se déclare « version de développement », ce qui est vrai. +/// +/// +public record VersionApplication +{ + /// Numéro publié (« 0.4.0 »), ou null pour un binaire non publié. + public string? Numero { get; init; } + + /// Instant de production du binaire, en UTC. null hors publication. + public DateTimeOffset? DateBuild { get; init; } + + /// Vrai quand le binaire vient bien de la chaîne de publication. + public bool Publiee => Numero is { Length: > 0 } && DateBuild is not null; + + /// Un binaire compilé à la main : il n'a pas de version à annoncer. + public static readonly VersionApplication Developpement = new(); + + /// + /// Interprète les deux valeurs lues dans l'assembly. Fonction pure, pour être + /// éprouvable sans fabriquer d'assembly. + /// + /// + /// AssemblyInformationalVersion. ⚠️ Le SDK y ajoute parfois +empreinte (source + /// link, SourceRevisionId) : la partie utile est ce qui précède le +. + /// + /// Métadonnée MaBibliDateBuild, au format ISO 8601 en UTC. + public static VersionApplication Depuis(string? versionInformative, string? horodatage) + { + // Le témoin d'abord : sans horodatage, rien n'a été injecté, et la version lue est celle + // que le SDK invente. On ne la rend donc pas, même si elle est présente. + if (!DateTimeOffset.TryParse( + horodatage, + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.AdjustToUniversal | System.Globalization.DateTimeStyles.AssumeUniversal, + out var date)) + { + return Developpement; + } + + var numero = versionInformative?.Split('+')[0].Trim(); + + return numero is { Length: > 0 } + ? new VersionApplication { Numero = numero, DateBuild = date } + : Developpement; + } +} diff --git a/MaBibli.Tests/RemonteeRoutesTests.cs b/MaBibli.Tests/RemonteeRoutesTests.cs index a2536fc..015cc52 100644 --- a/MaBibli.Tests/RemonteeRoutesTests.cs +++ b/MaBibli.Tests/RemonteeRoutesTests.cs @@ -37,6 +37,8 @@ public class RemonteeRoutesTests [InlineData("/auteurs", "/")] // Le reste du menu [InlineData("/prets", "/")] + // L'annexe du menu + [InlineData("/a-propos", "/")] [InlineData("/", "/")] public void Remonte_dun_cran(string chemin, string attendu) => Assert.Equal(attendu, RemonteeRoutes.Parent(chemin)); diff --git a/MaBibli.Tests/VersionApplicationTests.cs b/MaBibli.Tests/VersionApplicationTests.cs new file mode 100644 index 0000000..52cdff0 --- /dev/null +++ b/MaBibli.Tests/VersionApplicationTests.cs @@ -0,0 +1,95 @@ +using System.Reflection; +using MaBibli.Api.Endpoints; +using MaBibli.Shared.Dtos; + +namespace MaBibli.Tests; + +/// +/// La version affichée par « À propos » est ce qu'on demandera à quelqu'un dont l'application se +/// comporte mal : elle doit être exacte ou absente, jamais approximative. +/// +public class VersionApplicationTests +{ + [Fact] + public void Version_et_horodatage_injectes_donnent_une_version_publiee() + { + var version = VersionApplication.Depuis("0.4.0", "2026-08-21T10:30:00Z"); + + Assert.True(version.Publiee); + Assert.Equal("0.4.0", version.Numero); + Assert.Equal( + new DateTimeOffset(2026, 8, 21, 10, 30, 0, TimeSpan.Zero), + version.DateBuild!.Value.ToUniversalTime()); + } + + /// + /// ⚠️ Le cas qui justifie tout le mécanisme : sans injection, le SDK .NET pose « 1.0.0 » + /// dans l'assembly. Ce numéro se lirait comme une vraie version alors qu'il ne désigne rien. + /// + [Fact] + public void Sans_horodatage_le_numero_du_SDK_nest_PAS_annonce() + { + var version = VersionApplication.Depuis("1.0.0", null); + + Assert.False(version.Publiee); + Assert.Null(version.Numero); + Assert.Null(version.DateBuild); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("pas une date")] + public void Un_horodatage_illisible_vaut_absence(string horodatage) => + Assert.False(VersionApplication.Depuis("0.4.0", horodatage).Publiee); + + /// Un horodatage sans version n'est pas une version : on n'invente pas le numéro. + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Sans_numero_rien_nest_annonce(string? numero) => + Assert.False(VersionApplication.Depuis(numero, "2026-08-21T10:30:00Z").Publiee); + + /// + /// Le SDK ajoute parfois « +empreinte » à l'InformationalVersion (SourceRevisionId) : seule + /// la partie qui précède le « + » est un numéro de version lisible par un humain. + /// + [Fact] + public void L_empreinte_de_commit_est_retiree() => + Assert.Equal( + "0.4.0", + VersionApplication.Depuis("0.4.0+9a3f21c", "2026-08-21T10:30:00Z").Numero); + + /// Une date locale injectée est ramenée en UTC, comme toutes les dates du projet. + [Fact] + public void L_horodatage_est_ramene_en_UTC() => + Assert.Equal( + new DateTimeOffset(2026, 8, 21, 8, 30, 0, TimeSpan.Zero), + VersionApplication.Depuis("0.4.0", "2026-08-21T10:30:00+02:00")!.DateBuild!.Value.ToUniversalTime()); + + /// + /// La lecture des attributs est branchée sur le bon assembly. Les tests ne sont pas compilés + /// par la chaîne de publication : elle doit donc rendre « version de développement », ce qui + /// est exactement le comportement attendu d'un binaire compilé à la main. + /// + [Fact] + public void La_lecture_de_lassembly_ne_leve_rien_et_ne_publie_rien() + { + var version = VersionEndpoints.Lire(typeof(VersionEndpoints).Assembly); + + Assert.False(version.Publiee); + } + + /// Une métadonnée bien nommée est bien lue, quel que soit l'assembly qui la porte. + [Fact] + public void La_metadonnee_MaBibliDateBuild_est_celle_qui_est_lue() + { + var assembly = typeof(VersionApplicationTests).Assembly; + + // Aucun assembly de test ne la porte : la lecture doit rendre null, pas la première + // métadonnée venue (elles sont nombreuses, posées par le SDK). + Assert.Null(assembly.GetCustomAttributes() + .FirstOrDefault(a => a.Key == "MaBibliDateBuild")); + } +} diff --git a/README.md b/README.md index 2b676cd..6642b4a 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,17 @@ archive `.tar.gz` reproductible, calcule son `sha256`, et met à jour `version`, `amd64.url` et `amd64.sha256` dans `mabibli_ynh/manifest.toml` — sauf avec `--no-manifest-update`. +Il injecte au passage, dans l'assembly, la **version** (`-p:Version`) et un +**horodatage de build** (`-p:MaBibliDateBuild`), que la page « À propos » de +l'application affiche via `GET /api/version`. + +⚠️ **L'horodatage est le témoin de l'injection** : sans lui, l'application se déclare +« version de développement » plutôt que d'afficher le `1.0.0` que le SDK .NET pose par +défaut. Ce numéro-là se lirait comme une vraie version alors qu'il ne désigne rien — or +c'est précisément la valeur qu'on va chercher pour diagnostiquer un appareil dont le +cache est dépareillé. Un `dotnet build` local n'annonce donc aucune version, et c'est +voulu. + ⚠️ YunoHost lit le manifeste **depuis Gitea**, jamais une copie locale : un manifeste corrigé mais non poussé n'existe pas pour le serveur. C'est précisément ce que `publier.sh` empêche d'oublier.