@@ -12,67 +12,30 @@ import { resolveSourceFiles } from "../utilities/sourceFiles.js";
1212import { execOptionsForRuntime } from "@trigger.dev/core/v3/build" ;
1313import { writeJSONFile } from "../utilities/fileSystem.js" ;
1414
15- /**
16- * The managed index controller runs inside the build container at deploy time
17- * and indexes the user's tasks. It supports two modes:
18- *
19- * 1. **Online mode (default, used by Trigger.dev cloud)**:
20- * - Fetches environment variables from the API via `CliApiClient`.
21- * - Registers the resulting BackgroundWorker via the API.
22- * - Reports indexing failures via `failDeployment`.
23- *
24- * 2. **Offline mode (opt-in via `TRIGGER_INDEX_OFFLINE=1` build arg)**:
25- * - Skips the API entirely; no env vars are fetched, no
26- * BackgroundWorker is registered, no failures are reported.
27- * - Writes `index-metadata.json` (or `index-error.json` on failure)
28- * to the working directory inside the build container. The multi-stage
29- * Containerfile copies them into the final image so downstream tooling
30- * can read them out of the runtime image (and on failure the indexer
31- * process exits non-zero, failing the build).
32- * - Intended for self-hosted setups that drive the build via
33- * `trigger.dev/internal`'s `buildImage({ offlineIndex: true })`
34- * without API credentials in the build environment.
35- *
36- * Mode is selected by the `TRIGGER_INDEX_OFFLINE=1` env var.
37- */
38-
3915async function loadBuildManifest ( ) {
4016 const manifestContents = await readFile ( "./build.json" , "utf-8" ) ;
4117 const raw = JSON . parse ( manifestContents ) ;
4218
4319 return BuildManifest . parse ( raw ) ;
4420}
4521
46- // In offline mode (TRIGGER_INDEX_OFFLINE=1) the bootstrap skips API client
47- // construction entirely. Downstream code treats `cliApiClient === undefined`
48- // as the signal that we're running offline.
49- type BootstrapResult = {
50- buildManifest : BuildManifest ;
51- cliApiClient ?: CliApiClient ;
52- projectRef ?: string ;
53- deploymentId ?: string ;
54- } ;
55-
56- /**
57- * Returns the same shape as `cliApiClient.getEnvironmentVariables` for the
58- * offline path. We never have project env vars at index time in offline mode
59- * (the build container has no API access), so it's just an empty `variables`
60- * map wrapped in the success envelope so downstream code can branch once on
61- * `$env.success`.
62- */
63- const offlineEnvShim = ( ) =>
64- ( { success : true as const , data : { variables : { } as Record < string , string > } } ) ;
65-
66- async function bootstrap ( ) : Promise < BootstrapResult > {
22+ async function bootstrap ( ) {
6723 const buildManifest = await loadBuildManifest ( ) ;
6824
69- // Offline mode: API access is unavailable. The artifacts produced by
70- // indexDeployment are baked into the final image by the Containerfile.
25+ // Offline mode (TRIGGER_INDEX_OFFLINE=1): swap in a CliApiClient shim that
26+ // writes the same payloads to disk that the real client would have sent
27+ // over the wire. indexDeployment is unchanged — it just gets a different
28+ // implementation of the same interface.
7129 if ( env . TRIGGER_INDEX_OFFLINE === "1" ) {
72- return { buildManifest } ;
30+ return {
31+ buildManifest,
32+ cliApiClient : createOfflineCliApiClient ( ) ,
33+ // The shim ignores these but the shape needs to match.
34+ projectRef : env . TRIGGER_PROJECT_REF ?? "offline" ,
35+ deploymentId : env . TRIGGER_DEPLOYMENT_ID ?? "offline" ,
36+ } ;
7337 }
7438
75- // Online mode (default): use the API for env vars and registration.
7639 if ( typeof env . TRIGGER_API_URL !== "string" ) {
7740 console . error ( "TRIGGER_API_URL is not set" ) ;
7841 process . exit ( 1 ) ;
@@ -102,6 +65,8 @@ async function bootstrap(): Promise<BootstrapResult> {
10265 } ;
10366}
10467
68+ type BootstrapResult = Awaited < ReturnType < typeof bootstrap > > ;
69+
10570async function indexDeployment ( {
10671 cliApiClient,
10772 projectRef,
@@ -112,10 +77,7 @@ async function indexDeployment({
11277 const stderr : string [ ] = [ ] ;
11378
11479 try {
115- const $env =
116- cliApiClient && projectRef
117- ? await cliApiClient . getEnvironmentVariables ( projectRef )
118- : offlineEnvShim ( ) ;
80+ const $env = await cliApiClient . getEnvironmentVariables ( projectRef ) ;
11981
12082 if ( ! $env . success ) {
12183 throw new Error ( `Failed to fetch environment variables: ${ $env . error } ` ) ;
@@ -149,41 +111,6 @@ async function indexDeployment({
149111 const buildPlatform = process . env . BUILDPLATFORM ;
150112 const targetPlatform = process . env . TARGETPLATFORM ;
151113
152- if ( ! cliApiClient || ! deploymentId ) {
153- // Offline mode: write metadata to disk; the multi-stage Containerfile
154- // copies it into the final image where downstream tooling reads it.
155- const indexMetadata = {
156- contentHash : buildManifest . contentHash ,
157- packageVersion : buildManifest . packageVersion ,
158- cliPackageVersion : buildManifest . cliPackageVersion ,
159- tasks : workerManifest . tasks ,
160- queues : workerManifest . queues ,
161- sourceFiles,
162- runtime : workerManifest . runtime ,
163- runtimeVersion : workerManifest . runtimeVersion ,
164- buildPlatform,
165- targetPlatform,
166- } ;
167-
168- console . log ( "Writing index-metadata.json" ) ;
169-
170- await writeFile (
171- join ( process . cwd ( ) , "index-metadata.json" ) ,
172- JSON . stringify ( indexMetadata , null , 2 )
173- ) ;
174-
175- console . log (
176- JSON . stringify ( {
177- message : "Indexing completed (offline mode)" ,
178- buildPlatform,
179- targetPlatform,
180- taskCount : workerManifest . tasks . length ,
181- } )
182- ) ;
183- return ;
184- }
185-
186- // Online mode: register the BackgroundWorker via the API.
187114 const backgroundWorkerBody : CreateBackgroundWorkerRequestBody = {
188115 localOnly : true ,
189116 metadata : {
@@ -233,20 +160,71 @@ async function indexDeployment({
233160
234161 console . error ( "Failed to index deployment" , serialiedIndexError ) ;
235162
236- if ( cliApiClient && deploymentId ) {
237- await cliApiClient . failDeployment ( deploymentId , { error : serialiedIndexError } ) ;
238- } else {
239- // Offline mode: write error to disk so downstream tooling can surface it.
240- await writeFile (
241- join ( process . cwd ( ) , "index-error.json" ) ,
242- JSON . stringify ( { error : serialiedIndexError } , null , 2 )
243- ) ;
244- }
163+ await cliApiClient . failDeployment ( deploymentId , { error : serialiedIndexError } ) ;
245164
246165 process . exit ( 1 ) ;
247166 }
248167}
249168
169+ /**
170+ * Stub `CliApiClient` for offline indexing (TRIGGER_INDEX_OFFLINE=1).
171+ *
172+ * indexDeployment makes three calls on the API client:
173+ *
174+ * 1. `getEnvironmentVariables(projectRef)` — returns an empty `variables`
175+ * map. The build container has no API access in offline mode, so we
176+ * can't fetch project env vars; the indexer runs with `{}`.
177+ * 2. `createDeploymentBackgroundWorker(deploymentId, body)` — writes the
178+ * flattened body to `index-metadata.json`. Downstream tooling (e.g.
179+ * a register-only Job in the cluster) re-issues this payload to the
180+ * real API.
181+ * 3. `failDeployment(deploymentId, body)` — writes the error to
182+ * `index-error.json`.
183+ *
184+ * The multi-stage Containerfile copies these files into the final image so
185+ * downstream tooling reads them out of the runtime image.
186+ *
187+ * Cast through `unknown` because `CliApiClient` is a concrete class with
188+ * private fields and methods we don't need to stub. indexDeployment only
189+ * touches the three methods above.
190+ */
191+ function createOfflineCliApiClient ( ) : CliApiClient {
192+ return {
193+ async getEnvironmentVariables ( ) {
194+ return { success : true as const , data : { variables : { } as Record < string , string > } } ;
195+ } ,
196+ async createDeploymentBackgroundWorker (
197+ _deploymentId : string ,
198+ body : CreateBackgroundWorkerRequestBody
199+ ) {
200+ const indexMetadata = {
201+ ...body . metadata ,
202+ buildPlatform : body . buildPlatform ,
203+ targetPlatform : body . targetPlatform ,
204+ } ;
205+ await writeFile (
206+ join ( process . cwd ( ) , "index-metadata.json" ) ,
207+ JSON . stringify ( indexMetadata , null , 2 )
208+ ) ;
209+ return {
210+ success : true as const ,
211+ data : {
212+ id : "offline" ,
213+ version : "offline" ,
214+ contentHash : body . metadata . contentHash ,
215+ } ,
216+ } ;
217+ } ,
218+ async failDeployment ( _deploymentId : string , body : { error : unknown } ) {
219+ await writeFile (
220+ join ( process . cwd ( ) , "index-error.json" ) ,
221+ JSON . stringify ( body , null , 2 )
222+ ) ;
223+ return { success : true as const , data : { id : "offline" } } ;
224+ } ,
225+ } as unknown as CliApiClient ;
226+ }
227+
250228const results = await bootstrap ( ) ;
251229
252230await indexDeployment ( results ) ;
0 commit comments