Skip to content
Merged
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
111 changes: 111 additions & 0 deletions .github/workflows/publish-selfhost-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,121 @@ jobs:
if-no-files-found: error
retention-days: 1

# Release gate: run the full selfhost-docker e2e project against the exact
# by-digest image each build leg pushed, before any user-visible tag moves.
# Build-only smoke misses startup crashes and feature-path breaks (a package
# resolved at runtime but missing from the packaged runtime shipped broken in
# v1.5.30 exactly this way); per-arch matters too — the libsql native-addon
# bug was arm64-only.
e2e:
needs:
- metadata
- build
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: blacksmith-4vcpu-ubuntu-2404
- arch: arm64
runner: blacksmith-4vcpu-ubuntu-2404-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
permissions:
contents: read
packages: read

steps:
- name: Checkout release tag
uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.release_tag }}
persist-credentials: false

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-${{ runner.arch }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-bun-1.3.11-

# Scenario helpers spawn Node sidecars; pin the same Node 24 runtime the
# other workflows use.
- uses: actions/setup-node@v4
with:
node-version: 24

- run: bun install --frozen-lockfile

- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-${{ runner.arch }}-playwright-1.60.0
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-playwright-

# Install from e2e so bunx resolves ITS pinned playwright (the version
# the tests run against) rather than floating to the latest.
- name: Install Playwright Chromium
run: bunx playwright install --with-deps chromium chromium-headless-shell
working-directory: e2e

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Download image digest
uses: actions/download-artifact@v4
with:
name: digests-${{ matrix.arch }}
path: ${{ runner.temp }}/digests

- name: Resolve image reference
id: image_ref
shell: bash
env:
IMAGE: ${{ needs.metadata.outputs.image }}
run: |
set -euo pipefail
digest_file="$(find "$RUNNER_TEMP/digests" -maxdepth 1 -type f | head -n 1)"
if [ -z "$digest_file" ]; then
echo "No image digest was downloaded."
exit 1
fi
ref="${IMAGE}@sha256:$(basename "$digest_file")"
echo "ref=$ref" >> "$GITHUB_OUTPUT"
docker pull "$ref"

- name: Run selfhost-docker scenarios against the published digest
env:
E2E_SELFHOST_DOCKER_IMAGE: ${{ steps.image_ref.outputs.ref }}
run: bunx vitest run --project selfhost-docker --retry=2
working-directory: e2e

- name: Upload run artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-runs-selfhost-docker-${{ matrix.arch }}
path: |
e2e/runs/
e2e/selfhost-docker.boot.log
retention-days: 7

merge:
needs:
- metadata
- build
- e2e
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
packages: write
Expand Down
13 changes: 7 additions & 6 deletions e2e/targets/selfhost-docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
// lives in setup/selfhost-docker.globalsetup.ts.
import { Effect } from "effect";

import { cookieConsentStrategy } from "@executor-js/mcporter";

import { e2ePort } from "../src/ports";
import type { Identity, Target } from "../src/target";
import { runSelfhostContainer, stopSelfhostContainer } from "../setup/selfhost-docker.boot";
import { SELFHOST_ADMIN, signInSession } from "./selfhost";
import { forcedMcpConsent, SELFHOST_ADMIN, signInSession } from "./selfhost";

export const SELFHOST_DOCKER_PORT = e2ePort("E2E_SELFHOST_DOCKER_PORT", 5);
export const SELFHOST_DOCKER_BASE_URL =
Expand All @@ -35,11 +33,14 @@ export const selfhostDockerTarget = (): Target => ({
cookies,
};
}),
// Same forced-consent completion as the selfhost dev target: the app forces
// `prompt=consent` on every MCP authorize, so authorize redirects to the
// relative `/mcp-consent?consent_code=...` screen instead of straight to the
// callback — mcporter's cookieConsentStrategy can't parse that redirect.
mcpConsent: (identity: Identity) =>
cookieConsentStrategy({
appBaseUrl: SELFHOST_DOCKER_BASE_URL,
forcedMcpConsent(SELFHOST_DOCKER_BASE_URL, {
email: identity.credentials?.email ?? SELFHOST_ADMIN.email,
password: identity.credentials?.password ?? SELFHOST_ADMIN.password,
password: identity.credentials?.password || SELFHOST_ADMIN.password,
}),
// A real deployment cycle, not a warm `docker restart`: graceful stop
// (SIGTERM + docker's grace), remove the container, start a NEW one from
Expand Down
2 changes: 1 addition & 1 deletion e2e/targets/selfhost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const signInSession = async (
// mcporter's `cookieConsentStrategy` only handles the old direct-code redirect,
// so this completes the screen the way the page does: sign in, drive authorize,
// then POST the same `/api/auth/oauth2/consent` grant the Allow button fires.
const forcedMcpConsent =
export const forcedMcpConsent =
(baseUrl: string, credentials: { readonly email: string; readonly password: string }) =>
async ({ authorizationUrl }: { authorizationUrl: string }): Promise<{ code: string }> => {
const origin = new URL(baseUrl).origin;
Expand Down
Loading