diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b8f526..0a03a1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,6 +75,10 @@ jobs: working-directory: apps/electron run: bun run test:package-smoke + - name: Fresh-install E2E + working-directory: apps/electron + run: bun run test:fresh-install + - name: Verify checksums working-directory: dist/release run: shasum -a 256 -c SHA256SUMS.txt diff --git a/apps/electron/e2e/fresh-install.mjs b/apps/electron/e2e/fresh-install.mjs index 4c0f113..e53b09d 100644 --- a/apps/electron/e2e/fresh-install.mjs +++ b/apps/electron/e2e/fresh-install.mjs @@ -14,7 +14,7 @@ // (no provider connected → wizard gate opens). import { execSync, spawn } from 'node:child_process'; -import { existsSync, rmSync, mkdirSync } from 'node:fs'; +import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; @@ -25,13 +25,24 @@ const { chromium } = require('playwright-core'); const here = dirname(fileURLToPath(import.meta.url)); const appRoot = join(here, '..'); const repoRoot = join(here, '../../..'); -const packagedBinary = join( - repoRoot, - 'dist/electron/mac-arm64/Folio.app/Contents/MacOS/Folio' -); +const distDir = join(repoRoot, 'dist', 'electron'); const CDP_PORT = 9347; const userDataDir = join(appRoot, 'e2e/.user-data-fresh'); +function findAppBinary() { + if (!existsSync(distDir)) { + throw new Error(`No dist output at ${distDir}. Run \`bun run package\` first.`); + } + for (const entry of readdirSync(distDir)) { + if (!entry.startsWith('mac')) continue; + const candidate = join(distDir, entry, 'Folio.app', 'Contents', 'MacOS', 'Folio'); + if (existsSync(candidate) && statSync(candidate).isFile()) { + return candidate; + } + } + throw new Error(`No Folio.app binary found under ${distDir}/mac*.`); +} + // FINAGENT_E2E_KEEP_OPEN=1 — debugging only, NEVER in automated runs: leave // the app running when the harness finishes and print where it is, instead of // killing it. Automated runs/CI rely on the harness cleaning up its port. @@ -107,11 +118,7 @@ async function waitForCdp(timeoutMs) { } async function main() { - if (!existsSync(packagedBinary)) { - console.error(`Packaged app not found: ${packagedBinary}`); - console.error('Run `bun run package` first.'); - process.exit(1); - } + const packagedBinary = findAppBinary(); try { execSync("pkill -f 'remote-debugging-port=9347' || true", { stdio: 'ignore' }); } catch { diff --git a/docs/v5-blocker-sweep.md b/docs/v5-blocker-sweep.md index b24e137..2adfab8 100644 --- a/docs/v5-blocker-sweep.md +++ b/docs/v5-blocker-sweep.md @@ -75,10 +75,10 @@ payload, or export surface changed. bounded by the health probe (health.ts:238, 250: 5s + 10s CLI timeouts, 15s cache TTL health.ts:217), so worst case the wizard appears after ~15s on a hung CLI. Not stuck, but add a renderer-side timeout (e.g. 4s) if first-launch polish matters in V5. -2. **CI skips fresh-install gate (info):** release-check.mjs includes it (gate 7) but - release.yml does not run `test:fresh-install`; also "interaction audit"/"secret scan" - from docs are not mechanically enforced. Consider wiring the full `release:check` step - (or the missing gates) into CI. +2. **CI gate coverage:** `release.yml` now runs `test:fresh-install` after packaged smoke, + matching the release gate defined in `release-check.mjs`. The "interaction audit" and + "secret scan" listed in the quality-gate documentation remain informational and are not + yet mechanically enforced by CI. ## 6. Pre-existing failures (NOT from this sweep)