feat(cli): write a GitHub Actions or GitLab CI/CD pipeline in e2e init
KA
5h ago+2257+2257+0123456789012345678901234567890123456789-30-30-01234567890123456789
e2e init gets a CI step. In a project with the web or mobile engine it asks which CI service to set up, then writes one job for each platform the config's targets run on: web, iOS, and Android.
◆ Add a CI pipeline that runs the tests?
│ ○ GitHub Actions
│ ● GitLab CI/CD (in every merge request and on the default branch)
│ ○ None
| Targets in the config | Jobs |
|---|---|
| web only | web, running every target |
| iOS and/or Android only | ios and/or android |
| web and mobile | all of them, each with --target <its targets> |
--ci github-actions|gitlab|noneanswers the question.--yesalone writes none, since a pipeline runs on the next push. The wizard preselects the service the repository points to: its remote, a.github/folder, or a.gitlab-ci.yml.- Targets come from the new config
initwrites, or from loading an existing one the waye2e rundoes (loadConfigModule, andresolvePlatform/targetNamefromconfig/targets.ts). The load happens after the user confirms and after the install, so the config never runs on a cancelled wizard and an example's engine is installed first. A config that still doesn't load falls back topackage.json: web engine only, one web job runs every target; mobile engine, no pipeline. - Web job: the project's package manager (npm, pnpm, Yarn 1, Yarn 2+, Bun), Chromium with a cache,
e2e run --reporter list,junit. It runs every target that isn't iOS or Android, HTTP tests included. - Mobile jobs on GitHub follow e2e-inc/e2e-react-native's workflow:
macos-26with a Release simulator build, the image's own iPhone 17 Pro on a pinned iOS runtime, andE2E_DEVICEset to its UDID;ubuntu-latestwith KVM, anx86_64release APK, and an API 36 emulator, with a system-image install that retries a download that drops or comes back empty. - The app build: Expo (
expo run:ios,expo prebuild+ Gradle, no prebuild whenandroid/is committed), bare React Native (xcodebuildonios/*.xcworkspace, through Bundler with aGemfile;android/gradlew), or a step that fails until replaced, whichinitnames. - GitLab's hosted runners can't run a simulator or an emulator, so its mobile jobs target self-managed shell runners with
tagsto replace, as ondocs/ci/gitlab.mdx;initsays so, and asks fordevice: process.env.E2E_DEVICEwhen the config lacks it. - Node comes from
.nvmrcor.node-version, else 24. A project in a subfolder or a workspace member gets the file at the repository root, the install at the workspace root, and the jobs in the project folder. - Model key: GitHub passes the repository secret in the run step's
env; GitLab gets CI/CD variables on its own.initreads the gateway from the config's import and prints thegh secret setorglab variable setcommand. initnever overwrites a pipeline file. Beside an existing.gitlab-ci.ymlit writes.gitlab/e2e.gitlab-ci.ymland prints theinclude:to add, renaming the jobs with-runwhen the pipeline has one of their names and saying when itsstages:leave outtest. A workflow or pipeline that already runse2e rungets nothing. Nothing is written through a symlinked.githubor.gitlab.- Warnings for what would fail on the first push: no
app.command(web job only), a subscription gateway or a loopback model endpoint, a build step to replace, GitLab mobile runners. - The pipelines are YAML templates in
packages/e2e/templates/ci/, each a complete pipeline for npm at the repository root and an Expo app, whichactionlintandglab ci lintaccept as is.initedits a copy with theyamlpackage's Document API (new runtime dependency, pure JS), keeping comments and formatting; a platform with no targets loses its job. For the template's own case the output equals the template byte for byte. - GitHub action pins live only in the templates and match the docs and the e2e skill;
tests/unit/init-ci.test.tsfails when they drift. - Telemetry:
e2e_init_completedgets aciproperty.
flowchart TD
A[web or mobile engine in package.json?] -- no --> N[no question]
A -- yes --> B{e2e run already in a workflow or .gitlab-ci.yml?}
B -- yes --> N
B -- no --> C[ask, preselect from remote / .github / .gitlab-ci.yml]
C --> D[confirm changes, install]
D --> E[targets: new config, or load the existing one]
E --> F[web job for non-phone targets, ios and android jobs for phone targets]
F --> G[.github/workflows/e2e.yml, .gitlab-ci.yml, or .gitlab/e2e.gitlab-ci.yml to include]
Verified
Ran it locally: yes. The built CLI wrote the pipelines into real repos, and each ran unedited on hosted runners:
| Case | Where | Result |
|---|---|---|
| GitHub, web + iOS + Android, Expo, existing config with three targets | e2e-react-native#5, its own workflow replaced by the generated one | RN_RESULT |
| GitHub Actions, npm, repo root | e2e-github-actions#2, merged | 5/5, agent tests included |
GitHub Actions, pnpm in site/, .nvmrc at the root | e2e-github-actions#3 | 5/5 |
GitHub Actions, pnpm version from site/package.json | e2e-github-actions#5 | passed |
| GitHub, web job from the YAML template | e2e-github-actions#6 | 5/5, cache replayed |
GitLab, npm, new .gitlab-ci.yml | e2e-gitlab-ci!1, merged | 5/5 in the MR test report |
GitLab, beside an existing .gitlab-ci.yml via include: | e2e-gitlab-ci!2 | 5/5 |
GitLab, pnpm via corepack on node:26 | e2e-gitlab-ci!5 | 5/5 |
| GitLab, web job from the YAML template | e2e-gitlab-ci!6 | 5/5 |
actionlintandglab ci lintpass on every combination rendered: web only, mobile only, both, each build kind, npm/pnpm/Yarn/Bun, a subfolder, and a workspace member. The GitLab file for e2e-react-native passesglab ci linttoo.- Packed tarball in a scratch project, and the interactive wizard in a pseudo-terminal: the question above, with the service preselected from the remote.
- Not run on a runner: the bare React Native build, and GitLab's mobile jobs (gitlab.com's hosted runners can't run them).
Two fresh-context reviews found issues, all fixed: the first nine in the web-only version, then eight in the mobile one (the config loaded before the install, so an example got no pipeline; the GitLab header lost with the web job; and smaller ones). cubic's findings are fixed or answered in their threads, and kdy1's suggestion to keep the pipelines as YAML templates is in.
Tracked in ENG-1084.