/** * Global setup for playwright.integration-electron.config.ts - a narrower * variant of ./global-setup.ts. * * The Electron suite (11-electron-notification.spec.ts) boots its OWN * standalone Next.js server via electron/main.ts, so unlike the main * integration config it never talks to the docker-compose `webmail` * container on :3000 at all - only to `stalwart` (JMAP + SMTP). Bringing up * `webmail` too would be pointless work, and on a host where something else * already owns port 3000 (this repo doesn't own that port - any other * project's dev server can be sitting on it) it would fail outright for a * container this suite never uses. `docker compose up ` scopes the * bring-up to just `stalwart`. * * Set IT_NO_DOCKER=1 to skip container management entirely (useful when the * stack is already running). */ import { execFileSync } from 'node:child_process'; import { existsSync, copyFileSync } from 'node:fs'; import path from 'node:path'; import { JMAP_URL, ACCOUNTS, ACCOUNT_PASSWORD } from './helpers/config'; const INTEGRATION_DIR = path.resolve(__dirname, '..'); const COMPOSE_FILE = path.join(INTEGRATION_DIR, 'docker-compose.yml'); const ENV_FILE = path.join(INTEGRATION_DIR, '.env'); const STALWART_CLI_BIN = path.join(INTEGRATION_DIR, 'stalwart', 'stalwart-cli'); function run(cmd: string, args: string[]): void { execFileSync(cmd, args, { cwd: INTEGRATION_DIR, stdio: 'inherit' }); } async function waitForStalwart(timeoutMs = 240000): Promise { const url = `${JMAP_URL}/jmap/session`; const deadline = Date.now() + timeoutMs; const auth = 'Basic ' + Buffer.from(`${ACCOUNTS.alice.email}:${ACCOUNT_PASSWORD}`).toString('base64'); for (;;) { try { const res = await fetch(url, { headers: { Authorization: auth } }); if (res.ok) return; } catch { /* not up yet */ } if (Date.now() > deadline) throw new Error(`Timed out waiting for Stalwart JMAP at ${url}`); await new Promise((r) => setTimeout(r, 2000)); } } export default async function globalSetup(): Promise { if (process.env.IT_NO_DOCKER === '1') { console.log('[global-setup-electron] IT_NO_DOCKER=1 - skipping docker compose management'); } else { // stalwart/prepare-stalwart-cli.sh fetches a LINUX binary (it's COPYed // into the Stalwart container by integration/stalwart/Dockerfile - never // meant to run on the host at all) but ends by executing it as its own // sanity check, which only works when the host itself is Linux. On a // macOS host that self-check fails outright ("cannot execute binary // file") even though the download+extract already succeeded and the // file the Dockerfile needs is perfectly fine on disk. Skipping the // script once the binary already exists sidesteps that host/target // mismatch without touching the shared script (used by the main // integration config too, on hosts where it does work). if (existsSync(STALWART_CLI_BIN)) { console.log('[global-setup-electron] stalwart-cli already present, skipping fetch'); } else { console.log('[global-setup-electron] fetching stalwart-cli'); run('bash', [path.join(INTEGRATION_DIR, 'stalwart', 'prepare-stalwart-cli.sh')]); } if (!existsSync(ENV_FILE)) { console.log('[global-setup-electron] creating integration/.env from .env.example'); copyFileSync(path.join(INTEGRATION_DIR, '.env.example'), ENV_FILE); } console.log('[global-setup-electron] docker compose up -d --build --wait stalwart'); run('docker', [ 'compose', '-f', COMPOSE_FILE, '--env-file', ENV_FILE, 'up', '-d', '--build', '--wait', '--wait-timeout', '300', 'stalwart', ]); } console.log('[global-setup-electron] waiting for Stalwart JMAP'); await waitForStalwart(); console.log('[global-setup-electron] stack ready'); }