From 1fe51fd610cc84695b0e30e64704c48303862d51 Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 25 Jul 2026 02:14:55 +0200 Subject: [PATCH 1/5] feat: add pack-dry-run scripts for validating .nupkg packages and ensuring README presence --- scripts/pack-dry-run.ps1 | 65 ++++++++++++++++++++++++++++++++++++++++ scripts/pack-dry-run.sh | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 scripts/pack-dry-run.ps1 create mode 100644 scripts/pack-dry-run.sh diff --git a/scripts/pack-dry-run.ps1 b/scripts/pack-dry-run.ps1 new file mode 100644 index 0000000..12859df --- /dev/null +++ b/scripts/pack-dry-run.ps1 @@ -0,0 +1,65 @@ +# Packs every IsPackable=true project into ./nupkgs-dryrun/ and validates each +# .nupkg — verifies README embed, checks size, prints package ID + version. +# Run from repo root. +# +# ./scripts/pack-dry-run.ps1 + +$ErrorActionPreference = "Stop" +$repoRoot = Split-Path -Parent $PSScriptRoot +Set-Location $repoRoot + +$outDir = Join-Path $repoRoot "nupkgs-dryrun" +if (Test-Path $outDir) { Remove-Item $outDir -Recurse -Force } +New-Item -ItemType Directory -Path $outDir | Out-Null + +Write-Host "" +Write-Host "-> dotnet pack shelldocs.slnx -c Release -o $outDir" +Write-Host "" +dotnet pack shelldocs.slnx --configuration Release --output $outDir +if ($LASTEXITCODE -ne 0) { + Write-Host "PACK FAILED" -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "Produced packages:" +Write-Host "" + +$fail = 0 +foreach ($nupkg in Get-ChildItem $outDir -Filter *.nupkg | Sort-Object Name) { + $sizeKB = [math]::Round($nupkg.Length / 1KB, 1) + Write-Host (" {0} ({1} KB)" -f $nupkg.Name, $sizeKB) + + # A .nupkg is a zip — extract to a temp dir to inspect. + $tmp = Join-Path ([IO.Path]::GetTempPath()) ("nupkg-check-" + [guid]::NewGuid().ToString("N").Substring(0, 8)) + Expand-Archive -Path $nupkg.FullName -DestinationPath $tmp -Force + + # Every packable project ships README.md via . + $readme = Get-ChildItem $tmp -Filter README.md -Recurse | Select-Object -First 1 + if (-not $readme) { + Write-Host " MISSING README.md" -ForegroundColor Red + $fail++ + } + + # Sanity: nuspec present with expected version. + $nuspec = Get-ChildItem $tmp -Filter *.nuspec | Select-Object -First 1 + if ($nuspec) { + $xml = [xml](Get-Content $nuspec.FullName) + $id = $xml.package.metadata.id + $ver = $xml.package.metadata.version + Write-Host " id=$id version=$ver" + } + + Remove-Item $tmp -Recurse -Force +} + +Write-Host "" +if ($fail -gt 0) { + Write-Host "$fail package(s) failed validation" -ForegroundColor Red + exit 1 +} +Write-Host "OK — all packages passed validation" -ForegroundColor Green +Write-Host "" +Write-Host "Ship it with:" +Write-Host " git tag v" +Write-Host " git push origin v" diff --git a/scripts/pack-dry-run.sh b/scripts/pack-dry-run.sh new file mode 100644 index 0000000..a4fc2df --- /dev/null +++ b/scripts/pack-dry-run.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Packs every IsPackable=true project into ./nupkgs-dryrun/ and validates each +# .nupkg — verifies README embed, checks size, prints package ID + version. +# Run from repo root. +# +# ./scripts/pack-dry-run.sh + +set -euo pipefail + +repo_root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$repo_root" + +out_dir="$repo_root/nupkgs-dryrun" +rm -rf "$out_dir" +mkdir -p "$out_dir" + +echo +echo "-> dotnet pack shelldocs.slnx -c Release -o $out_dir" +echo +dotnet pack shelldocs.slnx --configuration Release --output "$out_dir" + +echo +echo "Produced packages:" +echo + +fail=0 +for nupkg in "$out_dir"/*.nupkg; do + [ -e "$nupkg" ] || continue + size_kb=$(( $(stat -c%s "$nupkg" 2>/dev/null || stat -f%z "$nupkg") / 1024 )) + echo " $(basename "$nupkg") (${size_kb} KB)" + + tmp="$(mktemp -d)" + unzip -q "$nupkg" -d "$tmp" + + if ! find "$tmp" -name README.md -print -quit | grep -q .; then + echo " MISSING README.md" >&2 + fail=$((fail + 1)) + fi + + nuspec="$(find "$tmp" -name '*.nuspec' | head -n 1)" + if [ -n "$nuspec" ]; then + id="$(sed -n 's:.*\([^<]*\).*:\1:p' "$nuspec" | head -n 1)" + ver="$(sed -n 's:.*\([^<]*\).*:\1:p' "$nuspec" | head -n 1)" + echo " id=$id version=$ver" + fi + + rm -rf "$tmp" +done + +echo +if [ "$fail" -gt 0 ]; then + echo "$fail package(s) failed validation" >&2 + exit 1 +fi +echo "OK — all packages passed validation" +echo +echo "Ship it with:" +echo " git tag v" +echo " git push origin v" From 02f3eb4f4653f6e6c02f3113d9e84db465f027bd Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 25 Jul 2026 02:15:20 +0200 Subject: [PATCH 2/5] docs: update README to reflect first public release status and link to changelog and publishing steps --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f55a6b..4502089 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **The docs framework for .NET.** Beautiful, animated, Cmd+K-searchable documentation sites, powered by Blazor and Tailwind. Compose with ShellUI (or any Blazor component library) — like fumadocs composes with shadcn/ui. -> Status: **`0.1.0-alpha` in progress.** Not yet published to NuGet. See [ROADMAP](docs/ROADMAP.md). +> Status: **`0.1.0-alpha`** — first public release. See [CHANGELOG](CHANGELOG.md) and [ROADMAP](docs/ROADMAP.md). Publish steps live in [docs/RELEASING.md](docs/RELEASING.md). ## Why ShellDocs From 5c8d21a83b59de3d24e2aeff433a4f79446cac56 Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 25 Jul 2026 02:15:47 +0200 Subject: [PATCH 3/5] docs: add releasing guide for NuGet packages, detailing setup and steady-state release process --- docs/RELEASING.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/RELEASING.md diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..b1498cc --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,105 @@ +# Releasing ShellDocs + +Runbook for cutting a NuGet release. First time = read start to finish; steady state = jump to "Steady-state release" at the bottom. + +## One-time setup (before the very first release) + +We use **NuGet Trusted Publishing** — the workflow requests a short-lived (1-hour) API key from nuget.org via OIDC on each run. No long-lived API key stored as a secret. Official docs: . + +### 1. Verify package IDs are available on NuGet + +Run once, before you register anything, so you don't discover a naming collision at t=publish: + +```powershell +foreach ($id in "ShellDocs.CLI","ShellDocs.Components","ShellDocs.Core","ShellDocs.Markdown","ShellDocs.Templates","ShellDocs.Tokens") { + Write-Host "-- $id" + dotnet nuget search $id --exact-match --source https://api.nuget.org/v3/index.json | Select-String $id +} +``` + +If any ID is taken by another author, decide: rename (`ShellUI.ShellDocs.*`?) or reach out to the owner. Do NOT publish under a different-looking name and hope no one notices — that's how brand-confusion issues start. + +### 2. Create the `release` GitHub environment + +Repo → Settings → Environments → **New environment** → name it exactly `release`. + +Nothing to configure inside (optional: add required reviewers if you want a manual gate on each publish). The environment's existence is what the workflow's `environment: release` line references, and matching against the TP policy in step 3 is what proves this workflow is what it says it is. + +### 3. Register a Trusted Publishing policy on NuGet + +1. Sign in at [nuget.org](https://www.nuget.org/) → click your username → **Trusted Publishing** → **Add** +2. Choose the owner (individual user OR organization — the policy applies to all packages owned by that account) +3. Fill (all values are case-insensitive): + - **Repository Owner:** `shellui-dev` (the GitHub organization/user name) + - **Repository:** `shelldocs` + - **Workflow File:** `release.yml` — **filename only**, no `.github/workflows/` prefix + - **Environment:** `release` — must match `environment: release` in our workflow. If you skip this, remove `environment: release` from the workflow too, or the policy match will fail. +4. Save. + +**Note on private repos:** first-time policies for private GitHub repos are provisional for 7 days. NuGet needs to see one successful publish (which carries GitHub's repository + owner IDs in the OIDC token) to lock the policy permanently. If no publish happens in 7 days, the policy goes inactive — you'd re-activate it and try again. + +### 4. Add the `NUGET_USER` secret + +The workflow's `NuGet/login@v1` action needs your **nuget.org profile username** (NOT email, NOT the GitHub org name — the visible profile name you sign in with, e.g. what shows on `nuget.org/profiles/`). + +Repo → Settings → Secrets and variables → Actions → New repository secret: +- **Name:** `NUGET_USER` +- **Value:** your nuget.org profile name + +### 5. Local pack dry-run + +Confirm the pack works locally before trusting CI. From repo root: + +```powershell +./scripts/pack-dry-run.ps1 +``` + +The script packs every `IsPackable=true` project into `./nupkgs-dryrun/`, prints IDs + sizes, and verifies `README.md` is embedded in each. Any missing README or unexpected package = fix before releasing. + +### 6. First release — expect the 7-day provisional window + +The very first `git push origin v0.1.0-alpha` triggers the workflow, which does OIDC exchange, publishes, and locks the policy permanently. Watch the Actions tab — if OIDC exchange fails, the most likely causes (in order) are: `NUGET_USER` secret missing or wrong, TP policy's `Workflow File` field includes a path prefix (should be just `release.yml`), or workflow's `environment: release` doesn't match the policy's Environment field. + +## Steady-state release + +Once the one-time setup is done, cutting a release is three commands. + +### 1. Bump the version + +Edit `Directory.Build.props` → `0.X.Y[-suffix]`. That propagates to every packable project via the shared props file. + +For a prerelease bump: `0.1.0-alpha` → `0.1.1-alpha` (patch) or `0.2.0-alpha` (minor). +For the first stable: strip the `-alpha` suffix → `1.0.0`. + +### 2. Update `CHANGELOG.md` + +Move the entries out of `[Unreleased]` into a new dated section (`[0.1.1-alpha] — YYYY-MM-DD`). Update the comparison links at the bottom. + +### 3. Commit, tag, push + +```bash +git add Directory.Build.props CHANGELOG.md +git commit -m "chore: release 0.X.Y[-suffix]" +git tag "v0.X.Y[-suffix]" +git push +git push origin "v0.X.Y[-suffix]" +``` + +The tag push triggers `.github/workflows/release.yml`: +1. Builds Release +2. Runs the test suite +3. Packs every `IsPackable=true` project +4. Pushes each `.nupkg` to nuget.org (`--skip-duplicate` so re-runs are safe) +5. Creates a GitHub Release from the tag with auto-generated notes + +Watch the run under Actions. If NuGet push fails on one package (e.g. `409 Conflict — already exists`), `--skip-duplicate` handles it silently; a real failure (bad API key, network) will surface as a red X. + +## Dry-run without publishing + +To validate the whole workflow without shipping to NuGet, go to Actions → Release → Run workflow → check "Pack and validate only". Runs build + pack, skips the push step. + +## After the release + +- Verify the packages appear at `https://www.nuget.org/packages/ShellDocs.CLI/`, etc. (indexing takes a few minutes) +- Test the install locally: `dotnet tool install -g ShellDocs.CLI --prerelease` in a scratch directory +- Announce as appropriate (blog post / X / whatever). Alpha releases are usually announced only internally From 8aa5a234ff8206051db8730c6dc56d59df9123e1 Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 25 Jul 2026 02:16:11 +0200 Subject: [PATCH 4/5] chore: add CHANGELOG.md for version 0.1.0-alpha, detailing features, packages, and known limitations --- CHANGELOG.md | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..140ef1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,99 @@ +# Changelog + +All notable changes to ShellDocs land here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning is [SemVer](https://semver.org/spec/v2.0.0.html) with prerelease suffixes (`-alpha`, `-beta`, `-rc`) — the alpha window explicitly reserves the right to break APIs on minor bumps. + +## [Unreleased] + +## [0.1.0-alpha] — 2026-07-25 + +First public release. The whole Phase 1 target is shipped, plus most of Phase 2's primitives + consumer DX polish. See [ROADMAP.md](docs/ROADMAP.md). + +### Packages + +Published to NuGet: + +- `ShellDocs.CLI` — global tool: `dotnet tool install -g ShellDocs.CLI --prerelease`. Commands: `init`, `add`, `dev`, `build`, `preview` +- `ShellDocs.Components` — RCL with ``, ``, ``, ``, ``, ``, ``, content primitives, API-reference primitives +- `ShellDocs.Core` — navigation graph, search index model, routing helpers, markdown plain-text extractor +- `ShellDocs.Markdown` — Markdig pipeline with frontmatter, `razor:preview` fenced blocks, inline Razor component tags +- `ShellDocs.Templates` — starter markdown + Program.cs snippets for `shelldocs init` scaffolding +- `ShellDocs.Tokens` — RCL with `tokens.css` — shadcn-compatible palette + spacing scale, single source of truth for `--background`, `--foreground`, `--primary`, `--radius`, dark mode + +### Added + +**Markdown pipeline (`ShellDocs.Markdown`)** +- YAML frontmatter parsing via YamlDotNet +- ` ```razor:preview ` fenced blocks — live-rendered previews with source-view toggle +- Inline Razor component tags mid-markdown (``, ``) +- Component type registry (`RegisterComponent()`) with per-type tag aliases (`RegisterComponent