Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/assets/badges/dotnet-10.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/assets/badges/license-mit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/assets/badges/nuget.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ title: Stratara — CQRS and Event Sourcing for .NET
<a class="btn btn-outline-secondary" href="https://github.com/yesbert/Stratara"><i class="bi bi-github"></i>&nbsp;GitHub</a>
</div>
<div class="st-badges">
<a href="https://www.nuget.org/packages?q=Stratara"><img src="https://img.shields.io/nuget/v/Stratara.Mediator?logo=nuget&label=NuGet" alt="NuGet"></a>
<a href="https://github.com/yesbert/Stratara/actions/workflows/ci.yml"><img src="https://github.com/yesbert/Stratara/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://github.com/yesbert/Stratara/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"></a>
<a href="https://dotnet.microsoft.com/"><img src="https://img.shields.io/badge/.NET-10-512BD4.svg?logo=dotnet" alt=".NET 10"></a>
<a href="https://www.nuget.org/packages?q=Stratara"><img src="assets/badges/nuget.svg" alt="Latest release on NuGet"></a>
<a href="https://github.com/yesbert/Stratara/blob/main/LICENSE"><img src="assets/badges/license-mit.svg" alt="MIT licence"></a>
<a href="https://dotnet.microsoft.com/"><img src="assets/badges/dotnet-10.svg" alt=".NET 10"></a>
</div>
</section>

Expand Down
18 changes: 8 additions & 10 deletions docs/legal/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ necessary storage; the legal basis is our legitimate interest in providing a fun

## Cookies

This site sets no cookies. It embeds no advertising networks, and fonts are served from this site
rather than from an external font CDN. Only the localStorage values described above are stored, and
only the analytics tool described below is loaded — and that one only with your consent.
This site sets no cookies. It embeds no advertising networks. Only the localStorage values
described above are stored, and only the analytics tool described below is loaded — and that one
only with your consent.

## Web analytics (Umami)

Expand All @@ -92,14 +92,12 @@ any time with effect for the future:

<button type="button" class="btn btn-outline-secondary btn-sm" data-consent-revoke>Withdraw consent</button>

## Status badges from third parties
## No third-party requests before you agree

Some pages — the start page above all — show status badges that are loaded as images from
`img.shields.io` (Shields.io) and from `github.com` (GitHub Actions). Because your browser fetches
these images directly from those providers, your IP address and your browser's request headers
reach them when such a page loads. This happens before any consent, because the badges are page
content rather than analytics; the legal basis is our legitimate interest in showing the current
build and package status (Art. 6 (1) (f) GDPR). No cookies are set by these requests.
Apart from the analytics described above, this site loads nothing from another host. Fonts, icons,
diagrams, the search index and the status badges are all served from this site itself — the badges
are committed images, refreshed when a release is cut, rather than hot-linked from a badge service.
Until you accept the analytics notice, your browser therefore talks to nobody but this site's host.

## External links

Expand Down
43 changes: 43 additions & 0 deletions scripts/refresh-badges.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
#
# Regenerate the badge images the documentation site serves from docs/assets/badges/.
#
# The site serves them itself rather than hot-linking a badge service, so that a visitor's browser
# makes no third-party request before that visitor has agreed to anything. The cost of that is
# these files going stale, which is what `LandingBadgeTests` fails on and this script fixes.
#
# The version comes from the newest dated section of CHANGELOG.md — the same source llms.txt uses,
# and for the same reason: <VersionPrefix> names the version being worked toward, which stays
# unreleased for the whole cycle.
#
# Usage: ./scripts/refresh-badges.sh

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BADGE_DIR="${ROOT_DIR}/docs/assets/badges"

# BSD and GNU sed disagree about \+ in a basic regex, so the match is grep -E's job and sed
# only trims the brackets off what it found.
VERSION="$(grep -m1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\].*[0-9]{4}-[0-9]{2}-[0-9]{2}' \
"${ROOT_DIR}/CHANGELOG.md" | sed -e 's/^## \[//' -e 's/\].*$//')"
if [[ -z "${VERSION}" ]]; then
echo "Error: no dated release section found in CHANGELOG.md" >&2
exit 1
fi

mkdir -p "${BADGE_DIR}"

fetch() {
local name="$1" url="$2"
curl -fsS --retry 3 -o "${BADGE_DIR}/${name}.svg" "${url}"
echo " ${name}.svg"
}

echo "Refreshing badges for ${VERSION}:"
fetch nuget "https://img.shields.io/badge/NuGet-v${VERSION}-007ec6?logo=nuget"
fetch license-mit "https://img.shields.io/badge/license-MIT-blue.svg"
fetch dotnet-10 "https://img.shields.io/badge/.NET-10-512BD4.svg?logo=dotnet"

echo
echo "Done. These files are committed, not fetched at page load — that is the point."
60 changes: 60 additions & 0 deletions tests/Stratara.Documentation.Tests/LandingBadgeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Text.RegularExpressions;

namespace Stratara.Documentation.Tests;

/// <summary>
/// The documentation site serves its badges from <c>docs/assets/badges/</c> rather than from a
/// badge service, so that a visitor's browser makes no third-party request before that visitor has
/// agreed to anything. That property is only worth having if it holds for every page, and a served
/// badge only stays true if something notices when it stops being.
/// <para>
/// The release badge is checked against the newest dated section of the changelog for the same
/// reason <c>llms.txt</c> is: <c>VersionPrefix</c> names the version being worked toward, which
/// stays unreleased for the whole cycle, so comparing against it would advertise a version nobody
/// can install.
/// </para>
/// </summary>
public partial class LandingBadgeTests
{
[GeneratedRegex(@"^##\s*\[(\d+\.\d+\.\d+)\][^\n]*?\d{4}-\d{2}-\d{2}", RegexOptions.Multiline)]
private static partial Regex NewestReleasedVersionPattern();

[GeneratedRegex(@"v(\d+\.\d+\.\d+)")]
private static partial Regex BadgeVersionPattern();

// Quoted, single-quoted, bare and protocol-relative all reach another host, and so does a
// Markdown image. A guard that only knew the shape we happen to write today would pass the
// first time somebody wrote it differently, which is the one moment it exists for.
[GeneratedRegex("""<img[^>]+src\s*=\s*["']?(?:https?:)?//|!\[[^\]]*\]\(\s*(?:https?:)?//""")]
private static partial Regex ExternallyHostedImagePattern();

[Fact]
public void TheReleaseBadge_NamesTheNewestRelease()
{
var root = RepositoryRoot.Locate();
var release = NewestReleasedVersionPattern()
.Match(File.ReadAllText(Path.Combine(root, "CHANGELOG.md")));
var badge = BadgeVersionPattern()
.Match(File.ReadAllText(Path.Combine(root, "docs", "assets", "badges", "nuget.svg")));

// Without these, a changelog heading or badge format we stopped recognising would leave
// both sides empty and the comparison would agree that nothing is wrong.
Assert.True(release.Success, "No dated release section was found in CHANGELOG.md.");
Assert.True(badge.Success, "No version was found in docs/assets/badges/nuget.svg.");
Assert.Equal(release.Groups[1].Value, badge.Groups[1].Value);
}

[Fact]
public void NoDocumentationPage_LoadsAnImageFromAnotherHost()
{
var offenders = DocumentationFiles.Enumerate()
.Where(file => ExternallyHostedImagePattern().IsMatch(File.ReadAllText(file)))
.ToList();

Assert.True(
offenders.Count == 0,
$"These pages load an image from another host, which reaches that host before the "
+ $"visitor has consented to anything. Serve the image from docs/assets/ instead: "
+ $"{string.Join(", ", offenders)}");
}
}
Loading