Skip to content

feat(nuget): ship precompiled CSS bundle + fix safelist gap (variant .cs helpers) #57

feat(nuget): ship precompiled CSS bundle + fix safelist gap (variant .cs helpers)

feat(nuget): ship precompiled CSS bundle + fix safelist gap (variant .cs helpers) #57

Workflow file for this run

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