Repository navigation
Expand file tree
/
Copy pathturbo.json
More file actions
255 lines (243 loc) · 12.3 KB
/
Copy pathturbo.json
File metadata and controls
255 lines (243 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
{
"$schema": "https://turborepo.com/schema.json",
// Default to `stream` so CI and scripted runs emit readable, greppable,
// linear logs. The interactive `dev` task needs Turbo's Terminal UI instead
// (`interactive: true` is REJECTED outright under stream), so the root `dev`
// script passes `--ui=tui` rather than making TUI global — global TUI would
// degrade every CI log for the benefit of one task.
"ui": "stream",
// The shared config packages MUST be here. A `workspace:*` dependency puts the
// package in the dependency graph, but its FILE CONTENTS are not part of a
// consumer task's hash — so editing packages/tsconfig/base.json or
// packages/eslint-config/base.js left ansari-api#typecheck and #lint at the
// SAME hash, and a warm cache would replay stale results against the new
// config. Green run, no signal, wrong answer: exactly the failure this spec
// exists to prevent.
//
// Verified in both directions: with these entries an edit moves the hash; with
// them removed it does not. Re-prove that if you touch this list.
"globalDependencies": [
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"packages/tsconfig/*.json",
"packages/eslint-config/*.js",
// Same rule as the config packages above (issue #59), for apps/auth#typecheck
// and #lint specifically. Those tasks declare no `^`-prefixed dependency
// (typecheck.dependsOn is ["gen:types"], lint none), so turbo does NOT walk
// apps/auth's internal deps when hashing them — and a `workspace:*` dep's file
// contents are not hashed on their own. Without this line, editing the auth
// library (@ansari/auth) leaves apps/auth#typecheck/#lint at the SAME hash and
// replays a stale result.
//
// apps/auth#build does NOT need it — build.dependsOn includes "^build", whose
// caret makes turbo traverse dependency package hashes, so build already busts
// on @ansari/auth edits. Tested both directions per task: with this entry,
// editing packages/auth/src moves apps/auth#typecheck's hash; without it, the
// typecheck hash is unchanged (build's changes either way). @ansari/auth's own
// lint/typecheck/test run on its own files regardless.
//
// Trade-off (same one accepted for globalEnv above): a globalDependencies entry
// is GLOBAL, so editing packages/auth/src invalidates EVERY task's cache repo-wide
// (verified: @ansari/eslint-config#build's hash moves too), not just apps/auth's.
// A slightly colder cache is the accepted cost of never replaying a stale result.
"packages/auth/src/**",
// Same rule for @ansari/types (spec 300): it now carries RUNTIME code, the
// X-Ansari-Client parser that apps/api and packages/auth (so apps/auth)
// import. test/typecheck/lint do not walk `workspace:*` deps, so without
// this entry editing the parser left ansari-api#typecheck at the same hash
// and a warm cache replayed a stale result. Verified both directions.
"packages/types/src/**"
],
// Declared GLOBALLY rather than per-task, deliberately.
//
// Turborepo 2.x defaults to STRICT env mode: a task's child process sees only
// the variables declared here or in its own `env`. apps/api reads these through
// `config`'s getters (lib/config.ts), which Zod-validate on first property
// ACCESS — so an undeclared variable does not fail at import, it fails later on
// whichever code path first touches `config`. One task can pass while another
// dies on the same missing var.
//
// Listing them once, globally, structurally prevents the "forgot to add env to
// task X" bug: during development this config shipped with `env` on build/test/
// typecheck but NOT on `dev`, and `pnpm dev` crashed with
// "Environment validation failed". Per-task lists are repetition, and repetition
// is where omissions hide. The cost of declaring globally is a slightly colder
// cache (a DATABASE_URL change also invalidates the frontend); the cost of
// omitting one is a crash or, worse, a cache key that ignores a value baked
// into the artifact.
//
// DERIVED, NOT GUESSED. To re-derive, ALL FOUR of these are required. Each was
// added because skipping it lost a real variable: step 3 is how
// FACILITATOR_REQUEST_BUDGET_MS / FACILITATOR_SYNTHESIS_RESERVE_MS were missed,
// and step 4 is how SENTRY_AUTH_TOKEN was missed:
// 1. The Zod schemas: apps/api/lib/config.ts AND packages/auth/src/env.ts
// (apps/auth's env; apps/auth reads nothing else).
// 2. STATIC reads: grep -rn "process\.env\.[A-Z]" apps packages
// (DATABASE_URL in both drizzle configs, NEXT_RUNTIME, NODE_ENV,
// RAILWAY_ENVIRONMENT, SENTRY_DSN, VITEST)
// 3. DYNAMIC reads: grep -rn "process\.env\[" apps — then follow each
// call site to the string literals passed in. A static `process.env.X`
// pattern CANNOT see `process.env[name]`, and the failure is silent: the
// override is simply ignored and the default applies.
// 4. DOCUMENTED env our source never names — cross-check the
// docs/self-hosting.md env table and apps/api/.env.example against this
// list. Some variables are consumed by DEPENDENCIES at build time and
// appear nowhere in our code: SENTRY_AUTH_TOKEN is read by
// withSentryConfig for source-map uploads during `next build`, so steps
// 1-3 are all blind to it by construction. Documentation is the
// authority for this class.
// Plus the frontend's EXPO_PUBLIC_*, which Dockerfile.web bakes into the JS
// bundle at export time — they MUST be in the cache key or a cached build can
// ship the wrong API URL while reporting a cache hit.
"globalEnv": [
"DATABASE_URL",
// Better Auth service (issue #59: apps/auth + packages/auth). Under strict
// env mode an undeclared var is invisible to tasks AND absent from the cache
// key, so a cached apps/auth build could ship the wrong secret/URL while
// reporting a hit. Declared globally for the same reason as everything else
// here — per-task lists are where omissions hide.
"BETTER_AUTH_SECRET",
"BETTER_AUTH_URL",
"CORS_ORIGIN",
// apps/auth <-> apps/api (spec 300). The issuer, audience and layer origin
// are in BOTH Zod schemas; AUTH_URL (apps/auth's public origin) is
// apps/api's. There is no shared secret, internal port or rate-limit mode
// (owner amendment 2026-10-17). The legacy JWT_SECRET / ACCESS_ /
// REFRESH_TOKEN_EXPIRY_HOURS left with the legacy auth code (phase 9).
"AUTH_URL",
"AUTH_JWT_ISSUER",
"AUTH_JWT_AUDIENCE",
"AUTH_LAYER_ORIGIN",
"GEMINI_API_KEY",
"GEMINI_MODEL",
"GEMINI_FALLBACK_MODEL",
"GOOGLE_CLOUD_PROJECT",
"GOOGLE_CLOUD_LOCATION",
"GOOGLE_APPLICATION_CREDENTIALS",
"GOOGLE_APPLICATION_CREDENTIALS_JSON",
"TINKER_API_KEY",
// Inkling model id + completion cap + timeout (issue #90): in the Zod
// schema (derivation step 1). Undeclared, the staging LoRA override would
// be invisible to tasks AND absent from the cache key — the default would
// silently apply while a cached build reports a hit.
"INKLING_MODEL",
"INKLING_MAX_TOKENS",
"INKLING_TIMEOUT_MS",
// Primary backend switch (issue #95): also in the Zod schema.
"PRIMARY_BACKEND",
"INKLING_BASE_URL",
"INKLING_API_KEY",
"KALEMAT_API_KEY",
"USUL_API_TOKEN",
"USUL_BASE_URL",
// FRONTEND_URL, MARKETMAKER_URL and RESEND_API_KEY below are apps/auth's
// since spec 300 (packages/auth env). Neither schema has an admin list any
// more (owner amendment 2026-10-17).
"LEADERBOARD_API_KEY",
"MAINTENANCE_MODE",
"IOS_MINIMUM_BUILD_VERSION",
"IOS_LATEST_BUILD_VERSION",
"ANDROID_MINIMUM_BUILD_VERSION",
"ANDROID_LATEST_BUILD_VERSION",
"NODE_ENV",
"FRONTEND_URL",
"MARKETMAKER_URL",
"NEXT_RUNTIME",
"RAILWAY_ENVIRONMENT",
"RESEND_API_KEY",
"SENTRY_DSN",
// Build-time only, consumed by withSentryConfig (not by our source) for
// source-map uploads during `next build`. Documented in
// docs/self-hosting.md; invisible to any grep of our own code.
"SENTRY_AUTH_TOKEN",
"VITEST",
// Read dynamically via process.env[name] in lib/facilitator/agent.ts —
// invisible to a static process.env.X grep.
"FACILITATOR_REQUEST_BUDGET_MS",
"FACILITATOR_SYNTHESIS_RESERVE_MS",
"EXPO_PUBLIC_*",
"EXPO_NO_TELEMETRY",
"PORT"
],
"tasks": {
// uniwind codegen. `uniwind-types.d.ts` is GITIGNORED, so it must be declared
// as an output: on a warm cache Turbo skips this task, and with no declared
// output there would be nothing to restore — green on a cold cache, broken on
// a warm one.
//
// CAVEAT on the narrowed `inputs`: correct only while `global.css` pulls in
// nothing repo-local. Its `@source` currently points into node_modules, which
// the pnpm-lock.yaml globalDependency already covers. The day someone adds a
// local `@import`/`@source`, this task goes STALE-SILENT — edits to that file
// will not bust the cache and the generated dts will quietly lag. If that
// happens, add the file here or fall back to `$TURBO_DEFAULT$`.
"gen:types": {
"inputs": ["src/global.css", "package.json"],
"outputs": ["uniwind-types.d.ts"]
},
"lint": {
"outputs": []
},
// Real graph edge, replacing the old `pnpm run gen:types && tsc` shell chain.
//
// RACE WARNING: do NOT run `typecheck` and `build` in a SINGLE turbo
// invocation for ansari-api. apps/api/tsconfig.json includes
// `.next/types/**/*.ts`, which a concurrent `next build` rewrites underneath
// `tsc` — an intermittent `typecheck exited (2)` that passes on re-run. CI is
// unaffected because it runs them as separate sequential steps; keep it that
// way, and run them as separate commands locally too. This predates the task
// graph (the same overlap existed with parallel pnpm runs) but turbo makes
// concurrency the default, so it surfaces more often.
"typecheck": {
"dependsOn": ["gen:types"],
"outputs": []
},
// `.env.ci` is listed explicitly for documentation value only: it is
// git-tracked, so `$TURBO_DEFAULT$` already covers it. Naming it makes the
// dependency obvious to a reader — but it is NOT an extra safety net, and it
// would not help if the file were ever gitignored.
"test": {
"inputs": ["$TURBO_DEFAULT$", ".env.ci"],
"outputs": []
},
// CI runs test:coverage, NOT test — it must be a real task or CI has to
// bypass the task graph entirely.
"test:coverage": {
"inputs": ["$TURBO_DEFAULT$", ".env.ci"],
"outputs": ["coverage/**"]
},
"build": {
"dependsOn": ["^build", "gen:types"],
"inputs": ["$TURBO_DEFAULT$", ".env.ci"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
// REACHABILITY NOTE: this task is effectively unreachable through the graph.
// The frontend's `build` script shells straight to `pnpm run build:web`, which
// bypasses Turbo — so ordering for graph runs comes from `build`'s own
// dependsOn above, not from here. The config below applies only to a DIRECT
// `turbo run build:web`. It is kept for that case and for discoverability;
// do not rely on it to order anything during `turbo run build`.
//
// apps/frontend/package.json keeps `pnpm run gen:types &&` inside build:web on
// purpose: Dockerfile.web installs with --filter (no root devDependencies, so
// no turbo in that image) and calls build:web directly. Under the graph that
// chain is a duplicate uniwind run — idempotent and sub-second, not a cache
// hit — and it is what keeps the container path working.
"build:web": {
"dependsOn": ["gen:types"],
"outputs": ["dist/**"]
},
// `interactive: true` keeps stdin attached so the Expo TUI still responds to
// its keypress commands (i / a / w / r) when multiplexed alongside next dev.
// It REQUIRES Turbo's Terminal UI and therefore a real TTY — the root `dev`
// script passes --ui=tui. Consequence: `pnpm dev` needs an actual terminal
// and will refuse to run headless. That is correct for an interactive dev
// command; scripted/headless callers should target a specific app instead,
// e.g. `pnpm api dev`.
"dev": {
"cache": false,
"persistent": true,
"interactive": true
}
}
}