From fdbc47fd8ea52dbfb548306ce7ae86ca9d0793ec Mon Sep 17 00:00:00 2001 From: Stephen Frederick Date: Wed, 11 Nov 2026 00:32:37 -0500 Subject: [PATCH 1/2] Run Windows and macOS CI only for platform-sensitive changes A new Changes job maps a pull request's paths to lanes with scripts/ci-changes.ts. Linux checks and unit-tests every run and builds and drives the app unless only docs changed. Windows and macOS run for the Electron main process, backend file and SQLite handling, the PDF worker, dependencies, the toolchain and CI. Packaging inputs package the unpacked app and run three packaged specs; installers are built only when electron-builder.json or CI changes. Pushes to main run every Linux lane with the whole packaged suite. The ci:all-os label or a manual run selects everything, and only manual runs upload installers. Smoke tests run in one worker per core with a 20-second default limit. Releases drop the type checks and the unpackaged Electron run, which the packaged run repeats. Checkouts skip the vendored repos/ tree, and the Bun cache moves to runner.temp so Windows keeps it on the D: drive. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 136 ++++++++++++++++----- .github/workflows/release.yml | 41 ++++--- docs/deployment.md | 51 +++++--- docs/development.md | 2 +- package.json | 2 +- scripts/ci-changes.ts | 211 +++++++++++++++++++++++++++++++++ scripts/test-packaged.ts | 13 +- tests/smoke/ci-changes.test.ts | 82 +++++++++++++ 8 files changed, 470 insertions(+), 68 deletions(-) create mode 100644 scripts/ci-changes.ts create mode 100644 tests/smoke/ci-changes.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 990642c9..41a2b378 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,96 @@ name: Verify desktop on: pull_request: + # `labeled` lets the ci:all-os label start a full run. Other labels skip every job. + types: [opened, synchronize, reopened, labeled] push: branches: [main] workflow_dispatch: permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + # A label other than ci:all-os gets its own group, so adding it cannot cancel a real run. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}${{ github.event.action == 'labeled' && github.event.label.name != 'ci:all-os' && '-label' || '' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + # scripts/ci-changes.ts maps the changed paths to lanes. Linux checks and unit-tests every run. + # Windows and macOS (1.7x and 10x a Linux minute) run only for platform-sensitive paths. The + # packaged app is built and tested only when packaging inputs change, and installers only when + # electron-builder.json or CI does. Pushes to main run every Linux lane. Release tags run all three + # operating systems before publishing. The ci:all-os label or a manual run selects everything. + # If the change cannot be listed, or this job fails, every lane runs. + changes: + name: Changes + if: github.event.action != 'labeled' || github.event.label.name == 'ci:all-os' + runs-on: ubuntu-26.04 + timeout-minutes: 5 + outputs: + app: ${{ steps.detect.outputs.app }} + cross_os: ${{ steps.detect.outputs.cross_os }} + packaged: ${{ steps.detect.outputs.packaged }} + installers: ${{ steps.detect.outputs.installers }} + packaged_suite: ${{ steps.detect.outputs.packaged_suite }} + steps: + # Depth 2 brings the pull request's base, the merge commit's first parent. + - uses: actions/checkout@v7 + with: + fetch-depth: 2 + sparse-checkout: | + /.bun-version + /scripts/ci-changes.ts + sparse-checkout-cone-mode: false + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: .bun-version + - name: Detect affected lanes + id: detect + env: + EVENT_NAME: ${{ github.event_name }} + ALL_OS: ${{ github.event.label.name == 'ci:all-os' || contains(github.event.pull_request.labels.*.name, 'ci:all-os') }} + run: | + set -uo pipefail + paths="$RUNNER_TEMP/changed-paths" + args=(everything) + if [[ "$EVENT_NAME" == push ]]; then + args=(main) + elif [[ "$EVENT_NAME" == pull_request && "$ALL_OS" != true ]]; then + # --no-renames lists a rename as its old and new path, so moving a file counts for both. + if git diff --name-only --no-renames HEAD^1 HEAD > "$paths"; then + args=(pull-request "$paths") + else + echo "Could not list the pull request's changes. Running every lane." + fi + fi + bun scripts/ci-changes.ts "${args[@]}" | tee -a "$GITHUB_OUTPUT" + exit "${PIPESTATUS[0]}" + verify: name: Build (${{ matrix.os }}) - timeout-minutes: 30 + needs: changes + if: ${{ !cancelled() && needs.changes.result != 'skipped' }} + timeout-minutes: 20 strategy: fail-fast: false - max-parallel: 3 matrix: - os: [ubuntu-26.04, windows-2025, macos-26] + os: ${{ fromJSON(needs.changes.outputs.cross_os == 'false' && '["ubuntu-26.04"]' || '["ubuntu-26.04","windows-2025","macos-26"]') }} runs-on: ${{ matrix.os }} + # An output that is missing because Changes failed runs its lane. + env: + APP: ${{ needs.changes.outputs.app != 'false' }} + PACKAGED: ${{ needs.changes.outputs.packaged != 'false' }} + INSTALLERS: ${{ needs.changes.outputs.installers != 'false' }} + PACKAGED_SUITE: ${{ needs.changes.outputs.packaged_suite || 'full' }} steps: + # repos/ holds vendored reference source that no check, build or test reads. - uses: actions/checkout@v7 + with: + sparse-checkout: | + /* + !/repos/ + sparse-checkout-cone-mode: false - uses: oven-sh/setup-bun@v2 with: bun-version-file: .bun-version - - name: Cache Bun dependencies - uses: actions/cache@v6 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} - restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- - - run: bun install --frozen-lockfile # Tests open SQLite with synchronous=FULL in the temporary directory, which defaults to C:. # The hosted runner's D: drive, which also holds the workspace, is faster for this I/O. - name: Use the D drive for temporary files on Windows @@ -41,29 +101,43 @@ jobs: New-Item -ItemType Directory -Force -Path $temp | Out-Null "TEMP=$temp" >> $env:GITHUB_ENV "TMP=$temp" >> $env:GITHUB_ENV - - run: bun run check - - if: runner.os != 'Windows' - run: bun run test - # Windows runner disk stalls can slow tests tenfold for tens of seconds. Tests with their - # own limit allow for that; this gives the rest 20 seconds instead of 5. - - if: runner.os == 'Windows' - run: bun run test --timeout 20000 - - run: bun run build + # The Bun cache lives under runner.temp so that on Windows it shares the workspace's D: drive: + # restoring tens of thousands of files to C: took about a minute, and Bun could not hard-link + # across drives into node_modules. + - name: Cache Bun dependencies + uses: actions/cache@v6 + with: + path: ${{ runner.temp }}/bun-cache + key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} + restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- + - run: bun install --frozen-lockfile + env: + BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache + # Type checks, lint and formatting give the same result on every OS. - if: runner.os == 'Linux' - run: xvfb-run -a bun run test:e2e - - if: runner.os != 'Linux' - run: bun run test:e2e - - name: Package desktop installers - run: bun --bun electron-builder --publish never + run: bun run check + # The test script runs files in one worker per core and gives tests without their own + # limit 20 seconds: Windows runner disk stalls and parallel workers both slow some past 5. + - run: bun run test + - if: env.APP == 'true' + run: bun run build + - name: Test the unpackaged desktop app + if: env.APP == 'true' + run: ${{ runner.os == 'Linux' && 'xvfb-run -a ' || '' }}bun run test:e2e + # The packaged tests need only the unpacked app. Installers are built when their own inputs + # change, on manual runs, and for releases. + - name: Package the desktop app + if: env.PACKAGED == 'true' + run: bun --bun electron-builder --publish never ${{ env.INSTALLERS != 'true' && '--dir' || '' }} env: CSC_IDENTITY_AUTO_DISCOVERY: "false" - - name: Test packaged application on Linux - if: runner.os == 'Linux' - run: xvfb-run -a bun run test:packaged - - name: Test packaged application on Windows and macOS - if: runner.os != 'Linux' - run: bun run test:packaged + - name: Test the packaged desktop app + if: env.PACKAGED == 'true' + run: >- + ${{ runner.os == 'Linux' && 'xvfb-run -a ' || '' }}bun run test:packaged + ${{ env.PACKAGED_SUITE == 'smoke' && '--smoke' || '' }} - name: Upload desktop installers + if: github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v7 with: name: desktop-${{ runner.os }}-${{ runner.arch }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63c46f7f..1e2fdbcc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,8 +14,20 @@ jobs: matrix: os: [ubuntu-26.04, windows-2025, macos-26] runs-on: ${{ matrix.os }} + # Pull requests run Windows and macOS only for platform-sensitive changes, and main runs Linux + # alone, so every release tag repeats the unit tests on all three before publishing. Each OS + # then builds its installers and drives the packaged app through every end-to-end spec. The + # unpackaged end-to-end run is left out: it runs the same specs, and the packaged run is the + # build users install. Type checks, lint and formatting give the same result on every OS and + # run on Linux for every pull request and push to main. steps: + # repos/ holds vendored reference source that no check, build or test reads. - uses: actions/checkout@v7 + with: + sparse-checkout: | + /* + !/repos/ + sparse-checkout-cone-mode: false - uses: oven-sh/setup-bun@v2 with: bun-version-file: .bun-version @@ -24,13 +36,6 @@ jobs: env: RELEASE_TAG: ${{ github.ref_name }} run: bun -e 'const p=require("./package.json"); if (process.env.RELEASE_TAG !== `v${p.version}`) process.exit(1)' - - name: Cache Bun dependencies - uses: actions/cache@v6 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} - restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- - - run: bun install --frozen-lockfile # Tests open SQLite with synchronous=FULL in the temporary directory, which defaults to C:. # The hosted runner's D: drive, which also holds the workspace, is faster for this I/O. - name: Use the D drive for temporary files on Windows @@ -41,18 +46,18 @@ jobs: New-Item -ItemType Directory -Force -Path $temp | Out-Null "TEMP=$temp" >> $env:GITHUB_ENV "TMP=$temp" >> $env:GITHUB_ENV - - run: bun run check - - if: runner.os != 'Windows' - run: bun run test - # Windows runner disk stalls can slow tests tenfold for tens of seconds. Tests with their - # own limit allow for that; this gives the rest 20 seconds instead of 5. - - if: runner.os == 'Windows' - run: bun run test --timeout 20000 + # Same cache as ci.yml: under runner.temp, Windows restores it to the workspace's D: drive. + - name: Cache Bun dependencies + uses: actions/cache@v6 + with: + path: ${{ runner.temp }}/bun-cache + key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} + restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- + - run: bun install --frozen-lockfile + env: + BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache + - run: bun run test - run: bun run build - - if: runner.os == 'Linux' - run: xvfb-run -a bun run test:e2e - - if: runner.os != 'Linux' - run: bun run test:e2e - run: bun --bun electron-builder --publish never env: CSC_IDENTITY_AUTO_DISCOVERY: "false" diff --git a/docs/deployment.md b/docs/deployment.md index 7725faeb..c41add70 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -5,20 +5,39 @@ Development uses a loopback Vite server. Release builds package a static SvelteK chooser operations. The main process authorizes the trusted top-level frame before connecting it, then forwards calls through the same shared schemas to Bun. -GitHub CI checks and packages Windows, macOS, and Linux builds concurrently on -pull requests, pushes to main, and manual runs of the Verify desktop workflow. -All three jobs can run at once, and a failure on one platform leaves the others -running. New commits cancel superseded CI runs. Each job uploads its installers -as a `desktop--` artifact, available on the workflow run for seven -days. CI builds Windows NSIS installers, macOS DMG and ZIP files, and Linux -AppImage and Debian packages using each runner's native architecture. Repository -text files use LF line endings on every OS so Windows passes the same format -checks. Windows runner disk stalls have slowed tests tenfold for tens of seconds, so -Windows CI keeps temporary files on the faster D: drive and gives smoke tests without -their own limit 20 seconds instead of 5. Tests with their own limit allow for these -stalls on every OS. - -Releases run from version tags matching package.json. They publish unsigned +The Verify desktop workflow runs on pull requests, pushes to main, and manual runs. +Its Changes job, `scripts/ci-changes.ts`, maps a pull request's changed paths to lanes: + +- Linux checks types, lint and formatting and runs the smoke tests on every run. Unless + the change touches only documentation or other paths no build reads, it also builds + the app and drives it unpackaged through the Electron flows. +- Windows and macOS repeat the smoke tests, build and Electron flows when the change + touches platform-sensitive code: the Electron main process, backend file, process and + SQLite handling, the PDF worker, dependencies, the toolchain, the desktop test + harness, or CI itself. +- When packaging inputs change, such as dependencies, package metadata or the packaged + resource layout, each of those runners also packages the unpacked app and runs + `bun run test:packaged --smoke` against it. Installers are built only when + `electron-builder.json` or CI changes. + +Pushes to main run every Linux lane, including every Electron flow against the +packaged app, but not Windows or macOS. Label a pull request `ci:all-os`, or run the +workflow manually, to run everything on all three operating systems. Manual runs also +upload each runner's installers as a `desktop--` artifact, kept for +seven days. CI builds Windows NSIS installers, macOS DMG and ZIP files, and Linux +AppImage and Debian packages using each runner's native architecture. New commits +cancel superseded pull request runs, and a failure on one platform leaves the others +running. + +Smoke tests run in one worker per core and give tests without their own limit 20 +seconds instead of Bun's 5. Windows runner disk stalls have slowed tests tenfold for +tens of seconds, and parallel workers slow CPU-heavy tests. Windows CI keeps temporary +files and the Bun cache on the faster D: drive. Repository text files use LF line +endings on every OS. + +Releases run from version tags matching package.json. Because Windows and macOS do +not run on main, each release runs the smoke tests on all three, builds the installers +and drives the packaged app through every Electron flow. It publishes unsigned installers and SHA256SUMS to GitHub Releases after all builds succeed. Code signing and notarization are not configured; the workflow does not require Apple or Windows developer credentials. Unsigned applications retain the platform's normal installation prompts. @@ -48,8 +67,8 @@ minified duplicates and the web viewer are not shipped. `app.asar` carries only the production packages the Electron bundle leaves external, Effect and electron-effect-rpc. The Modal SDK is a development dependency compiled into `app-backend`. No system Bun or Node is -required by the packaged app. CI launches the packaged application after building -its installers on each OS, verifying the compiled backend and renderer together. +required by the packaged app. Releases launch the packaged application on each OS +after building its installers, verifying the compiled backend and renderer together. Electron starts one backend and sends its schema-validated startup configuration over a private stdin pipe. The backend migrates the database, binds an ephemeral diff --git a/docs/development.md b/docs/development.md index a9912d49..c67976d4 100644 --- a/docs/development.md +++ b/docs/development.md @@ -16,7 +16,7 @@ by `svelte-check --tsgo` for Effect diagnostics. | `bun run test:e2e` | Drive the built Electron app through Playwright | | `bun run verify` | Run checks, smoke tests, builds, and Electron flows | | `bun run package:desktop` | Build installers without publishing | -| `bun run test:packaged` | Run the desktop flows against exactly one packaged executable | +| `bun run test:packaged` | Run desktop flows against one packaged app; `--smoke` runs the PR subset | | `bun run test:screenshots` | Capture the offline Electron fixture in `test-results/` | Local Electron tests run only on Linux, through `scripts/isolated-desktop.ts`. diff --git a/package.json b/package.json index 7b42df52..46efed2b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint": "bun --bun oxlint -c oxlint.config.ts --disable-nested-config src desktop backend scripts tests tools *.ts", "format": "bun fmt", "format:check": "bun fmt --check", - "test": "bun test ./tests/smoke", + "test": "bun test ./tests/smoke --parallel --no-isolate --timeout 20000", "test:sam": "bun scripts/test-sam.ts", "test:lint-rule-cli": "bun tools/oxlint/anti-slop/rules/require-readable-spacing-cli.test.ts", "reconcile:fixtures": "bun scripts/reconcile-fixtures.ts", diff --git a/scripts/ci-changes.ts b/scripts/ci-changes.ts new file mode 100644 index 00000000..4fdcedb7 --- /dev/null +++ b/scripts/ci-changes.ts @@ -0,0 +1,211 @@ +/** + * Decide which Verify desktop lanes a change needs. The Changes job in .github/workflows/ci.yml + * runs this before any other job, from a sparse checkout without dependencies, so it imports + * nothing. Linux checks and unit-tests every run; each lane below runs only when a changed path + * can affect it. The workflow passes `everything` when it cannot list the changed paths. + * + * Entries ending in `/` match a directory, `*.ext` matches a root-level file, and anything else + * matches one exact path. tests/smoke/ci-changes.test.ts fails when an entry matches no tracked + * file, so a rename cannot silently stop a lane from running. + */ + +/** Paths that no build, end-to-end or packaged test reads. Release changes run only at a tag. */ +export const inertPaths = [ + ".github/workflows/release.yml", + "docs/", + "experiments/", + "repos/", + "tools/", + ".project-kit/", + ".vscode/", + "*.md", + "LICENSE", + ".env.example", + ".gitignore", +] as const; + +/** + * Inputs of the distributable installers alone: their targets and settings. Icons and + * dependency updates, including Electron Builder's, package the unpacked app, which embeds the + * icons. Release tags build every installer. + */ +export const installerInputs = [ + "electron-builder.json", + ".github/workflows/ci.yml", + "scripts/ci-changes.ts", +] as const; + +/** + * Inputs of the packaged app: dependencies and app metadata, icons, resource paths, and the + * bundled backend with its PDF assets. + */ +export const packagingInputs = [ + "package.json", + "bun.lock", + ".bun-version", + "desktop/icons/", + "desktop/main.ts", + "desktop/backend.ts", + "scripts/build-icns.ts", + "scripts/build-icons.ts", + "scripts/pinned-bun.ts", + "scripts/prepare-pdf-assets.ts", + "scripts/test-packaged.ts", +] as const; + +/** + * Code whose behavior differs between Linux, Windows and macOS: the Electron main process, files, + * processes and SQLite databases on disk, native modules, the desktop test harness, and the + * toolchain. Tests of those modules count as well. + */ +export const platformSensitivePaths = [ + "desktop/", + "backend/main.ts", + "backend/database-files.ts", + "backend/rename.ts", + "backend/ownership.ts", + "backend/documents/", + "backend/cloud/cache.ts", + "backend/cloud/files.ts", + "backend/cloud/transfer.ts", + "backend/review/package-files.ts", + "backend/review/packages.ts", + "bunfig.toml", + "playwright.config.ts", + ".gitattributes", + "scripts/desktop-test-environment.ts", + "scripts/isolated-desktop.ts", + "tests/e2e/isolated-electron.ts", + "tests/e2e/rpc-boundary.ts", + "tests/e2e/rpc-control.ts", + "tests/e2e/workbench-fixture.ts", + "tests/smoke/backend-logging.test.ts", + "tests/smoke/backend-request-limits.test.ts", + "tests/smoke/backend.test.ts", + "tests/smoke/credential-files.test.ts", + "tests/smoke/credentials.test.ts", + "tests/smoke/data-directory.test.ts", + "tests/smoke/database-files.test.ts", + "tests/smoke/desktop-test-environment.test.ts", + "tests/smoke/documents.test.ts", + "tests/smoke/drop-imports.test.ts", + "tests/smoke/icons.test.ts", + "tests/smoke/package-files.test.ts", + "tests/smoke/pdf-assets.test.ts", + "tests/smoke/rename.test.ts", +] as const; + +/** What started the run, which fixes some lanes before any path is considered. */ +export type ChangeScope = "pull-request" | "main" | "everything"; + +/** Verify desktop lanes beyond the Linux checks and unit tests that every run performs. */ +export interface Lanes { + /** Build the app and drive it unpackaged through the end-to-end suite. */ + readonly app: boolean; + /** Repeat the unit tests, build and end-to-end suite on Windows and macOS. */ + readonly crossOs: boolean; + /** Package the app and test the packaged executable. */ + readonly packaged: boolean; + /** Build the distributable installers instead of only the unpacked app. */ + readonly installers: boolean; + /** Run every packaged spec instead of the packaged smoke subset. */ + readonly fullPackagedSuite: boolean; +} + +/** The lanes to run and the changed paths that selected them. */ +export interface LaneDecision { + readonly lanes: Lanes; + readonly reasons: ReadonlyArray; +} + +/** Whether a path entry from the lists above matches a repository-relative file path. */ +export function pathMatches(entry: string, file: string) { + if (entry.endsWith("/")) return file.startsWith(entry); + + if (entry.startsWith("*.")) return !file.includes("/") && file.endsWith(entry.slice(1)); + + return file === entry; +} + +const matching = (entries: ReadonlyArray, files: ReadonlyArray) => + files.filter((file) => entries.some((entry) => pathMatches(entry, file))); + +const allLanes: Lanes = { + app: true, + crossOs: true, + packaged: true, + installers: true, + fullPackagedSuite: true, +}; + +/** + * Choose the lanes for a run. A pull request runs what its changed paths can affect; each tier + * includes the ones below it. Pushes to main run every Linux lane and the full packaged suite as + * the backstop; Windows and macOS ran on the pull request when they were needed, and release tags + * run them again before publishing. + */ +export function decideLanes(scope: ChangeScope, files: ReadonlyArray): LaneDecision { + if (scope === "everything") return { lanes: allLanes, reasons: ["Running every lane."] }; + + if (scope === "main") + return { + lanes: { ...allLanes, crossOs: false, installers: false }, + reasons: ["A push to main runs every Linux lane."], + }; + + const installerPaths = matching(installerInputs, files); + const packagingPaths = matching(packagingInputs, files); + const platformPaths = matching(platformSensitivePaths, files); + const installers = installerPaths.length > 0; + const packaged = installers || packagingPaths.length > 0; + const crossOs = packaged || platformPaths.length > 0; + + const app = + crossOs || files.some((file) => !inertPaths.some((entry) => pathMatches(entry, file))); + + const reasons = [ + ["installers", installerPaths], + ["packaged app", packagingPaths], + ["Windows and macOS", platformPaths], + ] as const; + + return { + lanes: { app, crossOs, packaged, installers, fullPackagedSuite: false }, + reasons: reasons.flatMap(([lane, paths]) => + paths.length > 0 ? [`${lane}: ${paths.slice(0, 5).join(", ")}`] : [], + ), + }; +} + +/** Format lanes as GITHUB_OUTPUT lines. */ +export function laneOutputs(lanes: Lanes) { + return [ + `app=${lanes.app}`, + `cross_os=${lanes.crossOs}`, + `packaged=${lanes.packaged}`, + `installers=${lanes.installers}`, + `packaged_suite=${lanes.fullPackagedSuite ? "full" : "smoke"}`, + ].join("\n"); +} + +const scopes: ReadonlyArray = ["pull-request", "main", "everything"]; + +if (import.meta.main) { + const [scopeArgument, pathsFile] = process.argv.slice(2); + const scope = scopes.find((candidate) => candidate === scopeArgument); + + if (scope === undefined || (scope === "pull-request" && pathsFile === undefined)) { + console.error("Usage: bun scripts/ci-changes.ts pull-request | main | everything"); + process.exit(1); + } + + const files = + pathsFile === undefined + ? [] + : (await Bun.file(pathsFile).text()).split("\n").filter((line) => line !== ""); + + const decision = decideLanes(scope, files); + + for (const reason of decision.reasons) console.error(reason); + console.log(laneOutputs(decision.lanes)); +} diff --git a/scripts/test-packaged.ts b/scripts/test-packaged.ts index 0751157a..a77f7cf4 100644 --- a/scripts/test-packaged.ts +++ b/scripts/test-packaged.ts @@ -28,7 +28,18 @@ if (executable === undefined || executables.length !== 1) { process.exit(1); } -const child = Bun.spawn([process.execPath, "run", "test:e2e"], { +// The packaged app differs from `bun run test:e2e` only in how it is assembled: the backend, PDF +// worker, assets and migrations load from its resources, and credentials use the packaged app's +// own OS encryption and data directory. These specs cover startup, PDF import and rendering, +// encrypted credentials and a reopen. Pull request CI runs them; main and releases run every spec. +const smokeSpecs = ["e2e/credentials.spec.ts", "e2e/polish.spec.ts", "e2e/review.spec.ts"]; +const args = process.argv.slice(2); + +const filters = args.includes("--smoke") + ? [...smokeSpecs, ...args.filter((argument) => argument !== "--smoke")] + : args; + +const child = Bun.spawn([process.execPath, "run", "test:e2e", ...filters], { stdin: "inherit", stdout: "inherit", stderr: "inherit", diff --git a/tests/smoke/ci-changes.test.ts b/tests/smoke/ci-changes.test.ts new file mode 100644 index 00000000..7a8fcc69 --- /dev/null +++ b/tests/smoke/ci-changes.test.ts @@ -0,0 +1,82 @@ +import { expect, test } from "bun:test"; +import { + decideLanes, + inertPaths, + installerInputs, + laneOutputs, + packagingInputs, + pathMatches, + platformSensitivePaths, +} from "../../scripts/ci-changes.ts"; + +const pullRequest = (...files: Array) => decideLanes("pull-request", files).lanes; + +test("pull requests run only the lanes their changed paths can affect", () => { + expect(pullRequest("README.md", "docs/deployment.md")).toEqual({ + app: false, + crossOs: false, + packaged: false, + installers: false, + fullPackagedSuite: false, + }); + + expect(pullRequest("src/lib/review/Workbench.svelte", "tests/e2e/polish.spec.ts")).toEqual({ + app: true, + crossOs: false, + packaged: false, + installers: false, + fullPackagedSuite: false, + }); + + expect(pullRequest("backend/rename.ts")).toMatchObject({ crossOs: true, packaged: false }); + expect(pullRequest("bun.lock")).toMatchObject({ + crossOs: true, + packaged: true, + installers: false, + }); + + expect(pullRequest("electron-builder.json")).toEqual({ + app: true, + crossOs: true, + packaged: true, + installers: true, + fullPackagedSuite: false, + }); +}); + +test("pushes to main run every Linux lane and manual or unknown changes run everything", () => { + expect(decideLanes("main", []).lanes).toEqual({ + app: true, + crossOs: false, + packaged: true, + installers: false, + fullPackagedSuite: true, + }); + + expect(laneOutputs(decideLanes("everything", []).lanes)).toBe( + "app=true\ncross_os=true\npackaged=true\ninstallers=true\npackaged_suite=full", + ); + + expect(decideLanes("pull-request", ["desktop/main.ts"]).reasons).toEqual([ + "packaged app: desktop/main.ts", + "Windows and macOS: desktop/main.ts", + ]); +}); + +test("every lane entry still matches a tracked file", () => { + const tracked = Bun.spawnSync(["git", "ls-files"], { stdout: "pipe" }).stdout.toString(); + const files = tracked.split("\n").filter((line) => line !== ""); + + expect(files.length).toBeGreaterThan(0); + + for (const entry of [ + ...inertPaths, + ...installerInputs, + ...packagingInputs, + ...platformSensitivePaths, + ]) + expect({ entry, matched: files.some((file) => pathMatches(entry, file)) }).toEqual({ + entry, + matched: true, + }); +}); From b5096974ea9fee7dc9560e566561141ee761c9da Mon Sep 17 00:00:00 2001 From: Stephen Frederick Date: Wed, 11 Nov 2026 01:35:39 -0500 Subject: [PATCH 2/2] Run the Electron suite once against the packaged app and drop the Bun cache CI ran the Electron suite twice where it packaged: once unpackaged and once against the packaged app. Every lane that builds the app now packages the unpacked app with electron-builder --dir and runs the suite against it, so pushes to main no longer repeat it. Full installers stay limited to installer inputs, manual runs and release tags. The packaged smoke subset is gone: the whole suite against the packaged app took no longer than the unpackaged run did, so there is one code path. The Bun dependency cache is removed on every OS. A fresh install took 4-19 seconds, while restoring and saving the cache took up to two minutes on Windows. Windows keeps Bun's package store under runner.temp on D: so installs hard-link into node_modules. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 48 +++++++------------ .github/workflows/release.yml | 19 +++----- docs/deployment.md | 37 +++++++-------- docs/development.md | 2 +- scripts/ci-changes.ts | 86 ++++++++++++---------------------- scripts/test-packaged.ts | 13 +---- tests/smoke/ci-changes.test.ts | 35 +++----------- 7 files changed, 79 insertions(+), 161 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41a2b378..b188d933 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,13 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}${{ github.event.action == 'labeled' && github.event.label.name != 'ci:all-os' && '-label' || '' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - # scripts/ci-changes.ts maps the changed paths to lanes. Linux checks and unit-tests every run. - # Windows and macOS (1.7x and 10x a Linux minute) run only for platform-sensitive paths. The - # packaged app is built and tested only when packaging inputs change, and installers only when - # electron-builder.json or CI does. Pushes to main run every Linux lane. Release tags run all three - # operating systems before publishing. The ci:all-os label or a manual run selects everything. - # If the change cannot be listed, or this job fails, every lane runs. + # scripts/ci-changes.ts maps the changed paths to lanes. Linux checks and unit-tests every run, + # and unless only docs changed, it packages the app and runs the Electron suite against it. + # Windows and macOS (1.7x and 10x a Linux minute) run only for platform-sensitive paths. + # Installers are built only when electron-builder.json or CI changes. Pushes to main run the + # Linux lanes. Release tags run all three operating systems before publishing. The ci:all-os + # label or a manual run selects everything. If the change cannot be listed, or this job fails, + # every lane runs. changes: name: Changes if: github.event.action != 'labeled' || github.event.label.name == 'ci:all-os' @@ -27,9 +28,7 @@ jobs: outputs: app: ${{ steps.detect.outputs.app }} cross_os: ${{ steps.detect.outputs.cross_os }} - packaged: ${{ steps.detect.outputs.packaged }} installers: ${{ steps.detect.outputs.installers }} - packaged_suite: ${{ steps.detect.outputs.packaged_suite }} steps: # Depth 2 brings the pull request's base, the merge commit's first parent. - uses: actions/checkout@v7 @@ -77,9 +76,7 @@ jobs: # An output that is missing because Changes failed runs its lane. env: APP: ${{ needs.changes.outputs.app != 'false' }} - PACKAGED: ${{ needs.changes.outputs.packaged != 'false' }} INSTALLERS: ${{ needs.changes.outputs.installers != 'false' }} - PACKAGED_SUITE: ${{ needs.changes.outputs.packaged_suite || 'full' }} steps: # repos/ holds vendored reference source that no check, build or test reads. - uses: actions/checkout@v7 @@ -93,6 +90,7 @@ jobs: bun-version-file: .bun-version # Tests open SQLite with synchronous=FULL in the temporary directory, which defaults to C:. # The hosted runner's D: drive, which also holds the workspace, is faster for this I/O. + # Bun's package store moves there too, so installs hard-link into node_modules on one drive. - name: Use the D drive for temporary files on Windows if: runner.os == 'Windows' shell: pwsh @@ -101,18 +99,10 @@ jobs: New-Item -ItemType Directory -Force -Path $temp | Out-Null "TEMP=$temp" >> $env:GITHUB_ENV "TMP=$temp" >> $env:GITHUB_ENV - # The Bun cache lives under runner.temp so that on Windows it shares the workspace's D: drive: - # restoring tens of thousands of files to C: took about a minute, and Bun could not hard-link - # across drives into node_modules. - - name: Cache Bun dependencies - uses: actions/cache@v6 - with: - path: ${{ runner.temp }}/bun-cache - key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} - restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- + "BUN_INSTALL_CACHE_DIR=$(Join-Path $env:RUNNER_TEMP 'bun-cache')" >> $env:GITHUB_ENV + # No dependency cache: a fresh install took 4-19 seconds, less than restoring and saving the + # cache, which took up to two minutes on Windows. - run: bun install --frozen-lockfile - env: - BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache # Type checks, lint and formatting give the same result on every OS. - if: runner.os == 'Linux' run: bun run check @@ -121,21 +111,17 @@ jobs: - run: bun run test - if: env.APP == 'true' run: bun run build - - name: Test the unpackaged desktop app - if: env.APP == 'true' - run: ${{ runner.os == 'Linux' && 'xvfb-run -a ' || '' }}bun run test:e2e - # The packaged tests need only the unpacked app. Installers are built when their own inputs - # change, on manual runs, and for releases. + # The Electron suite runs once, against the packaged app. `--dir` builds the unpacked app in + # seconds. Installers are built when their own inputs change, on manual runs, and for + # releases. - name: Package the desktop app - if: env.PACKAGED == 'true' + if: env.APP == 'true' run: bun --bun electron-builder --publish never ${{ env.INSTALLERS != 'true' && '--dir' || '' }} env: CSC_IDENTITY_AUTO_DISCOVERY: "false" - name: Test the packaged desktop app - if: env.PACKAGED == 'true' - run: >- - ${{ runner.os == 'Linux' && 'xvfb-run -a ' || '' }}bun run test:packaged - ${{ env.PACKAGED_SUITE == 'smoke' && '--smoke' || '' }} + if: env.APP == 'true' + run: ${{ runner.os == 'Linux' && 'xvfb-run -a ' || '' }}bun run test:packaged - name: Upload desktop installers if: github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e2fdbcc..b97f26e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,10 +16,9 @@ jobs: runs-on: ${{ matrix.os }} # Pull requests run Windows and macOS only for platform-sensitive changes, and main runs Linux # alone, so every release tag repeats the unit tests on all three before publishing. Each OS - # then builds its installers and drives the packaged app through every end-to-end spec. The - # unpackaged end-to-end run is left out: it runs the same specs, and the packaged run is the - # build users install. Type checks, lint and formatting give the same result on every OS and - # run on Linux for every pull request and push to main. + # then builds its installers and drives the packaged app through the Electron suite, as + # ci.yml does. Type checks, lint and formatting give the same result on every OS and run on + # Linux for every pull request and push to main. steps: # repos/ holds vendored reference source that no check, build or test reads. - uses: actions/checkout@v7 @@ -38,6 +37,7 @@ jobs: run: bun -e 'const p=require("./package.json"); if (process.env.RELEASE_TAG !== `v${p.version}`) process.exit(1)' # Tests open SQLite with synchronous=FULL in the temporary directory, which defaults to C:. # The hosted runner's D: drive, which also holds the workspace, is faster for this I/O. + # Bun's package store moves there too, so installs hard-link into node_modules on one drive. - name: Use the D drive for temporary files on Windows if: runner.os == 'Windows' shell: pwsh @@ -46,16 +46,9 @@ jobs: New-Item -ItemType Directory -Force -Path $temp | Out-Null "TEMP=$temp" >> $env:GITHUB_ENV "TMP=$temp" >> $env:GITHUB_ENV - # Same cache as ci.yml: under runner.temp, Windows restores it to the workspace's D: drive. - - name: Cache Bun dependencies - uses: actions/cache@v6 - with: - path: ${{ runner.temp }}/bun-cache - key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('.bun-version', 'bun.lock') }} - restore-keys: ${{ runner.os }}-${{ runner.arch }}-bun- + "BUN_INSTALL_CACHE_DIR=$(Join-Path $env:RUNNER_TEMP 'bun-cache')" >> $env:GITHUB_ENV + # No dependency cache, as in ci.yml: a fresh install is faster than restoring one. - run: bun install --frozen-lockfile - env: - BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache - run: bun run test - run: bun run build - run: bun --bun electron-builder --publish never diff --git a/docs/deployment.md b/docs/deployment.md index c41add70..f3bc9cfc 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -10,20 +10,18 @@ Its Changes job, `scripts/ci-changes.ts`, maps a pull request's changed paths to - Linux checks types, lint and formatting and runs the smoke tests on every run. Unless the change touches only documentation or other paths no build reads, it also builds - the app and drives it unpackaged through the Electron flows. -- Windows and macOS repeat the smoke tests, build and Electron flows when the change - touches platform-sensitive code: the Electron main process, backend file, process and - SQLite handling, the PDF worker, dependencies, the toolchain, the desktop test - harness, or CI itself. -- When packaging inputs change, such as dependencies, package metadata or the packaged - resource layout, each of those runners also packages the unpacked app and runs - `bun run test:packaged --smoke` against it. Installers are built only when - `electron-builder.json` or CI changes. - -Pushes to main run every Linux lane, including every Electron flow against the -packaged app, but not Windows or macOS. Label a pull request `ci:all-os`, or run the -workflow manually, to run everything on all three operating systems. Manual runs also -upload each runner's installers as a `desktop--` artifact, kept for + the app, packages the unpacked app with `electron-builder --dir`, and drives that + packaged app through the Electron flows with `bun run test:packaged`. CI does not run + the unpackaged `bun run test:e2e`; local runs still do. +- Windows and macOS repeat the smoke tests, packaging and Electron flows when the + change touches platform-sensitive code: the Electron main process, backend file, + process and SQLite handling, the PDF worker, dependencies, packaging, the toolchain, + the desktop test harness, or CI itself. +- Installers are built only when `electron-builder.json` or CI changes. + +Pushes to main run the Linux lanes but not Windows or macOS. Label a pull request +`ci:all-os`, or run the workflow manually, to run everything on all three operating +systems. Manual runs also upload each runner's installers as a `desktop--` artifact, kept for seven days. CI builds Windows NSIS installers, macOS DMG and ZIP files, and Linux AppImage and Debian packages using each runner's native architecture. New commits cancel superseded pull request runs, and a failure on one platform leaves the others @@ -31,9 +29,10 @@ running. Smoke tests run in one worker per core and give tests without their own limit 20 seconds instead of Bun's 5. Windows runner disk stalls have slowed tests tenfold for -tens of seconds, and parallel workers slow CPU-heavy tests. Windows CI keeps temporary -files and the Bun cache on the faster D: drive. Repository text files use LF line -endings on every OS. +tens of seconds, and parallel workers slow CPU-heavy tests. CI keeps no dependency +cache, because a fresh install is faster than restoring one. Windows CI keeps +temporary files and Bun's package store on the faster D: drive. Repository text files +use LF line endings on every OS. Releases run from version tags matching package.json. Because Windows and macOS do not run on main, each release runs the smoke tests on all three, builds the installers @@ -67,8 +66,8 @@ minified duplicates and the web viewer are not shipped. `app.asar` carries only the production packages the Electron bundle leaves external, Effect and electron-effect-rpc. The Modal SDK is a development dependency compiled into `app-backend`. No system Bun or Node is -required by the packaged app. Releases launch the packaged application on each OS -after building its installers, verifying the compiled backend and renderer together. +required by the packaged app. CI and releases launch the packaged application, +verifying the compiled backend and renderer together. Electron starts one backend and sends its schema-validated startup configuration over a private stdin pipe. The backend migrates the database, binds an ephemeral diff --git a/docs/development.md b/docs/development.md index c67976d4..a9912d49 100644 --- a/docs/development.md +++ b/docs/development.md @@ -16,7 +16,7 @@ by `svelte-check --tsgo` for Effect diagnostics. | `bun run test:e2e` | Drive the built Electron app through Playwright | | `bun run verify` | Run checks, smoke tests, builds, and Electron flows | | `bun run package:desktop` | Build installers without publishing | -| `bun run test:packaged` | Run desktop flows against one packaged app; `--smoke` runs the PR subset | +| `bun run test:packaged` | Run the desktop flows against exactly one packaged executable | | `bun run test:screenshots` | Capture the offline Electron fixture in `test-results/` | Local Electron tests run only on Linux, through `scripts/isolated-desktop.ts`. diff --git a/scripts/ci-changes.ts b/scripts/ci-changes.ts index 4fdcedb7..452a6932 100644 --- a/scripts/ci-changes.ts +++ b/scripts/ci-changes.ts @@ -9,7 +9,7 @@ * file, so a rename cannot silently stop a lane from running. */ -/** Paths that no build, end-to-end or packaged test reads. Release changes run only at a tag. */ +/** Paths that no build or end-to-end test reads. Release changes run only at a tag. */ export const inertPaths = [ ".github/workflows/release.yml", "docs/", @@ -25,9 +25,9 @@ export const inertPaths = [ ] as const; /** - * Inputs of the distributable installers alone: their targets and settings. Icons and - * dependency updates, including Electron Builder's, package the unpacked app, which embeds the - * icons. Release tags build every installer. + * Inputs of the distributable installers alone: their targets and settings. Every other run + * packages only the unpacked app, which already embeds the icons and dependencies. Release tags + * build every installer. */ export const installerInputs = [ "electron-builder.json", @@ -36,27 +36,10 @@ export const installerInputs = [ ] as const; /** - * Inputs of the packaged app: dependencies and app metadata, icons, resource paths, and the - * bundled backend with its PDF assets. - */ -export const packagingInputs = [ - "package.json", - "bun.lock", - ".bun-version", - "desktop/icons/", - "desktop/main.ts", - "desktop/backend.ts", - "scripts/build-icns.ts", - "scripts/build-icons.ts", - "scripts/pinned-bun.ts", - "scripts/prepare-pdf-assets.ts", - "scripts/test-packaged.ts", -] as const; - -/** - * Code whose behavior differs between Linux, Windows and macOS: the Electron main process, files, - * processes and SQLite databases on disk, native modules, the desktop test harness, and the - * toolchain. Tests of those modules count as well. + * Code whose behavior differs between Linux, Windows and macOS: the Electron main process with + * its packaged resource paths, files, processes and SQLite databases on disk, the PDF worker and + * its native canvas, dependencies and the toolchain, packaging, and the desktop test harness. + * Tests of those modules count as well. */ export const platformSensitivePaths = [ "desktop/", @@ -70,11 +53,19 @@ export const platformSensitivePaths = [ "backend/cloud/transfer.ts", "backend/review/package-files.ts", "backend/review/packages.ts", + "package.json", + "bun.lock", + ".bun-version", "bunfig.toml", "playwright.config.ts", ".gitattributes", + "scripts/build-icns.ts", + "scripts/build-icons.ts", "scripts/desktop-test-environment.ts", "scripts/isolated-desktop.ts", + "scripts/pinned-bun.ts", + "scripts/prepare-pdf-assets.ts", + "scripts/test-packaged.ts", "tests/e2e/isolated-electron.ts", "tests/e2e/rpc-boundary.ts", "tests/e2e/rpc-control.ts", @@ -100,16 +91,12 @@ export type ChangeScope = "pull-request" | "main" | "everything"; /** Verify desktop lanes beyond the Linux checks and unit tests that every run performs. */ export interface Lanes { - /** Build the app and drive it unpackaged through the end-to-end suite. */ + /** Build and package the app, then drive the packaged app through the end-to-end suite. */ readonly app: boolean; - /** Repeat the unit tests, build and end-to-end suite on Windows and macOS. */ + /** Repeat the unit tests, packaging and end-to-end suite on Windows and macOS. */ readonly crossOs: boolean; - /** Package the app and test the packaged executable. */ - readonly packaged: boolean; /** Build the distributable installers instead of only the unpacked app. */ readonly installers: boolean; - /** Run every packaged spec instead of the packaged smoke subset. */ - readonly fullPackagedSuite: boolean; } /** The lanes to run and the changed paths that selected them. */ @@ -130,47 +117,38 @@ export function pathMatches(entry: string, file: string) { const matching = (entries: ReadonlyArray, files: ReadonlyArray) => files.filter((file) => entries.some((entry) => pathMatches(entry, file))); -const allLanes: Lanes = { - app: true, - crossOs: true, - packaged: true, - installers: true, - fullPackagedSuite: true, -}; +const allLanes: Lanes = { app: true, crossOs: true, installers: true }; /** - * Choose the lanes for a run. A pull request runs what its changed paths can affect; each tier - * includes the ones below it. Pushes to main run every Linux lane and the full packaged suite as - * the backstop; Windows and macOS ran on the pull request when they were needed, and release tags - * run them again before publishing. + * Choose the lanes for a run. A pull request runs what its changed paths can affect; each lane + * includes the ones below it. Pushes to main run the Linux lanes as the backstop; Windows and + * macOS ran on the pull request when they were needed, and release tags run them again before + * publishing. */ export function decideLanes(scope: ChangeScope, files: ReadonlyArray): LaneDecision { if (scope === "everything") return { lanes: allLanes, reasons: ["Running every lane."] }; if (scope === "main") return { - lanes: { ...allLanes, crossOs: false, installers: false }, - reasons: ["A push to main runs every Linux lane."], + lanes: { app: true, crossOs: false, installers: false }, + reasons: ["A push to main runs the Linux lanes."], }; const installerPaths = matching(installerInputs, files); - const packagingPaths = matching(packagingInputs, files); const platformPaths = matching(platformSensitivePaths, files); const installers = installerPaths.length > 0; - const packaged = installers || packagingPaths.length > 0; - const crossOs = packaged || platformPaths.length > 0; + const crossOs = installers || platformPaths.length > 0; const app = crossOs || files.some((file) => !inertPaths.some((entry) => pathMatches(entry, file))); const reasons = [ ["installers", installerPaths], - ["packaged app", packagingPaths], ["Windows and macOS", platformPaths], ] as const; return { - lanes: { app, crossOs, packaged, installers, fullPackagedSuite: false }, + lanes: { app, crossOs, installers }, reasons: reasons.flatMap(([lane, paths]) => paths.length > 0 ? [`${lane}: ${paths.slice(0, 5).join(", ")}`] : [], ), @@ -179,13 +157,9 @@ export function decideLanes(scope: ChangeScope, files: ReadonlyArray): L /** Format lanes as GITHUB_OUTPUT lines. */ export function laneOutputs(lanes: Lanes) { - return [ - `app=${lanes.app}`, - `cross_os=${lanes.crossOs}`, - `packaged=${lanes.packaged}`, - `installers=${lanes.installers}`, - `packaged_suite=${lanes.fullPackagedSuite ? "full" : "smoke"}`, - ].join("\n"); + return [`app=${lanes.app}`, `cross_os=${lanes.crossOs}`, `installers=${lanes.installers}`].join( + "\n", + ); } const scopes: ReadonlyArray = ["pull-request", "main", "everything"]; diff --git a/scripts/test-packaged.ts b/scripts/test-packaged.ts index a77f7cf4..0751157a 100644 --- a/scripts/test-packaged.ts +++ b/scripts/test-packaged.ts @@ -28,18 +28,7 @@ if (executable === undefined || executables.length !== 1) { process.exit(1); } -// The packaged app differs from `bun run test:e2e` only in how it is assembled: the backend, PDF -// worker, assets and migrations load from its resources, and credentials use the packaged app's -// own OS encryption and data directory. These specs cover startup, PDF import and rendering, -// encrypted credentials and a reopen. Pull request CI runs them; main and releases run every spec. -const smokeSpecs = ["e2e/credentials.spec.ts", "e2e/polish.spec.ts", "e2e/review.spec.ts"]; -const args = process.argv.slice(2); - -const filters = args.includes("--smoke") - ? [...smokeSpecs, ...args.filter((argument) => argument !== "--smoke")] - : args; - -const child = Bun.spawn([process.execPath, "run", "test:e2e", ...filters], { +const child = Bun.spawn([process.execPath, "run", "test:e2e"], { stdin: "inherit", stdout: "inherit", stderr: "inherit", diff --git a/tests/smoke/ci-changes.test.ts b/tests/smoke/ci-changes.test.ts index 7a8fcc69..3401eed1 100644 --- a/tests/smoke/ci-changes.test.ts +++ b/tests/smoke/ci-changes.test.ts @@ -4,7 +4,6 @@ import { inertPaths, installerInputs, laneOutputs, - packagingInputs, pathMatches, platformSensitivePaths, } from "../../scripts/ci-changes.ts"; @@ -15,50 +14,33 @@ test("pull requests run only the lanes their changed paths can affect", () => { expect(pullRequest("README.md", "docs/deployment.md")).toEqual({ app: false, crossOs: false, - packaged: false, installers: false, - fullPackagedSuite: false, }); expect(pullRequest("src/lib/review/Workbench.svelte", "tests/e2e/polish.spec.ts")).toEqual({ app: true, crossOs: false, - packaged: false, installers: false, - fullPackagedSuite: false, }); - expect(pullRequest("backend/rename.ts")).toMatchObject({ crossOs: true, packaged: false }); - expect(pullRequest("bun.lock")).toMatchObject({ - crossOs: true, - packaged: true, - installers: false, - }); + expect(pullRequest("backend/rename.ts")).toEqual({ app: true, crossOs: true, installers: false }); + expect(pullRequest("bun.lock")).toEqual({ app: true, crossOs: true, installers: false }); expect(pullRequest("electron-builder.json")).toEqual({ app: true, crossOs: true, - packaged: true, installers: true, - fullPackagedSuite: false, }); }); -test("pushes to main run every Linux lane and manual or unknown changes run everything", () => { - expect(decideLanes("main", []).lanes).toEqual({ - app: true, - crossOs: false, - packaged: true, - installers: false, - fullPackagedSuite: true, - }); +test("pushes to main run the Linux lanes and manual or unknown changes run everything", () => { + expect(decideLanes("main", []).lanes).toEqual({ app: true, crossOs: false, installers: false }); expect(laneOutputs(decideLanes("everything", []).lanes)).toBe( - "app=true\ncross_os=true\npackaged=true\ninstallers=true\npackaged_suite=full", + "app=true\ncross_os=true\ninstallers=true", ); expect(decideLanes("pull-request", ["desktop/main.ts"]).reasons).toEqual([ - "packaged app: desktop/main.ts", "Windows and macOS: desktop/main.ts", ]); }); @@ -69,12 +51,7 @@ test("every lane entry still matches a tracked file", () => { expect(files.length).toBeGreaterThan(0); - for (const entry of [ - ...inertPaths, - ...installerInputs, - ...packagingInputs, - ...platformSensitivePaths, - ]) + for (const entry of [...inertPaths, ...installerInputs, ...platformSensitivePaths]) expect({ entry, matched: files.some((file) => pathMatches(entry, file)) }).toEqual({ entry, matched: true,