diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6257afb..015acb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,39 @@ jobs: - name: Restore dependencies run: dotnet restore ShellUI.slnx + # Generates src/ShellUI.Components/wwwroot/shellui-all.css fresh on every run. + # File is gitignored — the Windows + Linux Tailwind CLI binaries produce + # byte-different output, so drift-checking a committed file failed spuriously. + # Instead we regenerate and validate content properties. + - name: Build precompiled CSS bundle + # Invoke via `bash` (not `./script.sh`) so we don't rely on the file's + # executable bit. Windows contributors can edit and commit without + # remembering `git update-index --chmod=+x`. + run: | + set -euxo pipefail + bash scripts/rebuild-precompiled-css.sh + BUNDLE=src/ShellUI.Components/wwwroot/shellui-all.css + test -f "$BUNDLE" || (echo "ERROR: bundle was not produced"; exit 1) + + # Content assertions using grep -F (fixed-string, no regex escaping headaches). + # These catch classes of bugs we've actually shipped before — worth the paranoia. + grep -Fq -- '--background' "$BUNDLE" || (echo "ERROR: theme vars not in bundle"; exit 1) + grep -Fq '.bg-background' "$BUNDLE" || (echo "ERROR: core utility rule missing"; exit 1) + grep -Fq 'hover\:bg-accent' "$BUNDLE" || (echo "ERROR: hover: modifier rules missing"; exit 1) + grep -Fq 'state=open' "$BUNDLE" || (echo "ERROR: data-[state=open] arbitrary value not compiled"; exit 1) + grep -Fq 'px-2\.5' "$BUNDLE" || (echo "ERROR: Badge padding class px-2.5 missing (variant .cs helpers not scanned?)"; exit 1) + grep -Fq 'border-transparent' "$BUNDLE" || (echo "ERROR: border-transparent missing (variant .cs helpers not scanned?)"; exit 1) + + # Size guard — well above the ~77KB current 68 components emit, tight + # enough to catch a runaway (someone disabling minify, dumping the whole + # tailwind base without tree-shake, etc.). + size=$(wc -c < "$BUNDLE") + echo "precompiled bundle size: ${size} bytes" + if [ "$size" -gt 150000 ]; then + echo "ERROR: bundle exceeded 150KB — investigate before shipping." + exit 1 + fi + - name: Build run: dotnet build ShellUI.slnx --no-restore --configuration Release @@ -97,6 +130,13 @@ jobs: test -d "$LOCAL_FEED" || (echo "local feed not found at $LOCAL_FEED — did the pack step run?"; exit 1) ls "$LOCAL_FEED"/*.nupkg | head -3 + # DIAGNOSTIC: dump what's actually inside the fresh nupkg. The precompiled + # bundle must ship as a static web asset for consumers to `` it. + echo "---nupkg contents (looking for shellui-all.css):---" + unzip -l "$LOCAL_FEED"/ShellUI.Components*.nupkg | grep -E 'shellui-all|staticwebassets/' | head -10 || echo " (no matches — bundle not in nupkg!)" + echo "---source wwwroot check:---" + ls -la "$GITHUB_WORKSPACE/src/ShellUI.Components/wwwroot/" | head -10 + TMPDIR=$(mktemp -d) mkdir -p "$TMPDIR/app" && cd "$TMPDIR/app" dotnet new blazor -o NuGetSmoke --no-restore @@ -114,6 +154,14 @@ jobs: EOF + # Evict every cached ShellUI.Components from ~/.nuget/packages before restore. + # The runner-level cache is restored from actions/cache and may contain a + # stale copy of the current version from an earlier CI run (before we added + # the precompiled bundle, .cs scan, etc.). NuGet prefers cached over local + # feeds when version matches, so a stale cache silently masks the fresh + # nupkg. Wildcard covers any version — future-proof. + rm -rf ~/.nuget/packages/shellui.components + dotnet add package ShellUI.Components --prerelease # The safelist gets copied into wwwroot/ during the consumer's build by @@ -133,6 +181,31 @@ jobs: grep -q 'bg-background' "$SAFELIST" || (echo "safelist appears malformed — missing core Tailwind class 'bg-background'"; exit 1) wc -l "$SAFELIST" + # In .NET 10, MapStaticAssets() doesn't physically copy static web assets + # from referenced packages into the consumer's bin/ during `dotnet build` + # — it uses a manifest and reads from the extracted NuGet cache at runtime. + # So the correct assertion is "does the file exist in the extracted package + # cache after restore", which proves it'll be served at + # `_content/ShellUI.Components/shellui-all.css` in the running app. + BUNDLE="$(find ~/.nuget/packages/shellui.components -name 'shellui-all.css' -type f | head -1)" + if [ -z "$BUNDLE" ]; then + echo "ERROR: shellui-all.css not in consumer's extracted NuGet cache — the pure-NuGet install path is broken." + echo "---consumer's extracted NuGet cache tree:---" + find ~/.nuget/packages/shellui.components -type f 2>/dev/null | head -30 + exit 1 + fi + echo "found bundle at: $BUNDLE" + BUNDLE_SIZE=$(wc -c < "$BUNDLE") + echo "bundle size: $BUNDLE_SIZE bytes" + + # Spot-check a handful of representative rules using fixed-string grep + # (no regex escaping headaches). These catch classes of bugs we've hit. + grep -Fq -- '--background' "$BUNDLE" || (echo "ERROR: bundle missing --background theme var"; exit 1) + grep -Fq '.bg-background' "$BUNDLE" || (echo "ERROR: bundle missing .bg-background utility"; exit 1) + grep -Fq 'hover\:bg-accent' "$BUNDLE" || (echo "ERROR: bundle missing hover:bg-accent modifier rule"; exit 1) + grep -Fq 'state=open' "$BUNDLE" || (echo "ERROR: bundle missing data-[state=open] arbitrary-value modifier"; exit 1) + grep -Fq 'px-2\.5' "$BUNDLE" || (echo "ERROR: bundle missing Badge padding — .cs variant scan regression?"; exit 1) + - name: Upload build artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7325da4..823d79b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,19 @@ jobs: - name: Restore dependencies run: dotnet restore ShellUI.slnx + # Generate the precompiled CSS bundle before build so releases always ship + # a fresh one. File is gitignored — regenerated on every CI/release run. + - name: Build precompiled CSS bundle + run: | + set -euxo pipefail + bash scripts/rebuild-precompiled-css.sh + BUNDLE=src/ShellUI.Components/wwwroot/shellui-all.css + test -f "$BUNDLE" || (echo "ERROR: bundle was not produced"; exit 1) + # Content sanity — same assertions ci.yml runs. + grep -q -- '--background:' "$BUNDLE" || (echo "ERROR: theme vars missing"; exit 1) + grep -q '\.bg-background' "$BUNDLE" || (echo "ERROR: utilities missing"; exit 1) + echo "release bundle size: $(wc -c < "$BUNDLE") bytes" + - name: Build run: dotnet build ShellUI.slnx --no-restore --configuration Release diff --git a/.gitignore b/.gitignore index e3bf44c..db285ba 100644 --- a/.gitignore +++ b/.gitignore @@ -365,4 +365,9 @@ FodyWeavers.xsd shellui-installation-tests/ # Preview publish output (testing artifacts) -preview-publish/ \ No newline at end of file +preview-publish/ +# Generated Tailwind bundle — regenerated in CI, not committed. +# Windows and Linux Tailwind CLI produce byte-different output; committing +# would cause spurious drift-check failures. Local devs: run +# `bash scripts/rebuild-precompiled-css.sh` before running Path A tests. +src/ShellUI.Components/wwwroot/shellui-all.css diff --git a/README.md b/README.md index d24c395..ec29254 100644 --- a/README.md +++ b/README.md @@ -315,46 +315,185 @@ ShellUI ships two NuGet packages — the CLI is the primary install path; the ru ## Installation -Two paths — pick based on whether you already have Tailwind set up. +**Four install paths.** Pick one — they cover every "shape" of Blazor / static project. -### Path A — NuGet + your existing Tailwind setup (new in 0.4.x) +### 🚦 Quick decision matrix -If your project already builds Tailwind, install the runtime DLL via NuGet and add one line to your `input.css`: +| I want to… | Use | +|---|---| +| Ship the fastest — one `` tag, no build config | **Path A** — NuGet + precompiled bundle | +| Tree-shaken CSS, I already run Tailwind for my own utilities | **Path B** — NuGet + safelist | +| Own the component source code, restyle by editing `.razor` files | **Path C** — CLI | +| Prototype in a static HTML page or JSFiddle without NuGet at all | **Path D** — CDN | + +The four paths coexist cleanly. You can even mix them (use CLI for a few customized components and NuGet for the rest). + +--- + +### Path A — NuGet + precompiled CSS bundle (simplest, new in 0.4.x) + +**Who it's for:** you want components on screen with minimum ceremony. No Tailwind config, no npm, no CLI. Just NuGet and a `` tag. + +**Setup (2 lines of code):** + +```bash +dotnet add package ShellUI.Components +``` + +```razor +@* App.razor *@ + + +@* _Imports.razor *@ +@using ShellUI.Components +``` + +Done. `