Skip to content

ci: shard e2e across 4 runners and cache Playwright browsers + Nx - #1924

Merged
paustint merged 2 commits into
mainfrom
chore/ci/improve-jobs
Aug 10, 2026
Merged

ci: shard e2e across 4 runners and cache Playwright browsers + Nx#1924
paustint merged 2 commits into
mainfrom
chore/ci/improve-jobs

Conversation

@paustint

@paustint paustint commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

The e2e job was ~17m30s wall clock and gates nothing else, making it the
critical path for every PR that touches api or jetstream. 12m40s of that was
the test run itself, executing serially on a single worker.

E2E sharding

  • Split the suite across a 4-way matrix. Playwright distributes it evenly
    (33/33/32/32) and runs the setup project in every shard, so each gets its
    own authenticated storage state.
  • Sharding rather than raising workers is deliberate. Each shard is its own
    job with its own postgres service, app server and seeded users, so the only
    resource shared between shards is the Salesforce org. Raising workers inside
    one job would have them contend for the same database and the same logged-in
    user.
  • No needs: on build-and-test. Downloading a prebuilt dist would avoid four
    rebuilds but serialise e2e behind an 8m job; letting each shard build its own
    keeps e2e parallel, which is what actually shortens the PR.
  • maxFailures drops to 1 because the budget is now per shard — 2 each would
    have tolerated 8 failures across the run.
  • Shards emit blob reports that merge-e2e-reports stitches into the single HTML
    report the old job produced. That job tolerates an empty download, which is
    the normal state when the affected-guard skips e2e.

Playwright browsers

  • Cached by resolved Playwright version, and only chromium is installed now.
    playwright.config.ts defines a single Desktop Chrome project, so firefox and
    webkit were being downloaded on every run and never used. OS packages are not
    cacheable, so a cache hit still installs those via install-deps.

Nx computation cache

  • Persist .nx/cache through the GitHub Actions cache. This is the local cache,
    not Nx Cloud: nothing leaves GitHub and there is no plan to exhaust.
  • Production builds opt out with --skip-nx-cache. Env vars are not declared as
    Nx inputs, so a cached build can be replayed with the wrong values baked in.
    build:landing already did this; build:ci and build:affected now match.

Copilot AI lite review requested due to automatic review settings August 9, 2026 15:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces CI wall-clock time by sharding Jetstream’s Playwright E2E suite across 4 GitHub Actions runners, caching Playwright browser downloads and Nx’s local computation cache, and migrating workspace lint/format to oxlint/oxfmt (removing per-project ESLint configs).

Changes:

  • Shard apps/jetstream-e2e Playwright runs across a 4-way matrix and merge blob reports back into one HTML report artifact.
  • Cache Playwright browsers (chromium only) and persist Nx .nx/cache across CI runs; disable Nx cache for production builds where env vars aren’t declared as inputs.
  • Switch formatting/linting to oxfmt/oxlint, update scripts/docs/editor config, and remove legacy ESLint configuration files/targets.

Reviewed changes

Copilot reviewed 197 out of 201 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/generate-release-notes.ts Use oxfmt for generated notes formatting
scripts/generate-csp-hash-g-analytics.ts Update gtag script extraction regex
scripts/create-hotfix.mjs Formatting-only cleanup
pnpm-workspace.yaml Add patchedDependencies + nx eslint-plugin patch
patches/@nx__eslint-plugin@23.1.1.patch Patch Nx plugin to cache graph under oxlint
package.json Add oxlint/oxfmt; adjust scripts; keep prettier dep
nx.json Remove eslint generator/plugin config; keep vitest caching
mock-idp/docker-compose.yml Formatting-only (quotes)
libs/ui/src/lib/sobject-field-list/SobjectExpandChildrenBtn.tsx Formatting-only
libs/ui/src/lib/providers/DialogServiceProvider.tsx Avoid children prop (React lint)
libs/ui/src/lib/popover/Popover.tsx Formatting-only for template literals
libs/ui/src/lib/modal/Modal.tsx Simplify open expression
libs/ui/src/lib/list/tests/List.spec.tsx Formatting-only
libs/ui/src/lib/form/date/DateGrid.tsx Remove redundant ARIA role attr
libs/ui/src/lib/form/checkbox/tests/Checkbox.spec.tsx Formatting-only
libs/ui/src/lib/file-download-modal/options/FileDownloadGoogle.tsx Add oxlint disable comment (needs follow-up)
libs/ui/src/lib/data-table/grid/grid-types.ts Formatting-only for union type
libs/ui/src/lib/data-table/grid/grid-paste.ts Remove redundant empty-row length check
libs/ui/src/lib/card/tests/Card.spec.tsx Formatting-only
libs/ui/src/lib/alert/tests/Alert.spec.tsx Formatting-only
libs/ui/src/index.ts Trailing comma formatting
libs/ui/project.json Remove eslint lint target
libs/ui/eslint.config.js Remove legacy ESLint config
libs/types/project.json Remove eslint lint target
libs/types/eslint.config.js Remove legacy ESLint config
libs/test/e2e-utils/src/lib/TeamCreationUtils.ts Remove pointless Promise.all wrapper
libs/test/e2e-utils/eslint.config.cjs Remove legacy ESLint config
libs/test-utils/eslint.config.cjs Remove legacy ESLint config
libs/splitjs/project.json Remove eslint lint target
libs/splitjs/eslint.config.js Remove legacy ESLint config
libs/shared/utils/project.json Remove eslint lint target
libs/shared/utils/eslint.config.js Remove legacy ESLint config
libs/shared/ui-utils/src/lib/shared-ui-data-utils.ts Formatting-only
libs/shared/ui-utils/project.json Remove eslint lint target
libs/shared/ui-utils/eslint.config.js Remove legacy ESLint config
libs/shared/ui-router/eslint.config.js Remove legacy ESLint config
libs/shared/ui-record-form/project.json Remove eslint lint target
libs/shared/ui-record-form/eslint.config.js Remove legacy ESLint config
libs/shared/ui-db/eslint.config.cjs Remove legacy ESLint config
libs/shared/ui-core/src/state-management/query.state.ts Formatting-only
libs/shared/ui-core/src/query/QueryHistory/QueryHistoryModal.tsx Formatting-only
libs/shared/ui-core/src/orgs/OrgsCombobox.tsx Formatting-only
libs/shared/ui-core/src/load/load-records-utils.tsx Formatting-only
libs/shared/ui-core/src/jobs/Jobs.tsx Formatting-only
libs/shared/ui-core/src/app/HeaderNavbar.tsx Add React keys to menu item arrays
libs/shared/ui-core/src/app/AnalysisToolsPaywall.tsx Formatting-only
libs/shared/ui-core/eslint.config.js Remove legacy ESLint config
libs/shared/ui-core-shared/eslint.config.js Remove legacy ESLint config
libs/shared/ui-app-state/eslint.config.js Remove legacy ESLint config
libs/shared/node-utils/project.json Remove eslint lint target
libs/shared/node-utils/eslint.config.js Remove legacy ESLint config
libs/shared/data/src/lib/standardValueSet.ts Formatting-only
libs/shared/data/project.json Remove eslint lint target
libs/shared/data/eslint.config.js Remove legacy ESLint config
libs/shared/cookie-consent-banner/eslint.config.cjs Remove legacy ESLint config
libs/shared/constants/project.json Remove eslint lint target
libs/shared/constants/eslint.config.js Remove legacy ESLint config
libs/shared/client-logger/project.json Remove eslint lint target
libs/shared/client-logger/eslint.config.js Remove legacy ESLint config
libs/salesforce-api/src/lib/tests/api-bulk.spec.ts Formatting-only
libs/salesforce-api/project.json Remove eslint lint target
libs/salesforce-api/eslint.config.js Remove legacy ESLint config
libs/release-notes/src/lib/release-notes-utils.ts Formatting-only
libs/release-notes/project.json Remove eslint lint target
libs/release-notes/eslint.config.js Remove legacy ESLint config
libs/monaco-configuration/src/lib/monaco-apex-completions.ts Formatting-only
libs/monaco-configuration/project.json Remove eslint lint target
libs/monaco-configuration/eslint.config.js Remove legacy ESLint config
libs/icon-factory/project.json Remove eslint lint target
libs/icon-factory/eslint.config.js Remove legacy ESLint config
libs/features/update-records/src/selection/useMassUpdateFieldItems.ts Minor object shorthand cleanup
libs/features/update-records/eslint.config.js Remove legacy ESLint config
libs/features/teams/src/lib/TeamDashboard/TeamMemberSessionModal.tsx Remove redundant gridcell roles
libs/features/teams/src/lib/TeamDashboard/TeamMemberAuthActivityModal.tsx Remove redundant gridcell roles
libs/features/teams/src/lib/TeamDashboard/TeamAuditLogModal.tsx Remove redundant gridcell roles
libs/features/teams/src/lib/TeamDashboard/team-members/TeamMemberRow.tsx Remove redundant gridcell roles
libs/features/teams/src/lib/TeamDashboard/team-members/InvitationRow.tsx Remove redundant gridcell roles
libs/features/teams/eslint.config.cjs Remove legacy ESLint config
libs/features/sobject-export/eslint.config.js Remove legacy ESLint config
libs/features/salesforce-api/eslint.config.js Remove legacy ESLint config
libs/features/record-type-manager/eslint.config.cjs Remove legacy ESLint config
libs/features/query/src/utils/query-apex-utils.ts Use Array.from for indentation building
libs/features/query/src/QueryOptions/QueryFieldFunction.tsx Formatting-only
libs/features/query/src/QueryBuilder/QueryChildFields.tsx Formatting-only
libs/features/query/src/Query.tsx Simplify object literal with shorthand
libs/features/query/eslint.config.js Remove legacy ESLint config
libs/features/platform-event-monitor/src/PlatformEventMonitorEvents.tsx Formatting-only
libs/features/platform-event-monitor/eslint.config.js Remove legacy ESLint config
libs/features/permission-analysis/src/permission-export-result-view-modules/export-result-sorting.ts Replace new Array with Array.from
libs/features/permission-analysis/src/tests/export-grid-finding-cells.spec.ts Minor object shorthand cleanup
libs/features/permission-analysis/eslint.config.js Remove legacy ESLint config
libs/features/org-groups/src/lib/SalesforceOrgCardDraggable.tsx Formatting-only for conditional CSS
libs/features/org-groups/src/lib/OrgGroupCardNoOrganization.tsx Formatting-only for conditional CSS
libs/features/org-groups/src/lib/OrgGroupCard.tsx Formatting-only for conditional CSS
libs/features/org-groups/eslint.config.js Remove legacy ESLint config
libs/features/manage-permissions/src/utils/permission-manager-table-utils.tsx Object shorthand cleanup in many places
libs/features/manage-permissions/src/ManagePermissionsEditor.tsx Formatting-only for union type
libs/features/manage-permissions/eslint.config.js Remove legacy ESLint config
libs/features/load-records/src/utils/load-records-process.ts Formatting-only + small regex tweak
libs/features/load-records/src/components/LoadRecordsFieldMappingRow.tsx Formatting-only
libs/features/load-records/src/components/LoadRecordsFieldMappingRelatedObject.tsx Formatting-only
libs/features/load-records/src/components/load-results/LoadRecordsBatchApiResults.tsx Collapse multi-line import
libs/features/load-records/eslint.config.js Remove legacy ESLint config
libs/features/load-records-multi-object/src/load-records-multi-object-utils.ts Formatting-only
libs/features/load-records-multi-object/eslint.config.js Remove legacy ESLint config
libs/features/formula-evaluator/eslint.config.js Remove legacy ESLint config
libs/features/deploy/src/utils/DeployMetadataStatusModal.tsx Fix trackEvent call shape formatting
libs/features/deploy/src/deploy-metadata-history/DeployMetadataHistoryModal.tsx Object shorthand cleanup
libs/features/deploy/eslint.config.js Remove legacy ESLint config
libs/features/debug-log-viewer/eslint.config.js Remove legacy ESLint config
libs/features/data-analysis/src/shared/analysis-job-runtime-state.ts Formatting-only
libs/features/data-analysis/eslint.config.js Remove legacy ESLint config
libs/features/create-records/eslint.config.cjs Remove legacy ESLint config
libs/features/create-object-and-fields/src/LoadExistingFieldsModal.tsx Formatting-only
libs/features/create-object-and-fields/src/CreateFieldsRowField.tsx Add missing key for mapped Radios
libs/features/create-object-and-fields/src/CreateFieldsImportExport.tsx Formatting-only
libs/features/create-object-and-fields/src/CreateFieldsFormulaEditorManualField.tsx Object shorthand cleanup
libs/features/create-object-and-fields/src/CreateFields.tsx Formatting-only
libs/features/create-object-and-fields/eslint.config.js Remove legacy ESLint config
libs/features/automation-control/eslint.config.js Remove legacy ESLint config
libs/features/anon-apex/eslint.config.js Remove legacy ESLint config
libs/features/analysis-shared/src/field-usage/compute-field-usage-where-used.ts Replace new Array with Array.from
libs/features/analysis-shared/eslint.config.js Remove legacy ESLint config
libs/email/eslint.config.js Remove legacy ESLint config
libs/desktop-types/project.json Remove eslint lint target
libs/desktop-types/eslint.config.js Remove legacy ESLint config
libs/connected/connected-ui/src/lib/useListMetadata.tsx Formatting-only
libs/connected/connected-ui/src/lib/DescribeMetadataList.tsx Fix select-all callback structure
libs/connected/connected-ui/project.json Remove eslint lint target
libs/connected/connected-ui/eslint.config.js Remove legacy ESLint config
libs/auth/types/eslint.config.js Remove legacy ESLint config
libs/auth/server/src/lib/auth.utils.ts Add oxlint rule disable for control regex
libs/auth/server/src/lib/auth.db.service.ts Formatting-only
libs/auth/server/src/lib/tests/step-up-auth.service.spec.ts Remove useless fallback in spread
libs/auth/server/src/lib/tests/sso-auth.service.spec.ts Formatting-only
libs/auth/server/src/lib/tests/email-change.db.service.spec.ts Use non-null assertion in test
libs/auth/server/src/lib/tests/auth.db.service.password.spec.ts Remove optional chaining in test args
libs/auth/server/eslint.config.js Remove legacy ESLint config
libs/auth/salesforce-oauth/eslint.config.cjs Remove legacy ESLint config
libs/auth/acl/eslint.config.cjs Remove legacy ESLint config
libs/audit-logs/eslint.config.cjs Remove legacy ESLint config
libs/api-types/project.json Remove eslint lint target
libs/api-types/eslint.config.js Remove legacy ESLint config
libs/api-config/src/lib/request-context.ts Remove useless fallback in spread
libs/api-config/project.json Remove eslint lint target
libs/api-config/eslint.config.js Remove legacy ESLint config
eslint.config.js Remove workspace ESLint flat config
docker-compose.yml Formatting-only (quotes)
docker-compose.e2e.yml Formatting-only (quotes/env)
CONTRIBUTING.md Update formatting guidance to pnpm format
CLAUDE.md Update tooling references to oxlint/oxfmt
apps/landing/pages/pricing/index.tsx Add keys for feature elements + map index key
apps/landing/eslint.config.js Remove legacy ESLint config
apps/landing/components/blog-post-renderers.tsx Formatting-only
apps/jetstream/project.json Remove eslint lint target
apps/jetstream/eslint.config.js Remove legacy ESLint config
apps/jetstream-web-extension/eslint.config.js Remove legacy ESLint config
apps/jetstream-web-extension-e2e/eslint.config.js Remove legacy ESLint config
apps/jetstream-e2e/src/tests/query/query-editor.spec.ts Formatting-only
apps/jetstream-e2e/playwright.config.ts Shard-friendly config (workers, reporter, maxFailures)
apps/jetstream-e2e/eslint.config.js Remove legacy ESLint config
apps/jetstream-desktop/src/services/deep-link.service.ts Add oxlint rule disable for control regex
apps/jetstream-desktop/eslint.config.cjs Remove legacy ESLint config
apps/jetstream-desktop-client/eslint.config.cjs Remove legacy ESLint config
apps/jetstream-desktop-client-e2e/eslint.config.cjs Remove legacy ESLint config
apps/jetstream-canvas/eslint.config.cjs Remove legacy ESLint config
apps/geo-ip-api/project.json Remove eslint lint target
apps/geo-ip-api/eslint.config.js Remove legacy ESLint config
apps/docs/eslint.config.js Remove legacy ESLint config
apps/docs/docs/getting-started/_org-troubleshooting-table.mdx Formatting-only table alignment
apps/cron-tasks/src/utils/cloudflare.utils.ts Formatting-only
apps/cron-tasks/src/utils/cloudflare-daily-digest.utils.ts Formatting-only
apps/cron-tasks/src/utils/cloudflare-analytics-archiver.utils.ts Formatting-only
apps/cron-tasks/src/tests/cloudflare-daily-digest.spec.ts Formatting-only
apps/cron-tasks/src/tests/cloudflare-analytics-archiver.spec.ts Formatting-only
apps/cron-tasks/src/tests/clean-up-user-sync-history.spec.ts Replace new Array().fill().map with Array.from
apps/cron-tasks/project.json Remove eslint lint target
apps/cron-tasks/eslint.config.js Remove legacy ESLint config
apps/api/project.json Remove eslint lint target
apps/api/eslint.config.js Remove legacy ESLint config
.vscode/settings.json Switch default formatter to Oxc; remove eslint flat flag
.vscode/extensions.json Recommend oxc.oxc-vscode; drop prettier/eslint
.release-it-web-ext.json Use oxfmt for manifest formatting
.release-it-desktop.json Use oxfmt for package.json formatting
.prettierrc Remove Prettier config (oxfmt migration)
.prettierignore Remove Prettier ignore (oxfmt uses .oxfmtrc)
.oxlintrc.json Add oxlint configuration + Nx JS plugin
.oxfmtrc.json Add oxfmt configuration + ignores/overrides
.husky/pre-commit Add pre-commit format/lint checks
.gitignore Ignore server.log
.github/workflows/ci.yml Add lint job; shard e2e; cache Playwright + Nx cache
.github/labeler.yml Formatting-only (quotes)
.github/copilot-instructions.md Update instructions for oxlint/oxfmt
.git-blame-ignore-revs Add oxc migration rev to ignore in blame
.dockerignore Include .oxlintrc.json instead of .eslintrc.json
.claude/settings.json Allow pnpm oxfmt/oxlint commands
.agents/skills/conventional-commit/SKILL.md Remove trailing blank line
Suppressed comments (1)

libs/ui/src/lib/file-download-modal/options/FileDownloadGoogle.tsx:229

  • Same issue in the web implementation: the oxlint-disable-next-line react-hooks/rules-of-hooks comment is targeting the wrong rule (and the next line isn’t a Hook call). Also, handleUserInfoChange and useNonInitialEffect capture onFolderSelectedEvent but don’t list it as a dependency, which can lead to stale closures and still trigger react-hooks/exhaustive-deps. Add the dependency and drop the misleading disable comments.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/generate-csp-hash-g-analytics.ts
Comment thread libs/ui/src/lib/file-download-modal/options/FileDownloadGoogle.tsx
@paustint
paustint force-pushed the chore/ci/improve-jobs branch 2 times, most recently from 8b7f7f3 to c4b0730 Compare August 9, 2026 16:15
Copilot AI review requested due to automatic review settings August 9, 2026 16:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

.github/workflows/ci.yml:146

  • The Nx cache key includes ${{ github.sha }}, which guarantees a new Actions cache entry is created for every commit. That can grow cache storage quickly and increase eviction/churn. Consider keying primarily off inputs that actually invalidate the cache (e.g., OS + lockfile hash) so the cache is reusable across commits without creating a new cache object each run.
          path: .nx/cache
          key: nx-${{ runner.os }}-${{ github.sha }}
          restore-keys: |
            nx-${{ runner.os }}-

Comment thread .github/workflows/ci.yml
@paustint

Copy link
Copy Markdown
Contributor Author

Went through Copilot's suppressed (low-confidence) comments:

  • .github/workflows/ci.yml (Nx cache key) — partly fair, adjusted locally. The per-commit key is deliberate: Actions cache entries are immutable, so keying only on OS + lockfile hash would freeze the Nx cache at whatever the first run produced and never accumulate new task results. Kept the unique key but added the lockfile hash, so restores prefer a cache built against the same dependencies before falling back. Not pushed yet.
  • FileDownloadGoogle.tsx:229 — doesn't apply. onFolderSelectedEvent is useEffectEvent(...), and rules-of-hooks is precisely the rule that fires when one is called outside an Effect, so the disable names the correct rule. The dependency suggestion is inverted: useEffectEvent functions are intentionally excluded from dependency arrays, and exhaustive-deps reports nothing on this file.

Copilot AI review requested due to automatic review settings August 10, 2026 13:08
@paustint
paustint force-pushed the chore/ci/improve-jobs branch from c4b0730 to 2bae758 Compare August 10, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/ci.yml:353

  • The workflow duplicates the Playwright invocation inline (including the config path). This makes it easy for CI and local scripts to drift (e.g., if testDir/config changes later). Reuse the existing playwright:test script and pass --shard via -- so there’s a single source of truth for the Playwright CLI args.
        run: >-
          pnpm start-server-and-test --expect 200
          'pnpm start:e2e' http://localhost:3333
          'pnpm playwright test src --config apps/jetstream-e2e/playwright.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}'

package.json:60

  • The PR description says production builds opt out of the Nx computation cache via --skip-nx-cache (and specifically calls out build:ci and build:affected as updated). In package.json, build:ci and build:affected still run with caching enabled, and the newly added build:e2e also doesn’t opt out. Please either update these scripts to match the description, or adjust the description if the intent is to keep caching for CI verification builds.
    "build:ci": "pnpm nx run-many --output-style=static --target=build --parallel=4 --projects=jetstream,api,landing,jetstream-canvas,jetstream-web-extension,jetstream-desktop,jetstream-desktop-client,geo-ip-api,cron-tasks --configuration=production",
    "build:e2e": "pnpm nx run-many --output-style=static --target=build --parallel=4 --projects=api,jetstream,landing --configuration=production",
    "build:affected": "nx affected --target=build --exclude jetstream-e2e,jetstream-desktop-client-e2e,jetstream-web-extension-e2e --parallel=4 --configuration=production",

… + Nx

The e2e job was ~17m30s wall clock and gates nothing else, making it the
critical path for every PR that touches api or jetstream. 12m40s of that was
the test run itself, executing serially on a single worker.

Measured across two sharded runs: e2e 17m30s -> 11m02s, with 127 passed,
3 skipped (all pre-existing `test.skip` in source), 0 failures and 0 retries
both times. Test coverage is unchanged — 126 tests before and after.

E2E sharding
- Split across a 4-way matrix. Each shard is its own job with its own postgres
  service, app server and seeded users, so the only resource shared between
  them is the Salesforce org. Raising `workers` inside one job would instead
  have them contend for the same database and logged-in user.
- Playwright runs the `setup` project in every shard, so each gets its own
  authenticated storage state.
- No `needs:` on build-and-test. Downloading a prebuilt dist would avoid four
  rebuilds but serialise e2e behind an 8m job; independent builds keep e2e
  parallel, which is what actually shortens the PR.
- `maxFailures` drops to 1 because the budget is now per shard.
- Shards emit blob reports that merge-e2e-reports stitches into the single HTML
  report the old job produced, tolerating an empty download for the case where
  the affected-guard skips e2e.

Job naming
- A matrix reports one context per shard and none under its own job name, so a
  branch rule requiring "e2e" would wait forever on a context that never
  reports. The matrix is named `e2e-shard` and an aggregate gate job takes the
  `e2e` name, so branch protection needs no change now and none later if the
  shard count moves.
- `needs['e2e-shard'].result` uses the bracket form deliberately —
  `needs.e2e-shard` parses as a subtraction in GitHub expressions.

Scoped E2E build
- New `build:e2e` compiles only api, jetstream and landing. `build:ci` builds
  all nine apps, but the suite runs `dist/apps/api/main.js`, and api/src/main.ts
  statically serves just ../landing (auth pages) and ../jetstream. Nothing in
  the suite reaches canvas, the web extension, desktop, geo-ip-api or
  cron-tasks. Six apps' worth of build work drops off every shard.

Playwright browsers
- Cached by resolved version, and only chromium is installed — the config
  defines a single Desktop Chrome project, so firefox and webkit were being
  downloaded on every run and never used. Install dropped 82s -> 22s on a cache
  miss. OS packages are not cacheable, so a hit still runs `install-deps`.

Nx computation cache
- Persist .nx/cache through the GitHub Actions cache. This is the local cache,
  not Nx Cloud: nothing leaves GitHub and there is no plan to exhaust.
- Nothing shippable comes out of this workflow, so caching builds here is safe.
  The web app builds on Render; desktop and web-extension artifacts come from
  release.yml and publish-desktop-*.yml, none of which restore this cache. The
  dist-artifacts upload is never consumed.
- The e2e shards restore the cache read-only. All four build the same projects,
  so letting each save would race on one key and upload the same cache four
  times; build-and-test owns writing it.
Copilot AI review requested due to automatic review settings August 10, 2026 13:29
@paustint
paustint force-pushed the chore/ci/improve-jobs branch from 2bae758 to 1afe1d8 Compare August 10, 2026 13:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

package.json:60

  • The PR description notes production builds should opt out of Nx caching because env vars aren’t declared as Nx inputs, but the build scripts here still allow cached results. With the workflow now restoring .nx/cache, build:ci / build:affected can replay a previous build with different env baked in (and build:e2e will behave the same if it’s ever run in a cached context). Add --skip-nx-cache to these production build commands to match the stated intent and avoid false-green builds.
    "build": "cross-env NODE_ENV=production npm-run-all db:generate generate:version build:core build:landing",
    "build:core": "cross-env NODE_OPTIONS=--max_old_space_size=8192 nx run-many --output-style=dynamic --target=build --parallel=4 --projects=jetstream,jetstream-canvas,api --configuration=production",
    "build:ci": "pnpm nx run-many --output-style=static --target=build --parallel=4 --projects=jetstream,api,landing,jetstream-canvas,jetstream-web-extension,jetstream-desktop,jetstream-desktop-client,geo-ip-api,cron-tasks --configuration=production",
    "build:e2e": "pnpm nx run-many --output-style=static --target=build --parallel=4 --projects=api,jetstream,landing --configuration=production",
    "build:affected": "nx affected --target=build --exclude jetstream-e2e,jetstream-desktop-client-e2e,jetstream-web-extension-e2e --parallel=4 --configuration=production",

Copilot AI review requested due to automatic review settings August 10, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (4)

package.json:59

  • build:e2e is a production build but does not pass --skip-nx-cache, even though the PR description calls out that env vars aren’t declared as Nx inputs and production builds should opt out to avoid replaying cached outputs with different baked-in env values (e.g. Vite/Next env at build time). Either add --skip-nx-cache here (and/or update the PR description if the intent is to allow caching).
    "build:e2e": "pnpm nx run-many --output-style=static --target=build --parallel=4 --projects=api,jetstream,landing --configuration=production",

apps/landing/project.json:10

  • dependsOn uses ^typecheck, which runs typecheck for dependencies but not for landing itself. Other apps in this repo use dependsOn: ["^build", "typecheck"] on their build targets, ensuring the project’s own typecheck runs before building.
    "build": {
      "dependsOn": ["^build", "^typecheck"]
    }

.github/workflows/ci.yml:399

  • The blob reporter output directory is apps/jetstream-e2e/blob-report (because Playwright resolves outputDir relative to apps/jetstream-e2e/playwright.config.ts), but the merge step points merge-reports at ./all-blob-reports. After downloading artifacts, the reports will be under all-blob-reports/apps/jetstream-e2e/blob-report, so the merge step is likely looking in the wrong directory.
          if [ -z "$(ls -A all-blob-reports 2>/dev/null)" ]; then
            echo "::notice::No blob reports found — E2E was skipped by the affected guard"
            exit 0
          fi
          pnpm exec playwright merge-reports --reporter html ./all-blob-reports

.github/workflows/ci.yml:292

  • E2E is currently skipped unless the affected app list contains api or jetstream. That means changes that only affect the jetstream-e2e app itself (tests/config) won’t run E2E, which can let broken E2E changes merge and only fail later on push-to-main. Consider including jetstream-e2e in the guard check.
      # build:ci compiles all nine apps; E2E only reaches three of them. api/src/main.ts statically
      # serves ../landing (auth pages) and ../jetstream (the app), and nothing in the suite touches
      # canvas, the web extension, desktop, geo-ip-api or cron-tasks.
      - name: Build
        if: steps.guard.outputs.should_run == 'true'
        run: pnpm build:e2e

@paustint
paustint merged commit c955fe7 into main Aug 10, 2026
15 checks passed
@paustint
paustint deleted the chore/ci/improve-jobs branch August 10, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants