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

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.