Hive Hive
Sign in

feat(cli): add hidden bazel setup and credential-helper commands

GitHub issue · Closed

Metadata
Source
tuist/tuist #11199
Updated
Jun 24, 2026
Domains
CLI Kura
Details

What changed

Two new hidden commands (not listed in tuist --help) that connect Bazel’s remote cache to the Tuist/Kura cache:

  • tuist bazel setup generates a .bazelrc.tuist in the project directory:
    build --remote_cache=grpcs://<kura-endpoint>
    build --remote_header=x-tuist-account-handle=<account-handle>
    build --credential_helper=<kura-endpoint-host>=<path-to-helper-script>
    build --remote_instance_name=<project-handle>
    It also creates (only if missing) an executable tuist-bazel-credential-helper shell script in the same directory where user credentials are stored ($XDG_CONFIG_HOME/tuist/credentials), which simply execs tuist bazel credential-helper "$@".
  • tuist bazel credential-helper implements the Bazel credential helper spec: it accepts the get command (anything else exits non-zero per spec) and prints a JSON payload to stdout with the Authorization header and the token expiry.

Why

Bazel users on the Kura cache currently have to hand-assemble the remote-cache flags and manage tokens themselves. These commands reuse the CLI’s existing auth and endpoint-selection machinery so a Bazel workspace can be onboarded with a single command, with tokens kept fresh automatically at build time.

Design decisions

  • Reuses the Gradle plugin’s resolution logic, already ported to the CLI. Account/project handles come from the project configuration via ConfigLoader + FullHandleService (same semantics as the Gradle plugin’s ProjectHandle.parse), and endpoint selection goes through the existing CacheURLStore, which implements the identical strategy as the Gradle plugin’s CacheEndpointResolver: TUIST_CACHE_ENDPOINT env override → single endpoint used directly → parallel latency probe picking the fastest reachable endpoint.
  • Tokens use the CLI’s built-in refresh flow. ServerAuthenticationController.authenticationToken(serverURL:) refreshes expired user tokens (30s expiry buffer) before returning, so the credential helper always hands Bazel a fresh token. The expires field (RFC 3339) lets Bazel cache the credentials and re-invoke the helper only when needed; project tokens have no expiry, so the field is omitted for them (it is optional in the schema).
  • Response format follows the JSON schema, not the plan’s inline example. The get-credentials-response schema defines headers as a map of header name → list of values ({"headers": {"Authorization": ["Bearer <token>"]}}), which is what Bazel parses.
  • Credential-helper scope uses the endpoint host without port (Bazel scope patterns are host patterns; ports are not allowed), while --remote_cache keeps host:port when a port is present.
  • Commands live in TuistCacheCommand rather than a new TuistBazelCommand module. Conceptually, both commands are remote-cache plumbing — resolve handles, pick an endpoint, hand out tokens — which is the same domain as cache config in that module (the existing integration point for the Gradle plugin); they sit next to the code they mirror and reuse (CacheURLStore, the auth/handle-resolution flow). Practically, the per-domain module convention would suggest a dedicated module, but each new module requires a target + dependency array in Package.swift plus a new enum case threaded through Tuist/ProjectDescriptionHelpers/Module.swift (dependencies, test-target wiring, several switch sites), and its dependency list would be nearly identical to TuistCacheCommand‘s (TuistServer, TuistCAS, TuistHTTP, TuistConfigLoader, TuistEnvKey, Noora). Co-locating kept the manifest churn to a FileSystem dependency line in each manifest. If more Bazel subcommands accumulate and the namespace deserves isolation from the cache module’s build graph, extracting a TuistBazelCommand module later is mechanical.
  • bazel credential-helper is excluded from analytics tracking in TuistCommand, like auth refresh-token: Bazel invokes it as build plumbing and tracking would spawn background analytics uploads per invocation. Its stdout must also stay pure JSON.

Validation

  • swift build --product tuist compiles; tuist --help does not list bazel, while tuist bazel --help shows both subcommands.
  • End-to-end smoke test against a temp project (tuist.toml with project = "acme/app", TUIST_TOKEN + TUIST_CACHE_ENDPOINT overrides, isolated XDG_CONFIG_HOME): tuist bazel setup produced the exact .bazelrc.tuist above and an executable (rwxr-xr-x) helper script; echo '{"uri":"grpcs://..."}' | tuist bazel credential-helper get printed clean JSON to stdout and exited 0; credential-helper store failed with exit 1 as the spec requires.
  • 11 new unit tests (BazelSetupCommandServiceTests, BazelCredentialHelperCommandServiceTests) pass (run via swift test in a swift:6.2 container, mirroring the Linux CI job; they’ll run on macOS CI through the Xcode workspace like the rest of TuistCacheCommandTests). Note this directory is not an SPM test target because CacheConfigCommandServiceTests imports macOS-only TuistCore.
  • swiftformat --lint clean on all touched files.

🤖 Generated with Claude Code

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments
T
tuist[bot] Jun 10, 2026

🛠️ Tuist Run Report 🛠️

Previews 📦
App Commit Open on device
Tuist 581aefb01
Tests 🧪
Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistAcceptanceTests 0 % 0 0 0 581aefb01
TuistApp 0 % 28 0 28 98d99f8f3
TuistUnitTests 2 % 704 34 670 581aefb01
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 6m 46s 581aefb01
TuistApp 1.3s 581aefb01
TuistUnitTests 6m 41s 581aefb01
Bundles 🧰
Bundle Commit Install size Download size
Tuist 581aefb01 19.5 MB 14.7 MBΔ -7 B (-0.00%)
E
esnunes Jun 10, 2026

I’ve performed many tests locally and the commands are working as expected, the bazel configuration also works as expected

TA
tuist-atlas[bot] Jun 12, 2026

The release notes for version 4.200.0 include the changes from your pull request. The hidden bazel setup and credential-helper commands are now available. Update to 4.200.0 to use these features.