feat(cli): write a GitHub Actions or GitLab CI/CD pipeline in e2e init

KA
3d ago
+1351-14

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|none answers the question. --yes alone 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 (detectCiService in packages/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, keep report.json, junit.xml, and results/.
  • Node comes from .nvmrc or .node-version, else 24. GitHub uses node-version-file; GitLab runs in node:<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. init reads the gateway from the config's import and prints the gh secret set or glab variable set command.
  • init never overwrites a pipeline file. Beside an existing .gitlab-ci.yml it writes .gitlab/e2e.gitlab-ci.yml and prints the include: to add, naming the job e2e-run when the pipeline has an e2e job and saying when its stages: leave out test. A workflow or pipeline that already runs e2e run gets nothing. Nothing is written through a symlinked .github or .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. --ci on 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 that actionlint and glab ci lint accept as is. init edits a copy with the yaml package'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.ts fails when they drift.
  • Telemetry: e2e_init_completed gets a ci property.
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:

CaseWhereResult
GitHub Actions, npm, repo roote2e-github-actions#2, merged, then main5/5 passed, agent tests included
GitHub Actions, pnpm in site/, .nvmrc at the roote2e-github-actions#35/5 passed, report uploaded from site/.e2e
GitHub Actions, pnpm version from site/package.json (package_json_file)e2e-github-actions#5passed
GitLab, npm, new .gitlab-ci.ymle2e-gitlab-ci!1, merged, then main5/5 in the MR test report, 0 skipped
GitLab, beside an existing .gitlab-ci.yml via include:e2e-gitlab-ci!25/5 passed
GitLab, committed replay cachee2e-gitlab-ci!3Cache 2 replayed, 2 model calls instead of 7
GitLab, pnpm via corepack on node:26 (no corepack in the image)e2e-gitlab-ci!5corepack 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-actions and --ci gitlab each wrote their file and printed the secret and app.command hints.
  • 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.