diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..031fe56 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +# CI for polyengine-dioxus. +# +# Runs exactly the gates `just` runs locally, in the same order and through +# the same recipes, so a green check means what `just check && just test` +# means on a workstation. New gates belong in the justfile, not here; this +# file only supplies a machine to run them on. +# +# Why the build steps below are not optional: the two component-loading +# suites (host/tests/counter_test.ts, host/tests/fullstack_test.ts) THROW +# when their .wasm is missing rather than skipping it, so dropping a build +# step turns the suite red rather than quietly green. The one silent-skip +# in the suite is host/tests/decoder_test.ts's "golden vectors pending", +# which fires only if vectors/ is empty — it is committed, so the +# cross-language check against the Rust encoder always runs. +# +# Everything is version-pinned, matching how the rest of the repo pins +# (POLYENGINE_REV in the justfile, `=0.7.10` dioxus, `=0.60.0` +# wit-bindgen). A floating toolchain can turn `clippy -D warnings` red on a +# new lint with no commit to blame. + +name: ci + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + RUST_VERSION: "1.98.0" + DENO_VERSION: "2.9.5" + WASM_TOOLS_VERSION: "1.247.0" + JUST_VERSION: "1.54.0" + +jobs: + gates: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + # wasm32-wasip2 builds the app components; wasm32-unknown-unknown + # builds polyengine's translator shim in `just deps`. + - name: Install Rust ${{ env.RUST_VERSION }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: wasm32-wasip2, wasm32-unknown-unknown + components: clippy + + - uses: denoland/setup-deno@v2 + with: + deno-version: ${{ env.DENO_VERSION }} + + - uses: taiki-e/install-action@v2 + with: + tool: just@${{ env.JUST_VERSION }},wasm-tools@${{ env.WASM_TOOLS_VERSION }} + + - uses: Swatinem/rust-cache@v2 + + # Saves the pinned checkout and the built translator_shim.wasm, so + # `just deps` becomes a fetch+checkout on a hit. Excludes the shim's + # own build dir (large, and only needed when the shim is absent). + # Keyed on the whole justfile rather than POLYENGINE_REV alone: + # conservative (an unrelated justfile edit misses), but it cannot go + # stale against a rev bump, which is the failure that would matter. + - name: Cache pinned polyengine checkout + uses: actions/cache@v6 + with: + path: | + .deps/polyengine + !.deps/polyengine/target + key: polyengine-${{ runner.os }}-${{ hashFiles('justfile') }} + + - run: just deps + + # cargo check + clippy -D warnings + deno check (host/src, host/tests, + # bench). + - run: just check + + - name: Build test fixture component + run: just fixtures + + - name: Build counter example component + run: just example counter + + # cargo test (incl. the golden-vector encoder check) + deno task test + # (incl. the same vectors against the TypeScript decoder). + - run: just test diff --git a/bench/ops.ts b/bench/ops.ts index a3fd44c..b7e126d 100644 --- a/bench/ops.ts +++ b/bench/ops.ts @@ -25,6 +25,13 @@ import { parseHTML } from "linkedom"; import { mountApp } from "../host/src/host.ts"; import type { Mounted } from "../host/src/host.ts"; +import type { UntranslatedArtifacts } from "@deltic/runtime/embedder"; + +/** Taken from the embedder's own type rather than restated, so this stays + * pinned to what `mountApp`'s `source` actually accepts. (The `unknown` it + * replaces did not hide the stale call shape below — `deno check` reports it + * fine; nothing was running `deno check` over bench/. See deno.json.) */ +type Translator = UntranslatedArtifacts["translator"]; export const RUNS = 5; // Below this, for an op touching >=1000 rows, the number is not credible @@ -199,11 +206,15 @@ export function median(xs: number[]): number { async function freshMount( t: TransportName, componentBytes: Uint8Array, - translator: unknown, + translator: Translator, ): Promise<{ root: Element; mounted: Mounted; errors: unknown[] }> { const root = makeRoot(); const errors: unknown[] = []; - const mounted = await mountApp({ componentBytes, translator, root, onError: (err) => errors.push(err) }); + const mounted = await mountApp({ + source: { componentBytes, translator }, + root, + onError: (err) => errors.push(err), + }); await waitFor(() => root.querySelector("#row-count") !== null, `${t}: initial mount`); if (errors.length > 0) { throw new Error(`${t}: onError fired during mount: ${Deno.inspect(errors)}`); @@ -377,7 +388,7 @@ const MAX_ATTEMPTS_PER_OP = 3; export async function runOp( t: TransportName, componentBytes: Uint8Array, - translator: unknown, + translator: Translator, op: OpDef, ): Promise { let lastErr: unknown; @@ -405,7 +416,7 @@ export async function runOp( async function runOpOnce( t: TransportName, componentBytes: Uint8Array, - translator: unknown, + translator: Translator, op: OpDef, ): Promise { const { root, mounted } = await freshMount(t, componentBytes, translator); diff --git a/deno.json b/deno.json index 8379998..9058c7a 100644 --- a/deno.json +++ b/deno.json @@ -11,7 +11,7 @@ "lib": ["deno.ns", "dom", "dom.iterable", "esnext"] }, "tasks": { - "check": "deno check host/src host/tests", + "check": "deno check host/src host/tests bench", "test": "deno test --allow-read=. host/tests/", "bench": "bash bench/run.sh" }