feat(cli): write a GitHub Actions or GitLab CI/CD pipeline in e2e init
e2e init gets a CI step. In a project with the web engine it asks which CI service to set up, then writes one job that runs the web tests on a hosted Linux runner.
◆ Add a CI pipeline that runs the web tests?
│ ○ GitHub Actions
│ ● GitLab CI/CD (web tests in every merge request and on the default branch)
│ ○ None
--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(detectCiServiceinpackages/e2e/src/cli/init/ci.ts). - One job: install with the project's package manager (npm, pnpm, Yarn 1, Yarn 2+, Bun), install Chromium with a cache,
e2e run --reporter list,junit, keepreport.json,junit.xml, andresults/. - Node comes from
.nvmrcor.node-version, else 24. GitHub usesnode-version-file; GitLab runs innode:<major>. - A project in a subfolder or a workspace member gets the file at the repository root. The install runs at the workspace root and the tests 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, naming the jobe2e-runwhen the pipeline has ane2ejob 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, a subscription gateway or a loopback model endpoint (new or existing config), and existing configs with mobile targets (the job can't name the web target for you). - Mobile gets no job: it needs the app's build.
--cion a mobile-only project points at the docs. - The pipelines are YAML templates in
packages/e2e/templates/ci/, each a complete pipeline for npm at the repository root thatactionlintandglab ci lintaccept as is.initedits a copy with theyamlpackage's Document API (new runtime dependency, pure JS), which keeps comments and formatting. For npm at the root 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 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 -- GitHub Actions --> G[.github/workflows/e2e.yml]
C -- GitLab, no .gitlab-ci.yml --> L[.gitlab-ci.yml]
C -- GitLab, has .gitlab-ci.yml --> I[.gitlab/e2e.gitlab-ci.yml + include to add]
Verified
Ran it locally: yes. The built CLI wrote the pipelines into two playground repos with real model keys, and each ran unedited on the hosted runners:
| Case | Where | Result |
|---|---|---|
| GitHub Actions, npm, repo root | e2e-github-actions#2, merged, then main | 5/5 passed, agent tests included |
GitHub Actions, pnpm in site/, .nvmrc at the root | e2e-github-actions#3 | 5/5 passed, report uploaded from site/.e2e |
GitHub Actions, pnpm version from site/package.json (package_json_file) | e2e-github-actions#5 | passed |
GitLab, npm, new .gitlab-ci.yml | e2e-gitlab-ci!1, merged, then main | 5/5 in the MR test report, 0 skipped |
GitLab, beside an existing .gitlab-ci.yml via include: | e2e-gitlab-ci!2 | 5/5 passed |
| GitLab, committed replay cache | e2e-gitlab-ci!3 | Cache 2 replayed, 2 model calls instead of 7 |
GitLab, pnpm via corepack on node:26 (no corepack in the image) | e2e-gitlab-ci!5 | corepack installed by the job, 5/5 passed |
- Packed tarball (
pnpm pack) in a scratch project under the temp dir:npx e2e init --yes --ci github-actionsand--ci gitlabeach wrote their file and printed the secret andapp.commandhints. - Interactive run in a pseudo-terminal, repo with a GitLab remote: the question above, with GitLab CI/CD preselected; Enter wrote
.gitlab-ci.yml. - Not run on a hosted runner: Yarn and Bun (unit tests only).
A fresh-context review found nine issues, all fixed here: the workspace member's package manager, package_json_file for pnpm in a subfolder, corepack and Yarn missing from node:26, a docs link to a page that doesn't exist yet (/ci/gitlab, in #1643), gateway imports with double quotes, --target web on existing configs, a second GitHub workflow that already runs e2e, the GitLab test stage, and a warning for a loopback model endpoint.
After review, the pipelines moved from TypeScript strings to the YAML templates (0454a179, kdy1's suggestion). The output from the templates ran again on real runners: GitHub 5/5 (e2e-github-actions#6) and GitLab 5/5 (e2e-gitlab-ci!6), and actionlint passes on the workflow for npm, pnpm, Yarn 1, Yarn 2+, Bun, a subfolder, and a workspace member.
cubic's seven findings are fixed in 65f1e606 (exact stage names, the job name, the loopback check for existing configs, symlinks, and three doc lines).
Tracked in ENG-1084.