Skip to content

feat(cli): add telegram channel adapter, scoped config, and diagnostics (replacement) #924

feat(cli): add telegram channel adapter, scoped config, and diagnostics (replacement)

feat(cli): add telegram channel adapter, scoped config, and diagnostics (replacement) #924

Workflow file for this run

name: CI — Tests
on:
pull_request:
push:
branches:
- dev
- preview
- main
workflow_call:
inputs:
run_ui_e2e:
required: false
default: false
type: boolean
run_mobile_e2e_android:
required: false
default: false
type: boolean
run_mobile_e2e_ios:
required: false
default: false
type: boolean
run_ui:
required: false
default: true
type: boolean
run_server:
required: false
default: true
type: boolean
run_server_db_contract:
required: false
default: true
type: boolean
run_cli:
required: false
default: true
type: boolean
run_stack:
required: false
default: true
type: boolean
run_typecheck:
required: false
default: true
type: boolean
run_cli_daemon_e2e:
required: false
default: true
type: boolean
run_e2e_core:
required: false
default: true
type: boolean
run_e2e_core_slow:
required: false
default: false
type: boolean
run_providers:
required: false
default: false
type: boolean
providers_preset:
required: false
default: all
type: string
providers_tier:
required: false
default: smoke
type: string
providers_opencode:
required: false
default: "1.1.52|c7c1e622aff3d457358385e28dff33ffa8401cb9067e865a9f35a2f94e899490"
type: string
providers_flags:
required: false
default: '{"strict_keys":false,"flake_retry":true,"update_baselines":false}'
type: string
providers_models:
required: false
default: '{"default":"","overrides":""}'
type: string
run_stress:
required: false
default: false
type: boolean
run_release_contracts:
required: false
default: false
type: boolean
run_installers_smoke:
required: false
default: true
type: boolean
installers_channel:
required: false
default: stable
type: string
run_binary_smoke:
required: false
default: true
type: boolean
run_self_host_systemd:
required: false
default: false
type: boolean
run_self_host_launchd:
required: false
default: false
type: boolean
run_self_host_schtasks:
required: false
default: false
type: boolean
run_self_host_daemon:
required: false
default: false
type: boolean
stress_config:
required: false
default: '{"repeat":"5","seed":""}'
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# Speed up CI: UI postinstall vendors large web-only assets (Monaco/Kokoro/Skia web).
# Tests and native builds do not require these assets.
HAPPIER_UI_VENDOR_WEB_ASSETS: "0"
jobs:
ui-e2e:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || (github.event_name == 'workflow_call' && inputs.run_ui_e2e) }}
name: UI E2E (Playwright)
runs-on: ubuntu-22.04
timeout-minutes: 35
env:
# UI E2E boots Expo web; keep web assets available to avoid CI-only runtime surprises.
HAPPIER_UI_VENDOR_WEB_ASSETS: "1"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect UI E2E-relevant changes
id: changes
if: github.event_name != 'workflow_call'
uses: dorny/paths-filter@v3
with:
filters: |
ui_e2e:
- 'apps/ui/**'
- 'apps/server/**'
- 'apps/cli/**'
- 'packages/tests/**'
- 'package.json'
- 'yarn.lock'
- name: Skip UI E2E (no relevant changes)
if: github.event_name != 'workflow_call' && steps.changes.outputs.ui_e2e != 'true'
run: |
echo "UI E2E skipped: no relevant changes."
- name: Setup Node (GitHub Actions)
if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true'
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true'
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile
- name: Install Playwright browsers
if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true'
run: npx playwright install --with-deps chromium
- name: Install sqlite3 (if missing)
if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true'
run: |
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 --version
exit 0
fi
sudo apt-get update
sudo apt-get install -y sqlite3
sqlite3 --version
- name: Run UI E2E
if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true'
run: yarn -s test:e2e:ui
- name: Upload UI E2E artifacts (Playwright)
if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && failure()
uses: actions/upload-artifact@v4
with:
name: ui-e2e-playwright-artifacts
path: |
packages/tests/.project/logs/e2e/ui-playwright
.project/logs/e2e/*ui-e2e*
if-no-files-found: ignore
mobile-e2e-android:
# Native E2E is intentionally opt-in for now (not a PR check).
if: ${{ github.event_name == 'workflow_call' && inputs.run_mobile_e2e_android }}
name: Mobile E2E (Maestro) (android) (opt-in)
runs-on: ubuntu-22.04
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
# Keep this lane mobile-focused (avoid downloading heavy web-only assets).
HAPPIER_INSTALL_SCOPE: "ui,protocol,agents,tests"
HAPPIER_UI_VENDOR_WEB_ASSETS: "0"
run: yarn install --frozen-lockfile
- name: Install Maestro
run: |
bash scripts/ci/install_maestro.sh
- name: Run Mobile E2E (android)
run: yarn -s test:e2e:mobile
- name: Upload Mobile E2E artifacts (Maestro)
if: failure()
uses: actions/upload-artifact@v4
with:
name: mobile-e2e-maestro-android-artifacts
path: |
packages/tests/.project/logs/e2e/mobile-maestro
if-no-files-found: ignore
mobile-e2e-ios:
# Native E2E is intentionally opt-in for now (not a PR check).
if: ${{ github.event_name == 'workflow_call' && inputs.run_mobile_e2e_ios }}
name: Mobile E2E (Maestro) (ios) (opt-in)
runs-on: macos-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
HAPPIER_INSTALL_SCOPE: "ui,protocol,agents,tests"
HAPPIER_UI_VENDOR_WEB_ASSETS: "0"
run: yarn install --frozen-lockfile
- name: Install Maestro
run: |
bash scripts/ci/install_maestro.sh
- name: Run Mobile E2E (ios)
run: yarn -s test:e2e:mobile:ios
- name: Upload Mobile E2E artifacts (Maestro)
if: failure()
uses: actions/upload-artifact@v4
with:
name: mobile-e2e-maestro-ios-artifacts
path: |
packages/tests/.project/logs/e2e/mobile-maestro
if-no-files-found: ignore
ui:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_ui }}
name: UI Tests (unit + integration)
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install Sapling
run: |
bash scripts/ci/install_sapling_ubuntu22.sh
- name: Verify Sapling CLI
run: sl --version
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile
- name: Run protocol unit tests
run: yarn workspace @happier-dev/protocol test
- name: Run unit tests
run: yarn workspace @happier-dev/app test:unit
- name: Run integration tests
run: yarn workspace @happier-dev/app test:integration
shared-packages-unit:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_ui }}
name: Shared Package Unit Tests
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run shared package unit tests
run: |
yarn workspace @happier-dev/transfers test
yarn workspace @happier-dev/agents test
yarn workspace @happier-dev/cli-common test
yarn workspace @happier-dev/connection-supervisor test
yarn --cwd packages/relay-server test
server:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_server }}
name: Server Tests (unit + integration)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run unit tests
run: yarn --cwd apps/server test:unit
- name: Run integration tests
run: yarn --cwd apps/server test:integration
server-db-contract:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_server_db_contract }}
name: Server DB Contract (Postgres)
runs-on: ubuntu-22.04
timeout-minutes: 25
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: happier
POSTGRES_USER: happier
POSTGRES_PASSWORD: happier
ports:
- 5432/tcp
options: >-
--health-cmd="pg_isready -U happier -d happier"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Migrate (Postgres)
env:
DATABASE_URL: postgresql://happier:happier@127.0.0.1:${{ job.services.postgres.ports[5432] }}/happier?sslmode=disable
run: yarn --cwd apps/server -s prisma migrate deploy
- name: Run db contract suite (Postgres)
env:
HAPPIER_DB_PROVIDER: postgres
DATABASE_URL: postgresql://happier:happier@127.0.0.1:${{ job.services.postgres.ports[5432] }}/happier?sslmode=disable
run: yarn --cwd apps/server test:server:db-contract
cli:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_cli }}
name: CLI Tests (unit + integration)
runs-on: ubuntu-22.04
timeout-minutes: 25
env:
HAPPIER_CLI_TMUX_INTEGRATION: "1"
HAPPIER_CLI_DAEMON_REATTACH_INTEGRATION: "1"
HAPPIER_NO_BROWSER_OPEN: "1"
TMPDIR: /tmp
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install system dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
APT_FLAGS="-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30"
if [ "${ACT:-}" = "true" ] || [ -d /var/run/act ]; then
. /etc/os-release || true
CODENAME="${VERSION_CODENAME:-noble}"
ARCH="$(dpkg --print-architecture)"
BASE_URL="http://ports.ubuntu.com/ubuntu-ports"
if [ "${ARCH}" = "amd64" ]; then BASE_URL="http://archive.ubuntu.com/ubuntu"; fi
if command -v sudo >/dev/null 2>&1; then
sudo rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true
echo "deb ${BASE_URL} ${CODENAME} main universe" | sudo tee /etc/apt/sources.list >/dev/null
else
rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true
echo "deb ${BASE_URL} ${CODENAME} main universe" > /etc/apt/sources.list
fi
fi
if command -v sudo >/dev/null 2>&1; then
sudo apt-get ${APT_FLAGS} update
sudo apt-get ${APT_FLAGS} install -y --no-install-recommends tmux
else
apt-get ${APT_FLAGS} update
apt-get ${APT_FLAGS} install -y --no-install-recommends tmux
fi
tmux -V
- name: Install Sapling
run: |
bash scripts/ci/install_sapling_ubuntu22.sh
- name: Verify Sapling CLI
run: sl --version
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Disable remote logging for CI (avoid hanging test process)
run: |
set -euo pipefail
if [ -f apps/cli/.env.integration-test ]; then
sed -i 's/^DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=.*/DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=/' apps/cli/.env.integration-test
sed -i 's/^DEBUG=.*/DEBUG=/' apps/cli/.env.integration-test
fi
- name: Run unit tests
run: yarn workspace @happier-dev/cli test:unit
- name: Run integration tests
run: yarn workspace @happier-dev/cli test:integration
stack:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_stack }}
name: Stack Tests (unit + integration)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run unit tests
run: yarn --cwd apps/stack test:unit
- name: Run integration tests
run: yarn --cwd apps/stack test:integration
release-contracts:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_release_contracts }}
name: Release Contracts
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run release contract tests
run: |
set -euo pipefail
yarn -s test:release:contracts
node scripts/pipeline/run.mjs release-sync-installers --check
installers-smoke-linux:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }}
name: Installer Smoke (Linux)
runs-on: ubuntu-latest
timeout-minutes: 20
env:
INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Verify published installers are synced
run: |
set -euo pipefail
node scripts/pipeline/run.mjs release-sync-installers --check
# Bootstrap-friendly: this repo can be green before the first GitHub release exists.
# Once cli-stable is published, we enforce that the published installer can install it.
- name: Detect CLI release tag
id: cli_tag
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="cli-stable"
installer="install.sh"
if [ "${INSTALLERS_CHANNEL}" = "preview" ]; then
tag="cli-preview"
installer="install-preview.sh"
fi
api="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}"
code="$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${api}" || true)"
if [ "${code}" = "200" ]; then
echo "tag_exists=true" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "installer=${installer}" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "tag_exists=false" >> "$GITHUB_OUTPUT"
echo "Skipping installer smoke: ${tag} release tag not found (HTTP ${code})."
- name: Run CLI installer from isolated temp location
if: steps.cli_tag.outputs.tag_exists == 'true'
env:
HAPPIER_GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
export HOME="$(mktemp -d)"
export HAPPIER_INSTALL_DIR="${HOME}/.happier"
export HAPPIER_BIN_DIR="${HOME}/.local/bin"
export HAPPIER_NONINTERACTIVE=1
export HAPPIER_NO_PATH_UPDATE=1
cp "apps/website/public/${{ steps.cli_tag.outputs.installer }}" /tmp/happier-install.sh
chmod +x /tmp/happier-install.sh
/tmp/happier-install.sh
test -x "${HAPPIER_BIN_DIR}/happier"
"${HAPPIER_BIN_DIR}/happier" --version
"${HAPPIER_BIN_DIR}/happier" --help >/dev/null
installers-smoke-macos:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }}
name: Installer Smoke (macOS)
runs-on: macos-latest
timeout-minutes: 25
env:
INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Verify published installers are synced
run: node scripts/pipeline/run.mjs release-sync-installers --check
- name: Detect CLI release tag
id: cli_tag
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="cli-stable"
installer="install.sh"
if [ "${INSTALLERS_CHANNEL}" = "preview" ]; then
tag="cli-preview"
installer="install-preview.sh"
fi
api="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}"
code="$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${api}" || true)"
if [ "${code}" = "200" ]; then
echo "tag_exists=true" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "installer=${installer}" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "tag_exists=false" >> "$GITHUB_OUTPUT"
echo "Skipping installer smoke: ${tag} release tag not found (HTTP ${code})."
- name: Run CLI installer from isolated temp location
if: steps.cli_tag.outputs.tag_exists == 'true'
env:
HAPPIER_GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
export HOME="$(mktemp -d)"
export HAPPIER_INSTALL_DIR="${HOME}/.happier"
export HAPPIER_BIN_DIR="${HOME}/.local/bin"
export HAPPIER_NONINTERACTIVE=1
export HAPPIER_NO_PATH_UPDATE=1
cp "apps/website/public/${{ steps.cli_tag.outputs.installer }}" /tmp/happier-install.sh
chmod +x /tmp/happier-install.sh
/tmp/happier-install.sh
test -x "${HAPPIER_BIN_DIR}/happier"
"${HAPPIER_BIN_DIR}/happier" --version
"${HAPPIER_BIN_DIR}/happier" --help >/dev/null
installers-smoke-windows:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }}
name: Installer Smoke (Windows)
runs-on: windows-latest
timeout-minutes: 25
env:
INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Verify published installers are synced
shell: pwsh
run: node scripts/pipeline/run.mjs release-sync-installers --check
- name: Detect CLI release tag
id: cli_tag
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
$ErrorActionPreference = "Continue"
$tag = "cli-stable"
$installer = "install.ps1"
if ($env:INSTALLERS_CHANNEL -eq "preview") {
$tag = "cli-preview"
$installer = "install-preview.ps1"
}
$api = "https://api.github.com/repos/$env:GITHUB_REPOSITORY/releases/tags/$tag"
$headers = @{
Authorization = "Bearer $env:GITHUB_TOKEN"
"X-GitHub-Api-Version" = "2022-11-28"
}
$exists = "false"
try {
Invoke-RestMethod -Uri $api -Headers $headers | Out-Null
$exists = "true"
} catch {
Write-Host "Skipping installer smoke: $tag release tag not found."
}
"tag_exists=$exists" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"installer=$installer" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Run CLI installer from isolated temp location
shell: pwsh
if: steps.cli_tag.outputs.tag_exists == 'true'
env:
HAPPIER_GITHUB_TOKEN: ${{ github.token }}
run: |
$ErrorActionPreference = "Stop"
$env:HAPPIER_NONINTERACTIVE = "1"
$env:HAPPIER_INSTALL_DIR = Join-Path $env:RUNNER_TEMP ".happier"
$env:HAPPIER_BIN_DIR = Join-Path $env:RUNNER_TEMP ".local\bin"
$scriptPath = Join-Path $env:RUNNER_TEMP "happier-install.ps1"
Copy-Item -Path ("apps/website/public/" + "${{ steps.cli_tag.outputs.installer }}") -Destination $scriptPath -Force
& $scriptPath
$binaryPath = Join-Path $env:HAPPIER_BIN_DIR "happier.exe"
if (-not (Test-Path $binaryPath)) {
throw "Installer did not create $binaryPath"
}
& $binaryPath --version
& $binaryPath --help | Out-Null
binary-smoke:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_binary_smoke }}
name: Binary Smoke (isolated cwd)
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run isolated binary smoke tests
run: |
set -euo pipefail
timeout --signal=KILL --kill-after=30s 25m node --test apps/stack/scripts/self_host_binary_smoke.integration.test.mjs
timeout --signal=KILL --kill-after=30s 45m node --test apps/stack/scripts/release_binary_smoke.integration.test.mjs
self-host-systemd-e2e:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_systemd }}
name: Self-Host E2E (Linux systemd)
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run real systemd self-host E2E
run: |
set -euo pipefail
node --test apps/stack/scripts/self_host_systemd.real.integration.test.mjs
self-host-launchd-e2e:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_launchd }}
name: Self-Host E2E (macOS launchd)
runs-on: macos-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run real launchd self-host E2E
run: |
set -euo pipefail
node --test apps/stack/scripts/self_host_launchd.real.integration.test.mjs
self-host-schtasks-e2e:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_schtasks }}
name: Self-Host E2E (Windows schtasks)
runs-on: windows-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run real schtasks self-host E2E
run: node --test apps/stack/scripts/self_host_schtasks.real.integration.test.mjs
self-host-daemon-e2e:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_daemon }}
name: Self-Host + Daemon E2E
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run real self-host + daemon E2E
run: node --test apps/stack/scripts/self_host_daemon.real.integration.test.mjs
typecheck:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_typecheck }}
name: Typecheck
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run governance self-tests
run: yarn test:wiring:self && yarn test:policy:self
- name: Run governance validators
run: yarn test:wiring && yarn test:policy
- name: Run governance inventory reports
run: yarn test:inventory && yarn test:migration:inventory
- name: Run typecheck
run: yarn typecheck
cli-daemon-e2e:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_cli_daemon_e2e }}
name: CLI + Server (light/sqlite) E2E
runs-on: ubuntu-latest
timeout-minutes: 120
env:
PORT: "3005"
HAPPIER_SERVER_URL: http://localhost:3005
HAPPIER_WEBAPP_URL: http://localhost:3005
HAPPIER_HOME_DIR: /tmp/happier-dev-test
DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING: "true"
HAPPIER_SERVER_LIGHT_DATA_DIR: /tmp/happier-server-light
HAPPIER_DB_PROVIDER: sqlite
HAPPY_DB_PROVIDER: sqlite
# Fall back to a deterministic non-secret for forks / untrusted PRs (no access to repo secrets).
HANDY_MASTER_SECRET: ${{ secrets.CI_MASTER_SECRET != '' && secrets.CI_MASTER_SECRET || 'happier-ci-master-secret-not-a-real-secret' }}
HAPPIER_NO_BROWSER_OPEN: "1"
TMPDIR: /tmp
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install system dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
APT_FLAGS="-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30"
if [ "${ACT:-}" = "true" ] || [ -d /var/run/act ]; then
. /etc/os-release || true
CODENAME="${VERSION_CODENAME:-noble}"
ARCH="$(dpkg --print-architecture)"
BASE_URL="http://ports.ubuntu.com/ubuntu-ports"
if [ "${ARCH}" = "amd64" ]; then BASE_URL="http://archive.ubuntu.com/ubuntu"; fi
if command -v sudo >/dev/null 2>&1; then
sudo rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true
echo "deb ${BASE_URL} ${CODENAME} main universe" | sudo tee /etc/apt/sources.list >/dev/null
else
rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true
echo "deb ${BASE_URL} ${CODENAME} main universe" > /etc/apt/sources.list
fi
fi
if command -v sudo >/dev/null 2>&1; then
sudo apt-get ${APT_FLAGS} update
sudo apt-get ${APT_FLAGS} install -y --no-install-recommends tmux
else
apt-get ${APT_FLAGS} update
apt-get ${APT_FLAGS} install -y --no-install-recommends tmux
fi
tmux -V
- name: Prepare light DB (SQLite) + apply migrations
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: |
set -euo pipefail
yarn install --frozen-lockfile --ignore-engines
yarn --cwd apps/server -s migrate:sqlite:deploy
- name: Build CLI
run: yarn workspace @happier-dev/cli build
- name: Install provider CLI stubs (CI only)
run: |
set -euo pipefail
trap 'exit 0' TERM INT
STUB_BIN_DIR="/tmp/ci-bin"
mkdir -p "${STUB_BIN_DIR}"
write_stub() {
local name="$1"
local version="$2"
cat > "${STUB_BIN_DIR}/${name}" <<EOF
#!/usr/bin/env bash
set -euo pipefail
case "\${1:-}" in
--version|-v|version)
echo "${version}"
exit 0
;;
--help|-h|help)
echo "${name} (ci stub)"
exit 0
;;
esac
# Some callers expect a long-running interactive process; do not exit.
if [ -t 0 ]; then
while true; do sleep 3600 & wait \$!; done
else
cat >/dev/null || true
while true; do sleep 3600 & wait \$!; done
fi
EOF
chmod +x "${STUB_BIN_DIR}/${name}"
}
write_stub "auggie" "0.0.0-ci-stub"
write_stub "claude" "0.0.0-ci-stub"
write_stub "codex" "0.0.0-ci-stub"
write_stub "gemini" "0.0.0-ci-stub"
write_stub "opencode" "0.0.0-ci-stub"
echo "${STUB_BIN_DIR}" >> "${GITHUB_PATH}"
export PATH="${STUB_BIN_DIR}:${PATH}"
command -v auggie && auggie --version
command -v claude && claude --version
command -v codex && codex --version
command -v gemini && gemini --version
command -v opencode && opencode --version
- name: Run daemon integration suite (with light server)
run: |
set -euo pipefail
mkdir -p "${HAPPIER_SERVER_LIGHT_DATA_DIR}"
nohup yarn --cwd apps/server start:light > "/tmp/happier-server-light.log" 2>&1 &
SERVER_PID="$(jobs -p | tail -n 1 || true)"
if [ -z "${SERVER_PID}" ]; then
echo "Failed to start server process"
tail -n 200 "/tmp/happier-server-light.log" || true
exit 1
fi
echo "${SERVER_PID}" > "/tmp/happier-server-light.pid"
cleanup() {
if [ -n "${SERVER_PID:-}" ]; then
kill "${SERVER_PID}" || true
fi
}
trap cleanup EXIT
for i in $(seq 1 60); do
if curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
# Require health to be stable (avoid flaking on a transient accept before the server is ready).
sleep 1
curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1
echo "Server is healthy (stable)"
break
fi
sleep 1
done
if ! curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
echo "Server failed to become healthy within timeout"
tail -n 200 "/tmp/happier-server-light.log" || true
exit 1
fi
# Create a real account + token via the normal `/v1/auth` flow (ed25519 signature),
# then write the credentials file expected by `.env.integration-test`.
node scripts/pipeline/run.mjs testing-create-auth-credentials
yarn --cwd apps/cli -s vitest run --config vitest.integration.config.ts src/daemon/daemon.integration.test.ts
- name: Dump server logs (on failure)
if: failure()
run: tail -n 300 "/tmp/happier-server-light.log" || true
- name: Dump daemon logs (on failure)
if: failure()
run: |
set -euo pipefail
ROOT="${HAPPIER_HOME_DIR:-$HOME/.happier-dev-test}"
echo "HAPPIER_HOME_DIR=${ROOT}"
echo "--- daemon.state.json candidates (redacted) ---"
find "$ROOT" -maxdepth 4 -type f -name 'daemon.state.json' -print | head -n 10 || true
find "$ROOT" -maxdepth 4 -type f -name 'daemon.state.json' -print | head -n 10 | while read -r f; do
node --input-type=module - <<'NODE' "$f" || true
import fs from "node:fs";
const file = process.argv[1];
const raw = fs.readFileSync(file, "utf8");
const data = JSON.parse(raw);
const redacted = {
pid: data?.pid,
httpPort: data?.httpPort,
startedAt: data?.startedAt,
startedWithCliVersion: data?.startedWithCliVersion,
daemonLogPath: data?.daemonLogPath,
};
console.log(`--- ${file} (redacted) ---`);
console.log(JSON.stringify(redacted, null, 2));
NODE
done
echo "--- logs (match scan) ---"
MATCH_PATTERN="SESSION_WEBHOOK_TIMEOUT|CHILD_EXITED_BEFORE_WEBHOOK|SPAWN_FAILED|Unauthorized|EADDRINUSE|ECONN|fetch failed|Request failed|HTTP 5[0-9]{2}|Deferred awaiter resolution|Current tracked sessions before webhook|Registered externally-started session"
find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print0 2>/dev/null \
| xargs -0 -r grep -nE "${MATCH_PATTERN}" \
| tail -n 400 || true
echo "--- logs (matched files tail) ---"
find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print0 2>/dev/null \
| xargs -0 -r grep -lE "${MATCH_PATTERN}" \
| head -n 20 | while read -r f; do
echo "=== tail: $f ==="
tail -n 200 "$f" || true
echo
done
echo "--- logs (tail) ---"
find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print 2>/dev/null \
| xargs -r ls -1t | head -n 16 | while read -r f; do
echo "=== tail: $f ==="
echo "--- error scan: $f ---"
# Avoid relying on ripgrep being installed; grep is always available.
grep -nE "SESSION_WEBHOOK_TIMEOUT|SPAWN_FAILED|Unauthorized|EADDRINUSE|ECONN|fetch failed|Request failed|HTTP 5[0-9]{2}" "$f" | tail -n 200 || true
echo
tail -n 200 "$f" || true
echo
done
e2e-core:
if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_e2e_core }}
name: Core E2E (fast, packages/tests, server-light, sqlite)
runs-on: ubuntu-22.04
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install Sapling
run: |
bash scripts/ci/install_sapling_ubuntu22.sh
- name: Verify Sapling CLI
run: sl --version
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run core e2e suite
env:
CI: "1"
HAPPIER_E2E_SAVE_ARTIFACTS: "0"
HAPPIER_E2E_DB_PROVIDER: sqlite
run: yarn test:e2e:core:fast
- name: Upload e2e artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: core-e2e-artifacts-fast-sqlite
path: .project/logs/e2e
e2e-core-slow:
if: ${{ github.event_name == 'workflow_call' && inputs.run_e2e_core_slow }}
name: Core E2E (slow, packages/tests, server-light, sqlite)
runs-on: ubuntu-22.04
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node (GitHub Actions)
if: env.ACT != 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Setup Node (act)
if: env.ACT == 'true'
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install Sapling
run: |
bash scripts/ci/install_sapling_ubuntu22.sh
- name: Verify Sapling CLI
run: sl --version
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run core e2e suite (slow)
env:
CI: "1"
HAPPIER_E2E_SAVE_ARTIFACTS: "0"
HAPPIER_E2E_DB_PROVIDER: sqlite
run: yarn test:e2e:core:slow
- name: Upload e2e artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: core-e2e-artifacts-slow-sqlite
path: .project/logs/e2e
release_actor_guard:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_providers }}
name: Release actor guard
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Authorize release actor
uses: ./.github/actions/release-actor-guard
with:
team_slug: release-admins
app_id: ${{ secrets.RELEASE_BOT_APP_ID }}
private_key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
providers:
needs: [release_actor_guard]
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_providers }}
name: Providers (contract matrix)
runs-on: ubuntu-latest
environment: providers-ci
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Install provider CLIs
env:
PRESET: ${{ inputs.providers_preset }}
OPENCODE_SPEC: ${{ inputs.providers_opencode }}
run: |
set -euo pipefail
need_claude=0
need_codex=0
need_opencode=0
case "$PRESET" in
all) need_claude=1; need_codex=1; need_opencode=1 ;;
claude) need_claude=1 ;;
codex) need_codex=1 ;;
opencode) need_opencode=1 ;;
*) echo "Unknown providers_preset: $PRESET" >&2; exit 2 ;;
esac
if [ "$need_claude" = "1" ]; then
npm install -g @anthropic-ai/claude-code
claude --version
fi
if [ "$need_codex" = "1" ]; then
npm install -g @openai/codex
codex --version
fi
if [ "$need_opencode" = "1" ]; then
# Avoid `curl ... | bash` by downloading a pinned, checksummed release artifact.
opencode_version="${OPENCODE_SPEC%%|*}"
opencode_sha256=""
if [ "$opencode_version" != "$OPENCODE_SPEC" ]; then
opencode_sha256="${OPENCODE_SPEC#*|}"
fi
version="${opencode_version#v}"
asset="opencode-linux-x64-baseline.tar.gz"
url="https://github.com/anomalyco/opencode/releases/download/v${version}/${asset}"
install_dir="$HOME/.opencode/bin"
mkdir -p "$install_dir"
tmp_dir="$(mktemp -d)"
curl -fsSL -o "$tmp_dir/$asset" "$url"
if [ -n "${opencode_sha256:-}" ]; then
echo "${opencode_sha256} $tmp_dir/$asset" | sha256sum -c -
fi
tar -xzf "$tmp_dir/$asset" -C "$tmp_dir"
mv -f "$tmp_dir/opencode" "$install_dir/opencode"
chmod 755 "$install_dir/opencode"
rm -rf "$tmp_dir"
echo "$install_dir" >> "$GITHUB_PATH"
export PATH="$install_dir:$PATH"
opencode --version
fi
- name: Validate provider auth env
env:
PRESET: ${{ inputs.providers_preset }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
set -euo pipefail
need_openai=0
need_anthropic=0
case "$PRESET" in
all) need_openai=1; need_anthropic=1 ;;
codex) need_openai=1 ;;
opencode) need_openai=1 ;;
claude) need_anthropic=1 ;;
*) echo "Unknown providers_preset: $PRESET" >&2; exit 2 ;;
esac
if [ "$need_openai" = "1" ] && [ -z "${OPENAI_API_KEY:-}" ] && [ -z "${CODEX_API_KEY:-}" ]; then
echo "Missing provider secrets: set OPENAI_API_KEY (or CODEX_API_KEY) to run preset=$PRESET" >&2
exit 1
fi
if [ "$need_anthropic" = "1" ] && [ -z "${ANTHROPIC_API_KEY:-}" ]; then
echo "Missing provider secrets: set ANTHROPIC_API_KEY to run preset=$PRESET" >&2
exit 1
fi
- name: Run provider contract suite
env:
CI: "1"
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
HAPPIER_E2E_PROVIDER_MODEL: ${{ fromJSON(inputs.providers_models).default }}
HAPPY_E2E_PROVIDER_MODEL: ${{ fromJSON(inputs.providers_models).default }}
HAPPIER_E2E_PROVIDER_MODELS: ${{ fromJSON(inputs.providers_models).overrides }}
HAPPY_E2E_PROVIDER_MODELS: ${{ fromJSON(inputs.providers_models).overrides }}
run: |
set -euo pipefail
flags=()
if [ "${{ fromJSON(inputs.providers_flags).strict_keys }}" = "true" ]; then flags+=("--strict-keys"); fi
if [ "${{ fromJSON(inputs.providers_flags).flake_retry }}" != "true" ]; then flags+=("--no-flake-retry"); fi
if [ "${{ fromJSON(inputs.providers_flags).update_baselines }}" = "true" ]; then flags+=("--update-baselines"); fi
yarn workspace @happier-dev/tests providers:run "${{ inputs.providers_preset }}" "${{ inputs.providers_tier }}" "${flags[@]}"
- name: Upload provider artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: providers-artifacts
path: .project/logs/e2e
stress:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_stress }}
name: Stress (repeat/chaos)
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: yarn
cache-dependency-path: yarn.lock
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@1.22.22 --activate
- name: Install dependencies
env:
YARN_PRODUCTION: "false"
npm_config_production: "false"
run: yarn install --frozen-lockfile --ignore-engines
- name: Run stress suite
env:
CI: "1"
HAPPIER_E2E_REPEAT: ${{ fromJSON(inputs.stress_config).repeat }}
HAPPIER_E2E_SEED: ${{ fromJSON(inputs.stress_config).seed }}
HAPPIER_E2E_FLAKE_RETRY: "1"
run: yarn test:stress
- name: Upload stress artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: stress-artifacts
path: .project/logs/e2e