SHA256
Versions-Definition: --version mit Remote-Check + README-Mismatch-Warnung
- Program.cs: PrintVersion() vergleicht Assembly-Version mit README.md (Block-Quote '> **Version: X.Y.Z**') und warnt bei Mismatch. Remote-Check via 'git ls-remote origin HEAD' vergleicht lokalen mit remote Commit-Hash; Hinweis 'UPDATE AVAILABLE' bei Differenz. Copy-Installationen (kein .git) erhalten Hinweis auf ./install.sh. - README.md: Versions-Block-Quote unter dem Titel (kanonisch), Update-Check-Doku, drei synchrone Versionsquellen dokumentiert. - AGENTS.md: Versions-Definition als Definition of Done für Releases (README + csproj + Assembly müssen synchron sein).
This commit is contained in:
@@ -34,3 +34,21 @@ dotnet bin/Release/net10.0/webstatic.dll serve --port 8000 # lokale V
|
|||||||
- **Kein Commit ohne expliziten Wunsch.** Bei Release: signierte Commits.
|
- **Kein Commit ohne expliziten Wunsch.** Bei Release: signierte Commits.
|
||||||
- `dist/`, `bin/`, `obj/`, `.translation-drafts/` sind gitignored.
|
- `dist/`, `bin/`, `obj/`, `.translation-drafts/` sind gitignored.
|
||||||
- Machine-Translation liefert nur **Entwürfe** (`.translation-drafts/`), nie Auto-Überschreibung.
|
- Machine-Translation liefert nur **Entwürfe** (`.translation-drafts/`), nie Auto-Überschreibung.
|
||||||
|
|
||||||
|
## Versions-Definition (Definition of Done für Releases)
|
||||||
|
Die Versionsnummer wird an **drei Stellen synchron** gehalten — ein Release-Commit
|
||||||
|
muss alle drei aktualisieren, andernfalls warnt `webstatic --version` vor einem
|
||||||
|
Mismatch:
|
||||||
|
|
||||||
|
1. **`README.md`** — Block-Quote `> **Version: X.Y.Z**` direkt unter dem Titel
|
||||||
|
(kanonisch; per `git show origin/main:README.md` remote sichtbar)
|
||||||
|
2. **`GCBoK.WebStaticBuilder.csproj`** — `<Version>X.Y.Z</Version>` (Assembly)
|
||||||
|
3. **`webstatic --version`** — gibt Assembly-Version aus, vergleicht mit README
|
||||||
|
und remote Commit-Hash (`git ls-remote origin HEAD`)
|
||||||
|
|
||||||
|
Vor einem Release sind lokal und remote die Version feststellbar, ohne die CLI
|
||||||
|
aufzurufen:
|
||||||
|
```bash
|
||||||
|
git show HEAD:README.md | head -3 # lokal
|
||||||
|
git show origin/main:README.md | head -3 # remote
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# GCBoK.WebStaticBuilder
|
# GCBoK.WebStaticBuilder
|
||||||
|
|
||||||
|
> **Version: 1.0.0** — Diese Versionsnummer steht in `README.md` (kanonisch),
|
||||||
|
> in `GCBoK.WebStaticBuilder.csproj` (`<Version>`) und wird von `webstatic --version`
|
||||||
|
> ausgegeben. Alle drei Quellen müssen synchron gehalten werden.
|
||||||
|
|
||||||
Eigenständiger, quelloffener **Static-Site-Generator** der GitCover Commons für
|
Eigenständiger, quelloffener **Static-Site-Generator** der GitCover Commons für
|
||||||
mehrsprachige, SEO-optimierte, compliance-freundliche Websites. Ersetzt den
|
mehrsprachige, SEO-optimierte, compliance-freundliche Websites. Ersetzt den
|
||||||
früheren C#-Builderskript der GCBoK-Website durch ein generisches, für KMU
|
früheren C#-Builderskript der GCBoK-Website durch ein generisches, für KMU
|
||||||
@@ -69,8 +73,46 @@ Siehe `webstatic.example/webstatic.json` und `docs/getting-started.md`.
|
|||||||
|
|
||||||
## Version & Update
|
## Version & Update
|
||||||
|
|
||||||
|
Die Versionsnummer wird an drei Stellen synchron gehalten (Definition of Done
|
||||||
|
für jeden Release-Commit):
|
||||||
|
|
||||||
|
1. **`README.md`** — Block-Quote direkt unter dem Titel (kanonisch, per Remote
|
||||||
|
via `git show origin/main:README.md` sichtbar)
|
||||||
|
2. **`GCBoK.WebStaticBuilder.csproj`** — `<Version>1.0.0</Version>` (Assembly)
|
||||||
|
3. **`webstatic --version`** — gibt die Assembly-Version aus
|
||||||
|
|
||||||
|
So kann vor einem Update sowohl lokal (im Installations-Repo) als auch remote
|
||||||
|
(im Upstream-Repo) die Version festgestellt werden, ohne die CLI aufzurufen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lokal installierte Version (Clone-Installation)
|
||||||
|
git -C /opt/GitCover/webstatic show HEAD:README.md | head -3
|
||||||
|
|
||||||
|
# Remote verfügbare Version (vor Pull)
|
||||||
|
git -C /opt/GitCover/webstatic show origin/main:README.md | head -3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update-Check via CLI
|
||||||
|
|
||||||
|
`webstatic --version` führt automatisch einen Remote-Check durch
|
||||||
|
(`git ls-remote origin HEAD`) und vergleicht lokalen mit remote Commit-Hash:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
webstatic --version
|
||||||
|
# GCBoK.WebStaticBuilder 1.0.0.0
|
||||||
|
# commit: 6ca8167 (local)
|
||||||
|
# remote: ff634a3
|
||||||
|
#
|
||||||
|
# UPDATE AVAILABLE
|
||||||
|
# Run: webstatic update
|
||||||
|
```
|
||||||
|
|
||||||
|
Bei Copy-Installationen (kein `.git`) entfällt der Remote-Check; das Tool weist
|
||||||
|
auf `./install.sh` als Update-Weg hin.
|
||||||
|
|
||||||
|
### Update durchführen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
webstatic --version # Assembly-Version + Git-Commit des Installations-Repos
|
|
||||||
webstatic update # Selbstaktualisierung (Clone-Installation): lokale
|
webstatic update # Selbstaktualisierung (Clone-Installation): lokale
|
||||||
# Änderungen werden gestasht, git pull, gemerged, neu gebaut
|
# Änderungen werden gestasht, git pull, gemerged, neu gebaut
|
||||||
```
|
```
|
||||||
|
|||||||
+113
-4
@@ -24,10 +24,7 @@ if (argsList.Contains("--help") || argsList.Contains("-h") || argsList.Contains(
|
|||||||
}
|
}
|
||||||
if (argsList.Contains("--version"))
|
if (argsList.Contains("--version"))
|
||||||
{
|
{
|
||||||
var v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
PrintVersion(verbose);
|
||||||
Console.WriteLine($"GCBoK.WebStaticBuilder {v?.ToString() ?? "0.0.0"}");
|
|
||||||
var hash = GitShortHash(AppContext.BaseDirectory);
|
|
||||||
if (hash != null) Console.WriteLine($"commit: {hash}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,3 +408,115 @@ static void ShowStatus(string rootDir)
|
|||||||
var markdown = File.ReadAllText(readme);
|
var markdown = File.ReadAllText(readme);
|
||||||
MarkdownConsoleRenderer.Render(markdown, Console.Out);
|
MarkdownConsoleRenderer.Render(markdown, Console.Out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PrintVersion(bool verbose)
|
||||||
|
{
|
||||||
|
var v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
var asmVersion = v?.ToString() ?? "0.0.0";
|
||||||
|
Console.WriteLine($"GCBoK.WebStaticBuilder {asmVersion}");
|
||||||
|
|
||||||
|
// README.md version check — canonical source is the block-quote
|
||||||
|
// "> **Version: X.Y.Z**" directly under the title.
|
||||||
|
var readmeVersion = ExtractReadmeVersion(AppContext.BaseDirectory);
|
||||||
|
if (readmeVersion != null)
|
||||||
|
{
|
||||||
|
// Assembly versions are 4-part (1.0.0.0); README uses 3-part (1.0.0).
|
||||||
|
// Compare the first 3 components to avoid trailing-zero noise.
|
||||||
|
var asmShort = v?.ToString(3) ?? asmVersion;
|
||||||
|
if (!string.Equals(asmShort, readmeVersion, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"WARNING: version mismatch — README.md says {readmeVersion}, assembly says {asmShort}");
|
||||||
|
Console.WriteLine(" Update README.md and/or <Version> in the .csproj to keep them in sync.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var repo = FindRepoRoot(AppContext.BaseDirectory);
|
||||||
|
if (repo == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("install: copy (no git repo — version check skipped)");
|
||||||
|
Console.WriteLine("update: run ./install.sh again to update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localHash = GitShortHash(AppContext.BaseDirectory);
|
||||||
|
if (localHash != null) Console.WriteLine($"commit: {localHash} (local)");
|
||||||
|
|
||||||
|
// Remote check (best-effort, non-blocking on failure)
|
||||||
|
var remoteShort = CaptureRemoteHeadShort(repo);
|
||||||
|
if (remoteShort == null)
|
||||||
|
{
|
||||||
|
if (verbose) Console.WriteLine("(remote check failed — no network or no upstream)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"remote: {remoteShort}");
|
||||||
|
|
||||||
|
if (localHash != null && !string.Equals(localHash, remoteShort, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(" UPDATE AVAILABLE");
|
||||||
|
Console.WriteLine(" Run: webstatic update");
|
||||||
|
}
|
||||||
|
else if (localHash != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" up to date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string? ExtractReadmeVersion(string baseDir)
|
||||||
|
{
|
||||||
|
// Search upwards for README.md (it lives at the repo root, not in bin/).
|
||||||
|
var dir = new DirectoryInfo(Path.GetFullPath(baseDir));
|
||||||
|
string? readmePath = null;
|
||||||
|
while (dir != null)
|
||||||
|
{
|
||||||
|
var candidate = Path.Combine(dir.FullName, "README.md");
|
||||||
|
if (File.Exists(candidate)) { readmePath = candidate; break; }
|
||||||
|
dir = dir.Parent;
|
||||||
|
}
|
||||||
|
if (readmePath == null) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(readmePath);
|
||||||
|
// Look for "> **Version: X.Y.Z**" within the first 10 lines
|
||||||
|
foreach (var line in lines.Take(10))
|
||||||
|
{
|
||||||
|
var m = System.Text.RegularExpressions.Regex.Match(
|
||||||
|
line, @"\*\*Version:\s*(\d+\.\d+(?:\.\d+)?)\*\*",
|
||||||
|
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||||
|
if (m.Success) return m.Groups[1].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* best-effort */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string? CaptureRemoteHeadShort(string repoDir)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "git",
|
||||||
|
Arguments = "ls-remote origin HEAD",
|
||||||
|
WorkingDirectory = repoDir,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
};
|
||||||
|
using var p = Process.Start(psi);
|
||||||
|
if (p == null) return null;
|
||||||
|
var outp = p.StandardOutput.ReadToEnd().Trim();
|
||||||
|
p.WaitForExit(5000); // 5s timeout
|
||||||
|
if (p.ExitCode != 0 || string.IsNullOrEmpty(outp)) return null;
|
||||||
|
// Format: "<full-sha>\tHEAD"
|
||||||
|
var parts = outp.Split('\t', ' ');
|
||||||
|
return parts.Length > 0 && parts[0].Length >= 7 ? parts[0][..7] : null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user