mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-02 10:05:41 +02:00
* perf(ci): split frontend build and test onto separate runners (MUL-5347) The frontend job is the critical path of every CI run that touches web code: p50 576s, versus 267s for backend. 94% of it is a single `turbo build typecheck lint test` invocation. Profiling showed the cost is contention, not inefficient tasks. A standard runner has 4 vCPUs, and the job scheduled two CPU-saturating tasks onto them concurrently: `@multica/views:test` (259 jsdom files) and `@multica/web:build` (a webpack production build). Measured against the same suites running with the machine to themselves: @multica/views:test 104s owning 4 cores -> 500s sharing them @multica/web:build 26s owning 10 cores -> 342s sharing 4 Split the work across two runners so neither starves the other. The split is weighted rather than even, because the halves are not close: the test group costs ~575 CPU-seconds and everything else ~250, so tests get a runner alone. Also drop `^typecheck` from the `test` and `lint` task definitions. No package in this repo emits build artifacts -- core/ui/views export raw .ts that the consumer transpiles -- so that edge ordered tasks without producing anything they consumed, and left the heaviest task in the graph idle for ~38s while core/ui ran `tsc --noEmit`. Removing it also drops three redundant typecheck tasks from the test job, taking it from 7 tasks / 798 CPU-seconds to 4 / 575. Type errors still fail CI through the `typecheck` task, which frontend-build owns. `frontend` is retained as an aggregate gate so the existing status-check name keeps reporting, including the established behaviour that it goes green rather than pending on PRs the path filter excludes. Co-authored-by: multica-agent <github@multica.ai> * perf(ci): cache turbo task results across runs (MUL-5347) Every CI run rebuilt all 16 frontend tasks cold -- turbo reported `Remote caching disabled` / `Cached: 0 cached, 16 total` on every run, because only the pnpm store was cached. Persist turbo's filesystem cache with actions/cache instead. No Vercel remote-cache credentials exist in this repo, so this is the local cache keyed per job. Measured, simulating a fresh checkout against a warm cache: frontend-build 54.7s -> 184ms (12/12 cached) frontend-test 115.7s -> 50ms (7/7 cached) Cache footprint is 20MB for the build job and ~60KB for the test job -- `test` declares no outputs, so turbo stores exit codes and logs, yet a hit still skips the most expensive task in the graph. Turning the cache on promotes two latent hashing bugs into real ones, so both are fixed here: `build` declared narrow `inputs` globs that matched neither `content/**/*.mdx` (compiled by fumadocs-mdx during the build), `public/**` assets, nor `package.json` / `tsconfig.json`. Editing any of them left the task hash byte-identical, which is inert when nothing is ever restored and a stale-build bug the moment something is. Dropped in favour of turbo's default input set. `test` regains its `^typecheck` edge, reverting part of the previous commit. That change was justified on the basis that the edge only imposed ordering, which was true without a cache: a turbo task hash covers a workspace dependency's sources only if a task edge reaches them, so with no edge, editing packages/views or packages/ui left `@multica/web#test` and `@multica/desktop#test` unchanged in hash and a cached pass would be replayed over changed code. `typecheck` is the only task every package defines, so it is the edge that closes the gap; `^test` would miss packages/ui, which has no test script. `lint` keeps no edge -- eslint here is not type-aware, so it cannot observe a dependency's sources. Also adds .github/workflows/ci.yml to globalDependencies: it pins the Node version, and without it a toolchain bump would replay results produced by the old runtime behind a green check. Co-authored-by: multica-agent <github@multica.ai> * fix(ci): scope turbo cache to the resolved runtime, drop typecheck from tests (MUL-5347) Addresses review feedback on the caching commit. Must-fix: the cache was not isolated by interpreter. `node-version: 22` floats across patch releases and turbo's global hash does not include the interpreter at all (`engines` is null in its dry-run cache inputs), so a runner moving to another 22.x would leave the workflow text and every task hash unchanged, restore the old cache through `restore-keys` and report green without executing anything. Both jobs now resolve `node --version` into a step output and carry it, plus `runner.arch`, in the cache key and the restore prefix. Must-fix: `actions/cache@v4` runs on the deprecated Node 20 runtime and was being force-migrated to Node 24 with a warning on every run. Moved to v6 -- the review suggested v5, but v6.1.0 is current and satisfies the same runner floor (>= 2.327.1; hosted runners are on 2.335.1). `test` no longer depends on `^typecheck`. It needs dependency sources in its hash, not a type check: a new hash-only `cache-inputs` transit task carries them. No package implements that script, so every node resolves to <NONEXISTENT> and nothing executes, while the edges still pull each dependency's files into the hash. Declared recursively so the chain survives a package gaining a purely transitive dependency. This returns the ~223 CPU-seconds the previous commit gave up: the cold test job goes from 7 tasks / 115.7s back to 4 tasks / 67.2s, and editing packages/ui still correctly re-runs the views, web and desktop suites while core:test stays cached. Corrects a comment that claimed `^test` would miss packages/ui because it has no test script. It would not -- turbo materialises a <NONEXISTENT> node that participates in hashing, verified against the dry graph. `^test` is still the wrong edge here, but because it serialises the suites behind each other, not for the stated reason. Also drops the stale note that tests no longer depend on `^typecheck`, and swaps the aggregate gate's `always()` for `!cancelled()` so a run superseded by a newer push does not spend a runner on a verdict nobody reads. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
72 lines
2.9 KiB
JSON
72 lines
2.9 KiB
JSON
{
|
|
"$schema": "https://turbo.build/schema.json",
|
|
// The CI workflow pins the toolchain these tasks run under (Node 22 today).
|
|
// Without it in the global hash, bumping that version would replay cached
|
|
// typecheck/test results produced by the old runtime and hide an
|
|
// incompatibility behind a green check.
|
|
"globalDependencies": [".github/workflows/ci.yml"],
|
|
"globalEnv": [
|
|
"DATABASE_URL",
|
|
"PORT",
|
|
"FRONTEND_PORT",
|
|
"FRONTEND_ORIGIN",
|
|
"NEXT_PUBLIC_API_URL",
|
|
"NEXT_PUBLIC_WS_URL",
|
|
"MULTICA_SERVER_URL",
|
|
"DOCS_URL",
|
|
"COMPOSE_PROJECT_NAME",
|
|
"POSTGRES_DB",
|
|
"POSTGRES_PORT",
|
|
"DESKTOP_RENDERER_PORT",
|
|
"DESKTOP_APP_SUFFIX"
|
|
],
|
|
"tasks": {
|
|
"build": {
|
|
"dependsOn": ["^build"],
|
|
// No explicit `inputs`: turbo's default (every git-tracked file in the
|
|
// package) is the only safe hash source now that the cache is actually
|
|
// restored in CI. The previous narrow glob list matched neither
|
|
// `content/**/*.mdx` (compiled by fumadocs-mdx during the build),
|
|
// `public/**` assets, nor `package.json` / `tsconfig.json`, so the task
|
|
// hash was unchanged by edits to any of them -- harmless while nothing
|
|
// was ever cached, a stale-build bug the moment it is.
|
|
"outputs": [".next/**", "!.next/cache/**", "dist/**", "out/**"]
|
|
},
|
|
"dev": {
|
|
"cache": false,
|
|
"persistent": true
|
|
},
|
|
"dev:staging": {
|
|
"cache": false,
|
|
"persistent": true
|
|
},
|
|
"typecheck": {
|
|
"dependsOn": ["^typecheck"]
|
|
},
|
|
// Hash-only transit task. No package implements a `cache-inputs` script,
|
|
// so every node resolves to <NONEXISTENT> and nothing is ever executed --
|
|
// but the edges still pull each dependency's file hashes into whatever
|
|
// depends on it. `dependsOn: ["^cache-inputs"]` makes that recursive, so
|
|
// the chain stays complete if a package ever gains a purely transitive
|
|
// dependency. This exists because a turbo task hash covers a workspace
|
|
// dependency's sources only when a task edge reaches them.
|
|
"cache-inputs": {
|
|
"dependsOn": ["^cache-inputs"]
|
|
},
|
|
// Without a dependency edge, editing packages/views or packages/ui left
|
|
// `@multica/web#test` and `@multica/desktop#test` byte-identical in hash,
|
|
// so a cached pass would be replayed over changed code. `^cache-inputs`
|
|
// closes that without side effects: `^typecheck` would also work but drags
|
|
// three real `tsc --noEmit` runs (~223 CPU-seconds) into the test job, and
|
|
// `^test` would serialise the suites behind each other.
|
|
"test": {
|
|
"dependsOn": ["^cache-inputs"]
|
|
},
|
|
// `lint` keeps no dependency edge: eslint here is not type-aware (no
|
|
// `projectService` / `parserOptions.project` anywhere in
|
|
// packages/eslint-config), so it only ever reads its own package's files
|
|
// and a dependency's source cannot change its result.
|
|
"lint": {}
|
|
}
|
|
}
|