feat(nuget): ship precompiled CSS bundle + fix safelist gap (variant .cs helpers) #57
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: | | |
| 10.0.x | |
| - name: Restore dependencies | |
| run: dotnet restore ShellUI.slnx | |
| # Regenerates src/ShellUI.Components/wwwroot/shellui-all.css and fails if it | |
| # differs from the committed file. Prevents the "someone edited a component | |
| # but forgot to rebuild the precompiled bundle" bug shipping to NuGet users. | |
| - name: Verify precompiled CSS bundle is up-to-date | |
| run: | | |
| set -euxo pipefail | |
| ./scripts/rebuild-precompiled-css.sh | |
| git diff --exit-code src/ShellUI.Components/wwwroot/shellui-all.css || ( | |
| echo "" | |
| echo "ERROR: src/ShellUI.Components/wwwroot/shellui-all.css is out of sync with the razor sources." | |
| echo "Regenerate locally and commit:" | |
| echo " ./scripts/rebuild-precompiled-css.sh" | |
| exit 1 | |
| ) | |
| # Bundle size guard — well above the ~77KB the current 68 components emit, | |
| # generous enough to absorb reasonable growth but tight enough to catch a | |
| # runaway (e.g. someone accidentally disables minify or dumps the whole | |
| # tailwind base without tree-shake). | |
| size=$(wc -c < src/ShellUI.Components/wwwroot/shellui-all.css) | |
| 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 | |
| - name: Run tests | |
| run: dotnet test ShellUI.slnx --no-restore --no-build --configuration Release --verbosity normal | |
| # End-to-end build of a scaffolded project — catches anything the in-process | |
| # TemplateCompileTests miss (missing usings, dependency resolution). | |
| - name: Smoke-test CLI scaffolding | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| TMPDIR=$(mktemp -d) | |
| dotnet pack src/ShellUI.CLI/ShellUI.CLI.csproj -c Release -o "$TMPDIR/nupkgs" --no-build | |
| dotnet tool install --tool-path "$TMPDIR/tools" --add-source "$TMPDIR/nupkgs" ShellUI.CLI --prerelease | |
| export PATH="$TMPDIR/tools:$PATH" | |
| mkdir -p "$TMPDIR/app" && cd "$TMPDIR/app" | |
| dotnet new blazor -o SmokeApp --no-restore | |
| cd SmokeApp | |
| shellui init --tailwind standalone --yes | |
| # Assert init produced a working host: App.razor patched with render mode, | |
| # theme bootstrap, and shellui.js script tag. | |
| grep -q 'HeadOutlet @rendermode="InteractiveServer"' Components/App.razor || (echo "init did not patch HeadOutlet @rendermode"; exit 1) | |
| grep -q 'Routes @rendermode="InteractiveServer"' Components/App.razor || (echo "init did not patch Routes @rendermode"; exit 1) | |
| grep -q 'ShellUI theme bootstrap' Components/App.razor || (echo "init did not inject theme bootstrap"; exit 1) | |
| grep -q '<script src="shellui.js"></script>' Components/App.razor || (echo "init did not inject shellui.js script tag"; exit 1) | |
| grep -q 'shellui-sidebar.js' Components/App.razor && (echo "init incorrectly injected shellui-sidebar.js script tag (sidebar JS is dynamically imported)"; exit 1) || true | |
| # Assert input.css has the full theme, not just @import "tailwindcss"; | |
| grep -q '@theme inline' wwwroot/input.css || (echo "init did not write full theme to input.css"; exit 1) | |
| grep -q ':root' wwwroot/input.css || (echo "init did not write :root variables to input.css"; exit 1) | |
| grep -q '\.dark' wwwroot/input.css || (echo "init did not write .dark variables to input.css"; exit 1) | |
| shellui add chart pie-chart dashboard-02 data-table --force | |
| # NuGet dependencies (Blazor-ApexCharts, System.Linq.Dynamic.Core) should | |
| # now be added automatically by `shellui add`. Assert they appear in the | |
| # project file so a regression in the auto-install fails loudly here. | |
| grep -q 'Blazor-ApexCharts' SmokeApp.csproj || (echo "shellui add chart did not add Blazor-ApexCharts NuGet dep"; exit 1) | |
| grep -q 'System.Linq.Dynamic.Core' SmokeApp.csproj || (echo "shellui add data-table did not add System.Linq.Dynamic.Core NuGet dep"; exit 1) | |
| # And the DataTable models file must land at Components/UI/Models/, not be missing. | |
| test -f Components/UI/Models/DataTableModels.cs || (echo "shellui add data-table did not install data-table-models"; exit 1) | |
| # chart-styles ships the CSS for the custom tooltip + ApexCharts chrome. | |
| # Without it, hovering a chart shows invisible white-on-white text. | |
| test -f wwwroot/css/charts.css || (echo "shellui add chart did not install chart-styles CSS"; exit 1) | |
| grep -q '<link href="css/charts.css"' Components/App.razor || (echo "shellui add chart did not link charts.css in App.razor"; exit 1) | |
| dotnet build -c Debug | |
| # Pure-NuGet install path — `dotnet add package ShellUI.Components` without | |
| # the CLI. Uses a one-off NuGet.config that whitelists ONLY the local feed, | |
| # so a missing local package can't silently fall back to nuget.org and pull | |
| # an older version (which would mask whether the current PR's package is | |
| # correct). $GITHUB_WORKSPACE is absolute — relative paths from inside the | |
| # temp consumer dir would resolve to the wrong root. | |
| - name: NuGet-only install — verify safelist ships | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| LOCAL_FEED="$GITHUB_WORKSPACE/src/ShellUI.Components/bin/Release" | |
| 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 | |
| TMPDIR=$(mktemp -d) | |
| mkdir -p "$TMPDIR/app" && cd "$TMPDIR/app" | |
| dotnet new blazor -o NuGetSmoke --no-restore | |
| cd NuGetSmoke | |
| # Pin restore to only the local feed; nuget.org explicitly disabled so a | |
| # broken local feed can't be papered over by a public-published older version. | |
| cat > NuGet.config <<EOF | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <configuration> | |
| <packageSources> | |
| <clear /> | |
| <add key="local" value="$LOCAL_FEED" /> | |
| </packageSources> | |
| </configuration> | |
| EOF | |
| dotnet add package ShellUI.Components --prerelease | |
| # The safelist gets copied into wwwroot/ during the consumer's build by | |
| # the package's build/ShellUI.Components.targets — auto-imported by NuGet. | |
| # Need to actually build the project, not just restore, for the Copy to run. | |
| dotnet build -c Debug | |
| SAFELIST="wwwroot/shellui-classes.txt" | |
| test -f "$SAFELIST" || ( | |
| echo "ERROR: $SAFELIST not in consumer project after dotnet build." | |
| echo "The package's build/ShellUI.Components.targets should have copied it." | |
| echo "Files in wwwroot/:"; ls -la wwwroot/ 2>/dev/null || echo " (no wwwroot/ directory)" | |
| exit 1 | |
| ) | |
| # Sanity: the safelist contains real Tailwind classes | |
| grep -q 'bg-background' "$SAFELIST" || (echo "safelist appears malformed — missing core Tailwind class 'bg-background'"; exit 1) | |
| wc -l "$SAFELIST" | |
| # The precompiled bundle is a runtime static web asset. After the consumer's | |
| # build, Razor SDK copies static assets from referenced packages into the | |
| # consumer's build output under bin/…/wwwroot/_content/<PackageId>/. Assert | |
| # the path exists and the bundle has real Tailwind rules — the "install | |
| # NuGet + one <link> tag = styled components" story hinges on this. | |
| BUNDLE="$(find bin -type f -name 'shellui-all.css' | head -1)" | |
| test -n "$BUNDLE" || ( | |
| echo "ERROR: shellui-all.css not in consumer's build output — the pure-NuGet install path is broken." | |
| find bin -type d -name '_content' -exec ls -la {} \; 2>/dev/null || echo " (no _content dir)" | |
| exit 1 | |
| ) | |
| echo "found bundle at: $BUNDLE" | |
| BUNDLE_SIZE=$(wc -c < "$BUNDLE") | |
| echo "bundle size: $BUNDLE_SIZE bytes" | |
| # Spot-check a handful of representative rules covering the categories | |
| # that break most easily (theme vars, hover, data-attribute selectors). | |
| grep -q -- '--background:' "$BUNDLE" || (echo "ERROR: bundle missing --background theme var"; exit 1) | |
| grep -q '\.bg-background' "$BUNDLE" || (echo "ERROR: bundle missing .bg-background utility"; exit 1) | |
| grep -q '\.hover.:bg-accent:hover' "$BUNDLE" || (echo "ERROR: bundle missing hover:bg-accent modifier rule"; exit 1) | |
| grep -q '\[state=open\]' "$BUNDLE" || (echo "ERROR: bundle missing data-[state=open] modifier — arbitrary values not compiled"; exit 1) | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nuget-packages | |
| path: | | |
| **/*.nupkg | |
| **/*.snupkg |