Tuist

Tuist

更新紀錄

Product

The test case page reported four numbers for the window you picked: how many times the test ran, how often it flaked, how reliable it was, and how long it took. Each one told you where the test stands. None of them told you how it got there, which is the part you act on. A test at 93% reliability is a different problem depending on whether it has been at 93% all month or fell there on Tuesday.

Every metric is now a chart. Click one and the card draws it across the period, next to what it was over the window before.

Analytics for a single test case in the Tuist dashboard: run outcomes stacked per day, each metric carrying its trend, and the test's history beside them

Runs is one bar per day, split by how those runs came out: successful, failed, flaky, quarantined, skipped. The height is the number of runs and the composition is the story. In the run above, three weeks of green give way to red and yellow bands nine days in, which is a regression with a date on it rather than a reliability number that drifted. The widget counts all runs, or just the failed or flaky ones, from a dropdown on its title.

Flakiness rate and reliability are drawn against a fixed 0-100% axis, so a test that sat between 98% and 100% all month reads as steady rather than as a cliff.

Duration draws the average with p50, p90 and p99 together. Reading them side by side separates a test that got slower for everyone from one that is usually fast and occasionally stalls: when the p50 climbs, the test itself changed; when only the p99 climbs, something intermittent did.

The test's own history runs down the side of the card, so a state change and the chart it explains are read together. The day someone quarantined a test sits beside the bars where its failures stopped counting.

Charts follow the date range picker, bucketing by hour, day or month to match the window. Days the test case did not run are gaps in the duration and rate lines rather than a test that suddenly took no time or failed every run, and a period with no runs at all says so instead of drawing a flat line at zero. A window with nothing before it is not a baseline either, so a test case that only started running this week shows no trend rather than a change of nothing.

Product

The Test Cases table showed one duration per test: the mean of its last 50 runs, with no time bound and no separation between CI and local. A single stalled run, a paused debugger for example, stayed in that average until 50 newer runs pushed it out, and could put a test that usually finishes in a second at the top of your slowest list.

It now shows p50, p90, p99 and the average side by side, over the runs from the last 14 days and scoped to the environment you select. Reading them together tells you something no single number does: a test whose p50 and p99 match takes the same time every run, while one whose p99 towers over its p50 is usually fast and occasionally not. Each column sorts on its own statistic, so "slowest by median" and "worst tail" are two orderings of the same table.

A test case needs at least five runs inside the window before its durations are ranked. Below that the cells read N/A, so a test with a single recorded run no longer sorts to the top on that one sample.

Test Cases table in the Tuist dashboard with p50, p90, p99 and average duration columns, sorted by p99

The same statistics are on the test case page, where the duration summary has a dropdown to switch between them.

Summary metrics for a single test case, showing the p90 run duration selected from a dropdown

Product

The summary metrics on a test case page now respond to a date range picker, with presets for the last 24 hours, 7 days, 30 days and 12 months, plus a custom range. The selection lives in the URL, so a link to a test case carries the window you were looking at.

Narrowing the window surfaces a test that has passed for months and started failing this week, which a longer average is slow to reflect.

Summary metrics for a test case in the Tuist dashboard, scoped to the last 30 days with a date range picker

Product

The quarantined tests page now shows a sortable "Quarantined at" column, and the flaky tests page a "Marked flaky at" column. Both timestamps come from the test's event history and reflect when the test entered its current state — re-quarantining a test starts a new period, and the timestamp always describes the same event as the "Quarantined by" attribution next to it.

Sorting by these columns lets you review the lists chronologically: surface the tests that have been parked the longest and are overdue for a fix, or check what was quarantined most recently and why.

Product

A target's output can depend on more than its source files and declared dependencies. Code-generation templates, tool versions, configuration files, and environment variables can all change the resulting binary without previously changing Tuist's target hash. This could cause Tuist to reuse a stale cached artifact.

Targets can now declare these values through additionalHashingInputs:

swift
let hashingInputs: [Target.HashingInput] = [
    .glob("Templates/Model.stencil"),
    .glob("Codegen"),
    .glob("Config/**/*.json"),
    .environmentVariable("FEATURE_CONFIGURATION"),
    .string("generator-v2"),
    .script("codegen --version"),
]

Pass the array to Target.target(additionalHashingInputs:). Exact files, directories, and glob patterns contribute their contents. Environment variables contribute their current value, strings contribute their literal value, and scripts contribute their standard output. When any declared input changes, the target hash changes and Tuist rebuilds the artifact instead of restoring stale cached output.

OSS

Noora is now available as standards-based web components. They share the design tokens, visual language, and component behavior used by Noora’s Phoenix LiveView components, while remaining usable from plain Hypertext Markup Language or any browser framework.

Install @tuist/noora from the Node Package Manager package registry, then import the design tokens and component registration bundle once in your browser entry point:

javascript
import "@tuist/noora/tokens.css";
import "@tuist/noora/web-components";

For a quick browser-only prototype, a content delivery network such as jsDelivr can serve the same published files directly from the Node Package Manager package:

html
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@tuist/noora@latest/priv/static/tokens.css"
/>
<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@tuist/noora@latest/priv/static/noora-web-components.js"
></script>

The @latest addresses are convenient for prototypes. Pin an exact published version when using Noora in production.

You can then use Noora custom elements directly:

html
<noora-button variant="primary">Create project</noora-button>

Interactive components use standard browser events where they fit and documented noora-* custom events for structured interactions. For example:

javascript
document.querySelector("noora-select").addEventListener("noora-select", (event) => {
  console.log(event.detail.value);
});

The package also includes TypeScript declarations, a Custom Elements Manifest, and generated reference documentation for attributes, properties, slots, styling parts, and events.

Product

A longer resource timeout can keep a slow but progressing cache download from falling back to a source build. Until now, however, every failed request still used three fixed retries. Raising the timeout could therefore turn one stalled transfer into up to four long waits, with no way to adjust that tradeoff.

Tuist now exposes the shared Hypertext Transfer Protocol (HTTP) retry policy through two environment variables:

  • TUIST_HTTP_MAXIMUM_RETRY_COUNT controls how many retries follow the initial attempt. It defaults to 3, setting it to 0 disables retries, and values above 10 are capped at 10.
  • TUIST_HTTP_RETRY_BASE_DELAY_IN_MILLISECONDS controls the initial exponential backoff delay. It defaults to 100 milliseconds and values above 30000 are capped at 30000 milliseconds.

The base delay now correctly starts at 100 milliseconds rather than roughly one millisecond. Each retry doubles the delay and adds up to one base delay of random jitter, helping clients avoid retrying in lockstep after a shared network failure. Individual retry delays never exceed 30 seconds.

Self-hosted operators can now pair a longer resource timeout with fewer attempts, or increase the base delay to spread retries during congestion. Invalid values fall back to the defaults.

Product

Until now, the only way to get a run's dashboard URLs out of a CI job was to read them off the log. On GitHub Actions the job summary covered it, but everywhere else you were left matching the log text with a regex, which quietly breaks whenever the wording or the URL shape changes.

tuist test, tuist xcodebuild test, and tuist xcodebuild build now take a --run-report-path (or TUIST_RUN_REPORT_PATH), and write a JSON report of the run to it:

json
{
  "runId": "...",
  "status": "success",
  "runURL": "https://tuist.dev/acme/app/runs/123",
  "testRunURL": "https://tuist.dev/acme/app/tests/456",
  "buildRunURL": "https://tuist.dev/acme/app/builds/789",
  "testRuns": [
    { "scheme": "App", "succeeded": true, "totalTests": 10, "skippedTests": 2, "ranTests": 8, "failedTestNames": [] }
  ],
  "buildRuns": [{ "scheme": "App", "succeeded": true, "durationInSeconds": 432 }]
}

Every URL the run produced is included, so a command that both builds and tests gives you both links — something the log could never do, because it only ever printed one of them.

On GitLab, that makes the links available to the rest of the pipeline:

yaml
test:
  script:
    - tuist test --run-report-path tuist-run.json
    - echo "TUIST_TEST_RUN_URL=$(jq -r '.testRunURL // empty' tuist-run.json)" >> run.env
  artifacts:
    reports:
      dotenv: run.env

The report is a supported format, documented in the Continuous Integration guide, rather than something you reverse-engineer from the logs.

Product

Tuist now supports the complete auth.md authentication lifecycle for coding agents. An unauthenticated agent can discover instructions from the Tuist deployment it is connecting to, register, ask you to approve a six-digit code in Tuist, and receive a scoped credential after approval.

The flow works with Claude Code, Codex, OpenCode, and Pi when they connect to Tuist's Model Context Protocol server. Credentials expire automatically, can be revoked, and remain bound to the Tuist deployment that issued them.

Once connected, an agent can list the accounts available to you, create the correct project, guide a Gradle integration, and verify remote-cache behavior through Tuist before reporting that the setup is complete.

Product

tuist install now uses SwifterPM by default to fetch and restore your Swift package dependencies. Resolution stays with the Swift Package Manager, while restoration links checkouts back to a global content-addressable store instead of copying them into every worktree. Warm restores drop to sub-second, and you stop paying for gigabytes of duplicated checkouts across worktrees.

This used to be opt-in through TUIST_USE_SWIFTERPM=1; now it's on by default. If you hit a package graph SwifterPM doesn't handle yet, set TUIST_USE_SWIFTERPM=0 to fall back to SwiftPM, and please open an issue so we can fix it.

Read more about how SwifterPM works in the announcement blog post.