ci: shard e2e across 4 runners and cache Playwright browsers + Nx - #1924
Conversation
There was a problem hiding this comment.
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-e2ePlaywright 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/cacheacross 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-hookscomment is targeting the wrong rule (and the next line isn’t a Hook call). Also,handleUserInfoChangeanduseNonInitialEffectcaptureonFolderSelectedEventbut don’t list it as a dependency, which can lead to stale closures and still triggerreact-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.
8b7f7f3 to
c4b0730
Compare
There was a problem hiding this comment.
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 }}-
|
Went through Copilot's suppressed (low-confidence) comments:
|
c4b0730 to
2bae758
Compare
There was a problem hiding this comment.
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:testscript and pass--shardvia--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 outbuild:ciandbuild:affectedas updated). Inpackage.json,build:ciandbuild:affectedstill run with caching enabled, and the newly addedbuild:e2ealso 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.
2bae758 to
1afe1d8
Compare
There was a problem hiding this comment.
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:affectedcan replay a previous build with different env baked in (andbuild:e2ewill behave the same if it’s ever run in a cached context). Add--skip-nx-cacheto 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",
There was a problem hiding this comment.
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:e2eis 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-cachehere (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
dependsOnuses^typecheck, which runs typecheck for dependencies but not forlandingitself. Other apps in this repo usedependsOn: ["^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 resolvesoutputDirrelative toapps/jetstream-e2e/playwright.config.ts), but the merge step pointsmerge-reportsat./all-blob-reports. After downloading artifacts, the reports will be underall-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
apiorjetstream. That means changes that only affect thejetstream-e2eapp itself (tests/config) won’t run E2E, which can let broken E2E changes merge and only fail later on push-to-main. Consider includingjetstream-e2ein 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
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
(33/33/32/32) and runs the
setupproject in every shard, so each gets itsown authenticated storage state.
workersis deliberate. Each shard is its ownjob 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.
needs:on build-and-test. Downloading a prebuilt dist would avoid fourrebuilds but serialise e2e behind an 8m job; letting each shard build its own
keeps e2e parallel, which is what actually shortens the PR.
maxFailuresdrops to 1 because the budget is now per shard — 2 each wouldhave tolerated 8 failures across the run.
report the old job produced. That job tolerates an empty download, which is
the normal state when the affected-guard skips e2e.
Playwright browsers
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
not Nx Cloud: nothing leaves GitHub and there is no plan to exhaust.
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.