Skip to content

chore(deps-dev): bump the dev-dependencies group across 1 directory with 12 updates #958

chore(deps-dev): bump the dev-dependencies group across 1 directory with 12 updates

chore(deps-dev): bump the dev-dependencies group across 1 directory with 12 updates #958

Workflow file for this run

name: Conformance
# Wires conformance + the release gate into CI (issue #12). The cheap, always-on
# jobs (release-gate, integration) run on every PR/push and gate stable
# releases. deploy-target auto-deploys packages/conformance-target to
# conformance.dwk.io on every push to main, in addition to on demand
# (workflow_dispatch) or the weekly schedule. The hosted conformance suites
# need a deployed, publicly reachable Worker, so they only run on demand or on
# the weekly schedule, against a target URL — they never block ordinary
# pushes.
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 6 * * 1" # Mondays 06:00 UTC
workflow_dispatch:
inputs:
standard:
description: "Which hosted suite to run"
required: true
type: choice
options: [micropub, webmention, solid, webdav, activitypub]
target_url:
description: "Base URL of a deployed target to test"
required: false
target_id:
description: "Conformance column to record into"
required: false
default: cloudflare
type: choice
options: [cloudflare, node, fedify]
peer_url:
description: >-
activitypub only: publicly reachable URL for the ephemeral Fedify
peer's inbox. Usually left empty — the job auto-provisions a
Cloudflare Quick Tunnel on the runner. Supply one only to override
(e.g. a tunnel you started yourself against a peer running
elsewhere).
required: false
permissions:
contents: read
jobs:
# Static guard: refuses to let any package go stable (>=1.0.0) before its
# conformance suites + integration lifecycle tests are recorded as passing.
release-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
# Node 24 for parity with CI: @dwk/server's node:sqlite shims run
# flag-free (spec/self-hosting.md §16 decision 2).
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Unit-test the release gate
run: pnpm test:gate
- name: Enforce release gate
run: pnpm release:gate
- name: Unit-test the catalog gate
run: pnpm test:catalog
- name: Enforce catalog gate (catalog.json covers every package)
run: pnpm catalog:check
# Verb-lifecycle integration tests (authenticated GET-through-WAC; PATCH with
# where/conflict; If-Match preconditioned writes) under workerd/Miniflare.
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
# Node 24 for parity with CI: @dwk/server's node:sqlite shims run
# flag-free (spec/self-hosting.md §16 decision 2).
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Integration lifecycle tests
run: pnpm test:integration
# Deploys the composed conformance target (packages/conformance-target) to
# conformance.dwk.io so the hosted suites have something to run against.
# Skips gracefully when the Cloudflare secrets are not configured, so the
# weekly schedule (and pushes to main, on forks) stays green before
# one-time setup. Runs on every push to main so conformance.dwk.io tracks
# main directly, in addition to the manual/scheduled paths.
deploy-target:
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'schedule' ||
github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Check Cloudflare credentials
id: creds
run: echo "ok=${{ secrets.CLOUDFLARE_API_TOKEN != '' }}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
if: steps.creds.outputs.ok == 'true'
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
if: steps.creds.outputs.ok == 'true'
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
if: steps.creds.outputs.ok == 'true'
with:
# Node 24 for parity with CI: @dwk/server's node:sqlite shims run
# flag-free (spec/self-hosting.md §16 decision 2).
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
if: steps.creds.outputs.ok == 'true'
- name: Build workspace packages
if: steps.creds.outputs.ok == 'true'
run: pnpm build
- name: Deploy conformance target
if: steps.creds.outputs.ok == 'true'
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: packages/conformance-target
- name: Deploy skipped
if: steps.creds.outputs.ok != 'true'
run: echo "CLOUDFLARE_API_TOKEN not configured; skipping deploy."
# Hosted conformance suites: only on manual dispatch or the weekly schedule,
# since they require a deployed target. The runner is a documented no-op when
# no target URL is supplied.
hosted-suite:
needs: deploy-target
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
runs-on: ubuntu-latest
strategy:
matrix:
standard: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.standard)) || fromJSON('["micropub","webmention","solid","webdav","activitypub"]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
# Node 24 for parity with CI: @dwk/server's node:sqlite shims run
# flag-free (spec/self-hosting.md §16 decision 2).
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
# litmus is a real CLI the WebDAV dispatcher shells out to; the Fedify
# peer (activitypub) is a devDependency already installed above; the
# other suites are hosted web services that need no local tooling.
- name: Install litmus (WebDAV)
if: matrix.standard == 'webdav'
run: sudo apt-get update && sudo apt-get install -y litmus
# ActivityPub: the ephemeral Fedify peer needs a publicly reachable
# callback URL. Unless the operator supplied peer_url themselves,
# auto-provision a Cloudflare Quick Tunnel (no account needed —
# Cloudflare is already this project's deploy target) forwarding to the
# peer's port on this runner. Failure to come up is non-fatal: the
# callback cases then report `skipped` loudly, and `webfinger` still
# runs.
- name: Provision Quick Tunnel (ActivityPub)
id: tunnel
if: >-
matrix.standard == 'activitypub' &&
inputs.target_url != '' &&
inputs.peer_url == ''
timeout-minutes: 3
run: |
curl -fsSL -o /tmp/cloudflared \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x /tmp/cloudflared
/tmp/cloudflared tunnel --url http://localhost:8765 --no-autoupdate \
> /tmp/cloudflared.log 2>&1 &
url=""
for _ in $(seq 1 30); do
url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /tmp/cloudflared.log | head -1 || true)
[ -n "$url" ] && break
sleep 2
done
if [ -z "$url" ]; then
echo "::warning::Quick Tunnel did not come up; callback cases will be skipped."
cat /tmp/cloudflared.log || true
else
echo "peer_url=$url" >> "$GITHUB_OUTPUT"
echo "Quick Tunnel ready: $url"
fi
# The owner publish URL is only meaningful for the ActivityPub suite, and
# building it is a URL *join*, not a string concat. Doing it inline with
# `format()` meant a `target_url` that carries a path or a trailing slash
# — which the WebDAV runbook's `https://…/dav/` requires — produced
# `https://…/dav//outbox`, and every non-ActivityPub dispatch computed a
# nonsense URL that then surfaced in the job log. Resolve it here
# instead: scoped to the one suite that reads it, with trailing slashes
# stripped. An unset token still yields an empty value, so the publish
# cases report `skipped` exactly as before.
- name: Resolve ActivityPub publish URL
id: publish
if: matrix.standard == 'activitypub'
env:
TARGET_URL: ${{ inputs.target_url || 'https://conformance.dwk.io' }}
PUBLISH_TOKEN: ${{ secrets.FEDIFY_PUBLISH_TOKEN }}
run: |
if [ -z "$PUBLISH_TOKEN" ]; then
echo "FEDIFY_PUBLISH_TOKEN unset; publish cases will report skipped."
exit 0
fi
base="$TARGET_URL"
while [ "$base" != "${base%/}" ]; do base="${base%/}"; done
echo "url=${base}/outbox" >> "$GITHUB_OUTPUT"
echo "Owner publish URL: ${base}/outbox"
- name: Run hosted conformance suite
env:
# The weekly schedule carries no `inputs` (those only exist for
# workflow_dispatch), so without a fallback here every scheduled run
# invoked run-suite.mjs with no --target and silently no-op'd (green
# badge, zero suites actually run). deploy-target above just
# (re)deployed exactly this URL, on both the schedule and dispatch
# paths, so it's the correct default whenever an operator doesn't
# override it.
TARGET_URL: ${{ inputs.target_url || 'https://conformance.dwk.io' }}
# Basic credentials for the WebDAV mount (an app password minted via
# the owner-gated endpoint); unset for the other suites.
WEBDAV_USERNAME: ${{ secrets.WEBDAV_USERNAME }}
WEBDAV_PASSWORD: ${{ secrets.WEBDAV_PASSWORD }}
# Publicly reachable callback URL for the ephemeral Fedify peer
# (issue #246): the operator's peer_url override when given, else
# the Quick Tunnel provisioned above (empty when neither — the
# callback cases then report `skipped`, never silently dropped).
FEDIFY_PEER_URL: ${{ inputs.peer_url || steps.tunnel.outputs.peer_url }}
# Owner publish channel for the fanout/announce-unwrap cases
# (fediverse interop #273): the deployed target's outbox URL plus
# its ACTIVITYPUB_PUBLISH_TOKEN (mirrored into this repo secret).
# Resolved by the step above (empty for every other suite, and when
# the token is unset) ⇒ those cases report `skipped`.
FEDIFY_PUBLISH_URL: ${{ steps.publish.outputs.url }}
FEDIFY_PUBLISH_TOKEN: ${{ secrets.FEDIFY_PUBLISH_TOKEN }}
# Matrix/input values interpolated via env, never directly into the
# `run:` shell — workflow_dispatch inputs are attacker-controlled
# for anyone with write access, so string-building a command from
# them directly is a shell-injection vector.
STANDARD: ${{ matrix.standard }}
TARGET_ID: ${{ inputs.target_id || (matrix.standard == 'activitypub' && 'fedify' || 'cloudflare') }}
run: |
node scripts/conformance/run-suite.mjs "$STANDARD" \
--target "$TARGET_URL" \
--target-id "$TARGET_ID"