|
| 1 | +name: Docs (GitHub Pages) |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + branches: [ main ] |
| 6 | + paths: |
| 7 | + - README.md |
| 8 | + - docs/** |
| 9 | + - github-pages/** |
| 10 | + - .github/workflows/jekyll-gh-pages.yml |
| 11 | + workflow_dispatch: |
| 12 | + |
| 13 | +permissions: |
| 14 | + contents: read |
| 15 | + pages: write |
| 16 | + id-token: write |
| 17 | + |
| 18 | +concurrency: |
| 19 | + group: pages |
| 20 | + cancel-in-progress: false |
| 21 | + |
| 22 | +jobs: |
| 23 | + build: |
| 24 | + runs-on: ubuntu-latest |
| 25 | + steps: |
| 26 | + - name: Checkout |
| 27 | + uses: actions/checkout@v5 |
| 28 | + |
| 29 | + - name: Add version and build date |
| 30 | + run: | |
| 31 | + echo "version: \"${{ github.run_number }}\"" >> github-pages/_config.yml |
| 32 | + echo "build_date: \"$(date +'%Y-%m-%d')\"" >> github-pages/_config.yml |
| 33 | +
|
| 34 | + - name: Generate site pages from docs |
| 35 | + run: | |
| 36 | + set -euo pipefail |
| 37 | +
|
| 38 | + rewrite_md_links() { |
| 39 | + # Rewrite *.md links to *.html for the rendered site (keep source docs GitHub-friendly). |
| 40 | + # Only rewrites relative links (no ':' in the target) so external URLs are untouched. |
| 41 | + sed -E \ |
| 42 | + -e 's|\]\(README\.md\)|](/)|g' \ |
| 43 | + -e 's|\]\(([^):]+)\.md(#[^)]*)?\)|](\1.html\2)|g' |
| 44 | + } |
| 45 | +
|
| 46 | + extract_description() { |
| 47 | + # Grab the first meaningful paragraph (skips headings, code fences, and bullet lists). |
| 48 | + awk ' |
| 49 | + BEGIN { in_code=0 } |
| 50 | + /^```/ { in_code = !in_code; next } |
| 51 | + in_code { next } |
| 52 | + NR == 1 { next } # skip H1 |
| 53 | + /^[[:space:]]*$/ { next } # skip blanks |
| 54 | + /^#/ { next } # skip headings |
| 55 | + /^(Accepted|Proposed|Rejected|Superseded|Deprecated)[[:space:]]/ { next } # skip ADR status line |
| 56 | + /^[[:space:]]*[-*]/ { next } # skip bullets |
| 57 | + { print; exit } |
| 58 | + ' |
| 59 | + } |
| 60 | +
|
| 61 | + keywords_for_feature() { |
| 62 | + case "$1" in |
| 63 | + storage-core.md) echo "IStorage, ManagedCode.Storage.Core, provider-agnostic storage, upload, download, streaming, metadata, Result<T>, .NET" ;; |
| 64 | + dependency-injection.md) echo "dependency injection, keyed services, IServiceCollection, IStorage, multi-tenant storage, .NET" ;; |
| 65 | + virtual-file-system.md) echo "virtual file system, IVirtualFileSystem, overlay, metadata cache, ManagedCode.Storage.VirtualFileSystem, .NET" ;; |
| 66 | + mime-and-crc.md) echo "MimeHelper, content-type, CRC32, integrity, ManagedCode.MimeTypes, ManagedCode.Storage.Core" ;; |
| 67 | + testfakes.md) echo "test fakes, provider doubles, Testcontainers, integration tests, ManagedCode.Storage.TestFakes" ;; |
| 68 | + integration-aspnet-server.md) echo "ASP.NET storage controller, streaming upload, ranged download, SignalR hub, ManagedCode.Storage.Server" ;; |
| 69 | + integration-dotnet-client.md) echo ".NET HTTP client, StorageClient, upload, download, chunked upload, CRC32, ManagedCode.Storage.Client" ;; |
| 70 | + integration-signalr-client.md) echo "SignalR client, streaming, upload, download, progress, StorageSignalRClient, ManagedCode.Storage.Client.SignalR" ;; |
| 71 | + chunked-uploads.md) echo "chunked uploads, resumable upload, CRC32, ASP.NET, ManagedCode.Storage.Server, ManagedCode.Storage.Client" ;; |
| 72 | + provider-azure-blob.md) echo "Azure Blob Storage, ManagedCode.Storage.Azure, IStorage, BlobClient, container, streaming upload, download, .NET" ;; |
| 73 | + provider-azure-datalake.md) echo "Azure Data Lake Gen2, ADLS, ManagedCode.Storage.Azure.DataLake, IStorage, filesystem, directory, .NET" ;; |
| 74 | + provider-aws-s3.md) echo "Amazon S3, AWS S3, ManagedCode.Storage.Aws, IStorage, bucket, streaming upload, Object Lock, legal hold, .NET" ;; |
| 75 | + provider-google-cloud-storage.md) echo "Google Cloud Storage, GCS, ManagedCode.Storage.Gcp, StorageClient, IStorage, bucket, streaming, .NET" ;; |
| 76 | + provider-filesystem.md) echo "file system storage, local development, ManagedCode.Storage.FileSystem, IStorage, tests, .NET" ;; |
| 77 | + provider-sftp.md) echo "SFTP storage, SSH.NET, ManagedCode.Storage.Sftp, IStorage, upload, download, .NET" ;; |
| 78 | + provider-onedrive.md) echo "OneDrive, Microsoft Graph, GraphServiceClient, Entra ID, OAuth, ManagedCode.Storage.OneDrive, IStorage, .NET" ;; |
| 79 | + provider-googledrive.md) echo "Google Drive API, DriveService, OAuth, service account, ManagedCode.Storage.GoogleDrive, IStorage, .NET" ;; |
| 80 | + provider-dropbox.md) echo "Dropbox API, DropboxClient, OAuth2, refresh token, ManagedCode.Storage.Dropbox, IStorage, .NET" ;; |
| 81 | + provider-cloudkit.md) echo "CloudKit Web Services, iCloud app data, ckAPIToken, ckWebAuthToken, ManagedCode.Storage.CloudKit, IStorage, .NET" ;; |
| 82 | + *) echo "ManagedCode.Storage, IStorage, .NET, storage" ;; |
| 83 | + esac |
| 84 | + } |
| 85 | +
|
| 86 | + keywords_for_api() { |
| 87 | + case "$1" in |
| 88 | + storage-server.md) echo "storage API, ASP.NET controllers, SignalR hub, upload, download, streaming, chunked upload, ranged download, ManagedCode.Storage.Server" ;; |
| 89 | + *) echo "ManagedCode.Storage API, HTTP, SignalR, ASP.NET, streaming" ;; |
| 90 | + esac |
| 91 | + } |
| 92 | +
|
| 93 | + keywords_for_adr() { |
| 94 | + case "$1" in |
| 95 | + 0001-icloud-drive-support.md) echo "iCloud Drive, CloudKit, Apple, server-side storage, provider design, ManagedCode.Storage.CloudKit, ADR" ;; |
| 96 | + *) echo "architecture decision record, ADR, ManagedCode.Storage" ;; |
| 97 | + esac |
| 98 | + } |
| 99 | +
|
| 100 | + mkdir -p github-pages/features |
| 101 | +
|
| 102 | + WORDS=$(wc -w < README.md) |
| 103 | + MINUTES=$(( (WORDS + 200) / 200 )) |
| 104 | +
|
| 105 | + cat > github-pages/index.md << 'EOF' |
| 106 | + --- |
| 107 | + layout: default |
| 108 | + title: Home |
| 109 | + description: ManagedCode.Storage documentation: cross-provider storage toolkit for .NET and ASP.NET streaming scenarios. |
| 110 | + keywords: ManagedCode.Storage, IStorage, .NET, ASP.NET, SignalR, Azure Blob Storage, Azure Data Lake, Amazon S3, Google Cloud Storage, OneDrive, Google Drive, Dropbox, CloudKit, SFTP, chunked uploads, streaming uploads |
| 111 | + is_home: true |
| 112 | + nav_order: 1 |
| 113 | + --- |
| 114 | + EOF |
| 115 | + sed -i 's/^ //' github-pages/index.md |
| 116 | + cat README.md >> github-pages/index.md |
| 117 | + echo "" >> github-pages/index.md |
| 118 | + echo "<p class=\"reading-time\">${MINUTES} min read</p>" >> github-pages/index.md |
| 119 | +
|
| 120 | + cat > github-pages/setup.md << 'EOF' |
| 121 | + --- |
| 122 | + layout: default |
| 123 | + title: Setup |
| 124 | + description: How to clone, build, and run tests for ManagedCode.Storage. |
| 125 | + keywords: ManagedCode.Storage setup, .NET 10, dotnet restore, dotnet build, dotnet test, Docker, Testcontainers, Azurite, LocalStack, FakeGcsServer, SFTP |
| 126 | + nav_order: 2 |
| 127 | + --- |
| 128 | + EOF |
| 129 | + sed -i 's/^ //' github-pages/setup.md |
| 130 | + rewrite_md_links < docs/Development/setup.md >> github-pages/setup.md |
| 131 | +
|
| 132 | + cat > github-pages/credentials.md << 'EOF' |
| 133 | + --- |
| 134 | + layout: default |
| 135 | + title: Credentials |
| 136 | + description: How to obtain credentials for OneDrive, Google Drive, Dropbox, and CloudKit. |
| 137 | + keywords: OneDrive credentials, Microsoft Graph auth, Entra ID, Google Drive OAuth, Drive API, service account, Dropbox OAuth2, refresh token, CloudKit ckAPIToken, ckWebAuthToken |
| 138 | + nav_order: 3 |
| 139 | + --- |
| 140 | + EOF |
| 141 | + sed -i 's/^ //' github-pages/credentials.md |
| 142 | + rewrite_md_links < docs/Development/credentials.md >> github-pages/credentials.md |
| 143 | +
|
| 144 | + cat > github-pages/testing.md << 'EOF' |
| 145 | + --- |
| 146 | + layout: default |
| 147 | + title: Testing |
| 148 | + description: Test strategy and how to run the ManagedCode.Storage test suite. |
| 149 | + keywords: ManagedCode.Storage tests, xUnit, Shouldly, integration tests, Testcontainers, Azurite, LocalStack, FakeGcsServer, HttpMessageHandler fakes |
| 150 | + nav_order: 4 |
| 151 | + --- |
| 152 | + EOF |
| 153 | + sed -i 's/^ //' github-pages/testing.md |
| 154 | + rewrite_md_links < docs/Testing/strategy.md >> github-pages/testing.md |
| 155 | +
|
| 156 | + cat > github-pages/features/index.md << 'EOF' |
| 157 | + --- |
| 158 | + layout: default |
| 159 | + title: Features |
| 160 | + description: Documentation for major modules and providers in ManagedCode.Storage. |
| 161 | + keywords: IStorage, providers, Azure Blob, AWS S3, Google Cloud Storage, OneDrive, Google Drive, Dropbox, CloudKit, FileSystem, SFTP, Virtual File System, ASP.NET Server, SignalR |
| 162 | + nav_order: 5 |
| 163 | + --- |
| 164 | + EOF |
| 165 | + sed -i 's/^ //' github-pages/features/index.md |
| 166 | + rewrite_md_links < docs/Features/index.md >> github-pages/features/index.md |
| 167 | +
|
| 168 | + for file in docs/Features/*.md; do |
| 169 | + base=$(basename "$file") |
| 170 | + if [ "$base" = "index.md" ]; then |
| 171 | + continue |
| 172 | + fi |
| 173 | +
|
| 174 | + title=$(head -n 1 "$file" | sed 's/^# //') |
| 175 | + title_escaped=$(printf '%s' "$title" | sed 's/"/\\"/g') |
| 176 | +
|
| 177 | + desc=$(extract_description < "$file" | tr -d '\r\n') |
| 178 | + desc=$(printf '%s' "$desc" | sed -E 's/[[:space:]]+/ /g; s/[[:space:]]+$//; s/:$//') |
| 179 | + if [ ${#desc} -gt 160 ]; then |
| 180 | + desc="${desc:0:159}…" |
| 181 | + fi |
| 182 | + if [ -z "${desc}" ]; then |
| 183 | + desc="${title} documentation." |
| 184 | + fi |
| 185 | + desc_escaped=$(printf '%s' "$desc" | sed 's/"/\\"/g') |
| 186 | +
|
| 187 | + keywords=$(keywords_for_feature "$base") |
| 188 | + keywords_escaped=$(printf '%s' "$keywords" | sed 's/"/\\"/g') |
| 189 | + cat > "github-pages/features/$base" << EOF |
| 190 | + --- |
| 191 | + layout: default |
| 192 | + title: "${title_escaped}" |
| 193 | + description: "${desc_escaped}" |
| 194 | + keywords: "${keywords_escaped}" |
| 195 | + --- |
| 196 | + EOF |
| 197 | + sed -i 's/^ //' "github-pages/features/$base" |
| 198 | + rewrite_md_links < "$file" >> "github-pages/features/$base" |
| 199 | + done |
| 200 | +
|
| 201 | + mkdir -p github-pages/adr |
| 202 | +
|
| 203 | + cat > github-pages/adr/index.md << 'EOF' |
| 204 | + --- |
| 205 | + layout: default |
| 206 | + title: ADR |
| 207 | + description: Architecture Decision Records (ADR) for ManagedCode.Storage. |
| 208 | + keywords: architecture decisions, ADR, ManagedCode.Storage, design decisions, providers, CloudKit, iCloud Drive |
| 209 | + nav_order: 6 |
| 210 | + --- |
| 211 | + EOF |
| 212 | + sed -i 's/^ //' github-pages/adr/index.md |
| 213 | + rewrite_md_links < docs/ADR/index.md >> github-pages/adr/index.md |
| 214 | +
|
| 215 | + for file in docs/ADR/*.md; do |
| 216 | + base=$(basename "$file") |
| 217 | + if [ "$base" = "index.md" ]; then |
| 218 | + continue |
| 219 | + fi |
| 220 | +
|
| 221 | + title=$(head -n 1 "$file" | sed 's/^# //') |
| 222 | + title_escaped=$(printf '%s' "$title" | sed 's/"/\\"/g') |
| 223 | +
|
| 224 | + desc=$(extract_description < "$file" | tr -d '\r\n') |
| 225 | + desc=$(printf '%s' "$desc" | sed -E 's/[[:space:]]+/ /g; s/[[:space:]]+$//; s/:$//') |
| 226 | + if [ ${#desc} -gt 160 ]; then |
| 227 | + desc="${desc:0:159}…" |
| 228 | + fi |
| 229 | + if [ -z "${desc}" ]; then |
| 230 | + desc="${title} decision record." |
| 231 | + fi |
| 232 | + desc_escaped=$(printf '%s' "$desc" | sed 's/"/\\"/g') |
| 233 | +
|
| 234 | + keywords=$(keywords_for_adr "$base") |
| 235 | + keywords_escaped=$(printf '%s' "$keywords" | sed 's/"/\\"/g') |
| 236 | + cat > "github-pages/adr/$base" << EOF |
| 237 | + --- |
| 238 | + layout: default |
| 239 | + title: "${title_escaped}" |
| 240 | + description: "${desc_escaped}" |
| 241 | + keywords: "${keywords_escaped}" |
| 242 | + --- |
| 243 | + EOF |
| 244 | + sed -i 's/^ //' "github-pages/adr/$base" |
| 245 | + rewrite_md_links < "$file" >> "github-pages/adr/$base" |
| 246 | + done |
| 247 | +
|
| 248 | + mkdir -p github-pages/api |
| 249 | +
|
| 250 | + cat > github-pages/api/index.md << 'EOF' |
| 251 | + --- |
| 252 | + layout: default |
| 253 | + title: API |
| 254 | + description: HTTP and SignalR API documentation for ManagedCode.Storage.Server. |
| 255 | + keywords: storage API, HTTP, SignalR, ASP.NET controllers, upload, download, streaming, chunked uploads, ranged downloads, ManagedCode.Storage.Server |
| 256 | + nav_order: 7 |
| 257 | + --- |
| 258 | + EOF |
| 259 | + sed -i 's/^ //' github-pages/api/index.md |
| 260 | + rewrite_md_links < docs/API/index.md >> github-pages/api/index.md |
| 261 | +
|
| 262 | + for file in docs/API/*.md; do |
| 263 | + base=$(basename "$file") |
| 264 | + if [ "$base" = "index.md" ]; then |
| 265 | + continue |
| 266 | + fi |
| 267 | +
|
| 268 | + title=$(head -n 1 "$file" | sed 's/^# //') |
| 269 | + title_escaped=$(printf '%s' "$title" | sed 's/"/\\"/g') |
| 270 | +
|
| 271 | + desc=$(extract_description < "$file" | tr -d '\r\n') |
| 272 | + desc=$(printf '%s' "$desc" | sed -E 's/[[:space:]]+/ /g; s/[[:space:]]+$//; s/:$//') |
| 273 | + if [ ${#desc} -gt 160 ]; then |
| 274 | + desc="${desc:0:159}…" |
| 275 | + fi |
| 276 | + if [ -z "${desc}" ]; then |
| 277 | + desc="${title} documentation." |
| 278 | + fi |
| 279 | + desc_escaped=$(printf '%s' "$desc" | sed 's/"/\\"/g') |
| 280 | +
|
| 281 | + keywords=$(keywords_for_api "$base") |
| 282 | + keywords_escaped=$(printf '%s' "$keywords" | sed 's/"/\\"/g') |
| 283 | + cat > "github-pages/api/$base" << EOF |
| 284 | + --- |
| 285 | + layout: default |
| 286 | + title: "${title_escaped}" |
| 287 | + description: "${desc_escaped}" |
| 288 | + keywords: "${keywords_escaped}" |
| 289 | + --- |
| 290 | + EOF |
| 291 | + sed -i 's/^ //' "github-pages/api/$base" |
| 292 | + rewrite_md_links < "$file" >> "github-pages/api/$base" |
| 293 | + done |
| 294 | +
|
| 295 | + cat > github-pages/templates.md << 'EOF' |
| 296 | + --- |
| 297 | + layout: default |
| 298 | + title: Templates |
| 299 | + description: Documentation templates used in this repository (Feature and ADR templates). |
| 300 | + keywords: documentation templates, feature template, ADR template, MCAF, ManagedCode.Storage docs |
| 301 | + nav_order: 8 |
| 302 | + --- |
| 303 | +
|
| 304 | + # Templates |
| 305 | +
|
| 306 | + These templates are used to keep documentation consistent and MCAF-friendly. |
| 307 | +
|
| 308 | + <div class="templates-list"> |
| 309 | + EOF |
| 310 | + sed -i 's/^ //' github-pages/templates.md |
| 311 | +
|
| 312 | + for file in docs/templates/*.md; do |
| 313 | + if [ -f "$file" ]; then |
| 314 | + filename=$(basename "$file") |
| 315 | + name="${filename%.md}" |
| 316 | +
|
| 317 | + echo "<div class=\"template-item\">" >> github-pages/templates.md |
| 318 | + echo "<span class=\"template-name\">${name}</span>" >> github-pages/templates.md |
| 319 | + echo "<div class=\"template-links\">" >> github-pages/templates.md |
| 320 | + echo "<a href=\"https://github.com/managedcode/Storage/blob/main/docs/templates/${filename}\">View</a>" >> github-pages/templates.md |
| 321 | + echo "<a href=\"https://raw.githubusercontent.com/managedcode/Storage/main/docs/templates/${filename}\" download>Download</a>" >> github-pages/templates.md |
| 322 | + echo "</div>" >> github-pages/templates.md |
| 323 | + echo "</div>" >> github-pages/templates.md |
| 324 | + fi |
| 325 | + done |
| 326 | +
|
| 327 | + echo "</div>" >> github-pages/templates.md |
| 328 | +
|
| 329 | + - name: Setup Pages |
| 330 | + uses: actions/configure-pages@v5 |
| 331 | + |
| 332 | + - name: Build with Jekyll |
| 333 | + uses: actions/jekyll-build-pages@v1 |
| 334 | + with: |
| 335 | + source: ./github-pages |
| 336 | + destination: ./_site |
| 337 | + |
| 338 | + - name: Upload artifact |
| 339 | + uses: actions/upload-pages-artifact@v3 |
| 340 | + |
| 341 | + deploy: |
| 342 | + needs: build |
| 343 | + runs-on: ubuntu-latest |
| 344 | + steps: |
| 345 | + - name: Deploy to GitHub Pages |
| 346 | + id: deployment |
| 347 | + uses: actions/deploy-pages@v4 |
0 commit comments