Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
.gitignore
WASM_VERSION
scripts/download-wasm.sh
scripts/e2e-pack.sh
CLAUDE.md
LICENSE
README.md
examples/
testapp/
testapp-packaged/
src/SchematicHQ.Client.Test/Cache/
src/SchematicHQ.Client.Test/Datastream/
src/SchematicHQ.Client.Test/Integration/
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,6 @@ $RECYCLE.BIN/
# Rules engine WASM binary (downloaded at build time from schematic-api GitHub Releases)
src/SchematicHQ.Client/RulesEngine/Wasm/rulesengine.wasm
src/SchematicHQ.Client/RulesEngine/Wasm/.wasm_version

# Local NuGet feed produced by scripts/e2e-pack.sh for the packaged-artifact E2E variant
artifacts/
61 changes: 61 additions & 0 deletions scripts/e2e-pack.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash
set -euo pipefail

# Packs the SchematicHQ.Client SDK into a local NuGet feed for the packaged-artifact
# E2E variant (testapp-packaged/). This validates the real distribution artifact:
# the embedded rulesengine.wasm and the transitive Wasmtime native library must
# survive pack -> restore -> run, which a plain <ProjectReference> build never
# exercises.
#
# Produces: artifacts/local-feed/SchematicHQ.Client.<version>.nupkg
# Consumed by: testapp-packaged/Testapp.Packaged.csproj (via testapp-packaged/nuget.config)

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FEED_DIR="$REPO_ROOT/artifacts/local-feed"
PKG_VERSION="${SCHEMATIC_E2E_PKG_VERSION:-0.0.0-e2e}"

# Ensure the rules engine WASM is present before packing so it gets embedded.
"$SCRIPT_DIR/download-wasm.sh"

echo "Packing SchematicHQ.Client v${PKG_VERSION} -> ${FEED_DIR}"
rm -rf "$FEED_DIR"
mkdir -p "$FEED_DIR"

dotnet pack "$REPO_ROOT/src/SchematicHQ.Client/SchematicHQ.Client.csproj" \
-c Release \
-p:Version="$PKG_VERSION" \
-o "$FEED_DIR"

NUPKG="$FEED_DIR/SchematicHQ.Client.${PKG_VERSION}.nupkg"
if [ ! -f "$NUPKG" ]; then
echo "ERROR: expected package not produced: $NUPKG" >&2
exit 1
fi

# Guard: fail loudly if the embedded WASM did not make it into the packaged DLL.
# The wasm (~672 KB) is an embedded manifest resource inside SchematicHQ.Client.dll,
# not a loose package entry, so we verify by DLL size — the exact packaging
# regression this variant exists to catch (an empty/stripped resource ships a
# DLL hundreds of KB smaller).
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
unzip -o -q "$NUPKG" -d "$TMP"

DLL="$TMP/lib/net8.0/SchematicHQ.Client.dll"
if [ ! -f "$DLL" ]; then
echo "ERROR: lib/net8.0/SchematicHQ.Client.dll missing from package" >&2
exit 1
fi

# Cross-platform file size (macOS: stat -f%z, Linux: stat -c%s).
DLL_SIZE=$(stat -f%z "$DLL" 2>/dev/null || stat -c%s "$DLL")
MIN_SIZE=600000 # rulesengine.wasm is ~672 KB; a DLL without it is far smaller.
if [ "$DLL_SIZE" -lt "$MIN_SIZE" ]; then
echo "ERROR: packaged DLL is ${DLL_SIZE} bytes (< ${MIN_SIZE})." >&2
echo " rulesengine.wasm appears NOT embedded — the package would silently" >&2
echo " fall back to flag defaults at runtime." >&2
exit 1
fi

echo "Packed OK: $NUPKG (embedded DLL ${DLL_SIZE} bytes)"
66 changes: 66 additions & 0 deletions testapp-packaged/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# testapp-packaged — packaged-artifact E2E variant

This is a second copy of the SDK E2E test app that consumes `SchematicHQ.Client`
as a **NuGet package** instead of a `<ProjectReference>`. It exists to catch
packaging/install failures that the source-built `../testapp` cannot.

## Why

The rules engine now runs inside a WebAssembly binary (`rulesengine.wasm`) that is
**embedded in the SDK DLL** and executed by the **Wasmtime native library**
(`libwasmtime`), which ships as a transitive, per-RID native asset of the `Wasmtime`
NuGet package. There are several links in the chain where that can break between
`dotnet build` on a dev machine and a real end-user install:

- `dotnet pack` producing a DLL that is missing the embedded `.wasm`
- the NuGet package omitting or mis-declaring the `Wasmtime` dependency
- `libwasmtime` failing to resolve for the consumer's RID at restore/publish time
(e.g. `linux-musl-x64` / Alpine, `linux-arm`, `win-x86` are **not** shipped by
Wasmtime)

`../testapp` builds the SDK from local source, so it exercises none of these — its
green run only proves the working tree compiles. This variant restores the built
`.nupkg` from a local feed, so a datastream-mode flag check here runs through the
**packaged** wasm + Wasmtime exactly as an end user would.

## How it works

1. `scripts/e2e-pack.sh` packs the SDK (`-p:Version=0.0.0-e2e`) into
`artifacts/local-feed/` and asserts the embedded wasm survived (DLL size guard).
2. `nuget.config` here adds that folder as a package source.
3. `Testapp.Packaged.csproj` links `../testapp/Program.cs` (identical app logic) and
references `SchematicHQ.Client` `0.0.0-e2e` from the local feed.

## Run locally

```bash
# from repo root — needs gh auth to the private schematic-api repo for the wasm
./scripts/e2e-pack.sh
dotnet build testapp-packaged/Testapp.Packaged.csproj -c Release
dotnet run --project testapp-packaged/Testapp.Packaged.csproj --no-build -c Release
# then, in another shell, drive it with actions/sdk-e2e/run-local.sh (datastream mode):
# E2E_API_KEY=api_xxx ./run-local.sh '{"useDataStream": true}'
```

## Wire into the shared suite

Add a matrix entry alongside the existing `csharp` one in
`schematic-api/.github/workflows/sdk_e2e.yml` so the packaged artifact is smoke-tested
in a wasm-backed (datastream) mode on every run:

```yaml
csharp-packaged:
repository: SchematicHQ/schematic-csharp
dotnet-version: "8.0"
setup: ./scripts/e2e-pack.sh && dotnet build testapp-packaged/Testapp.Packaged.csproj -c Release
start: dotnet run --project testapp-packaged/Testapp.Packaged.csproj --no-build -c Release
```

> **RID caveat:** GitHub's `ubuntu-latest` runner is `linux-x64` (glibc) — the RID
> Wasmtime *does* ship. This variant therefore proves the pack/restore/embed chain but
> **not** the platforms most likely to be missing a `libwasmtime` (Alpine/musl, arm,
> Windows, macOS). To cover those, run the packaged testapp inside the target base
> image (e.g. an `mcr.microsoft.com/dotnet/aspnet:8.0-alpine` container) and assert a
> non-default flag result. Because `DatastreamClient` degrades to flag defaults when the
> engine fails to initialize, a missing native lib is silent — the assertion must check
> a rule-derived value, not merely that the call returned.
38 changes: 38 additions & 0 deletions testapp-packaged/Testapp.Packaged.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<!--
Packaged-artifact E2E variant of the SDK test app.

Unlike ../testapp (which uses a <ProjectReference> to the local source), this
project consumes SchematicHQ.Client as a *NuGet package* restored from a local
feed (see nuget.config + scripts/e2e-pack.sh). That exercises the full
dev -> pack -> restore -> run chain, so the shared E2E suite can catch failures a
source build hides:
- the embedded rulesengine.wasm being stripped/omitted at pack time, and
- the transitive Wasmtime native library (libwasmtime) failing to resolve for
the consumer's runtime identifier (RID).

The application logic is identical to ../testapp: Program.cs is linked, not
duplicated, so both variants stay in lockstep.
-->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>SchematicHQ.Testapp</RootNamespace>
<AssemblyName>Testapp</AssemblyName>
<!-- Deterministic version stamped by scripts/e2e-pack.sh; overridable via the
SchematicPackageVersion MSBuild property or env var. -->
<SchematicPackageVersion Condition="'$(SchematicPackageVersion)' == ''">0.0.0-e2e</SchematicPackageVersion>
</PropertyGroup>

<ItemGroup>
<!-- Share the exact app logic with the project-reference test app. -->
<Compile Include="../testapp/Program.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="SchematicHQ.Client" Version="$(SchematicPackageVersion)" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions testapp-packaged/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<!-- nuget.org resolves transitive deps (Wasmtime, OpenFeature, StackExchange.Redis, ...). -->
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- Local feed populated by scripts/e2e-pack.sh (dotnet pack -o artifacts/local-feed).
Path is relative to this nuget.config. -->
<add key="local-e2e" value="../artifacts/local-feed" />
</packageSources>
</configuration>
Loading