@@ -3,6 +3,11 @@ import { depot } from "@depot/cli";
33import { x } from "tinyexec" ;
44import { BuildManifest , BuildRuntime } from "@trigger.dev/core/v3/schemas" ;
55import { networkInterfaces } from "os" ;
6+ import { join } from "path" ;
7+ import { safeReadJSONFile } from "../utilities/fileSystem.js" ;
8+ import { readFileSync } from "fs" ;
9+ import { isLinux } from "std-env" ;
10+ import { z } from "zod" ;
611
712export interface BuildImageOptions {
813 // Common options
@@ -171,6 +176,8 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
171176 options . imagePlatform ,
172177 "--provenance" ,
173178 "false" ,
179+ "--metadata-file" ,
180+ "metadata.json" ,
174181 "--build-arg" ,
175182 `TRIGGER_PROJECT_ID=${ options . projectId } ` ,
176183 "--build-arg" ,
@@ -196,7 +203,7 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
196203 options . loadImage ? "--load" : undefined ,
197204 ] . filter ( Boolean ) as string [ ] ;
198205
199- logger . debug ( `depot ${ args . join ( " " ) } ` ) ;
206+ logger . debug ( `depot ${ args . join ( " " ) } ` , { cwd : options . cwd } ) ;
200207
201208 // Step 4: Build and push the image
202209 const childProcess = depot ( args , {
@@ -243,7 +250,21 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
243250 } ;
244251 }
245252
246- const digest = extractImageDigest ( errors ) ;
253+ const metadataPath = join ( options . cwd , "metadata.json" ) ;
254+ const rawMetadata = await safeReadJSONFile ( metadataPath ) ;
255+
256+ const meta = BuildKitMetadata . safeParse ( rawMetadata ) ;
257+
258+ let digest : string | undefined ;
259+ if ( ! meta . success ) {
260+ logger . error ( "Failed to parse metadata.json" , {
261+ errors : meta . error . message ,
262+ path : metadataPath ,
263+ } ) ;
264+ } else {
265+ logger . debug ( "Parsed metadata.json" , { metadata : meta . data , path : metadataPath } ) ;
266+ digest = meta . data [ "containerimage.digest" ] ;
267+ }
247268
248269 return {
249270 ok : true as const ,
@@ -290,13 +311,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
290311 const lsLogs : string [ ] = [ ] ;
291312
292313 // List existing builders
293- const lsProcess = x ( "docker" , [ "buildx" , "ls" ] ) ;
314+ const lsProcess = x ( "docker" , [ "buildx" , "ls" , "--format" , "{{.Name}}" ] ) ;
294315 for await ( const line of lsProcess ) {
295316 lsLogs . push ( line ) ;
296317 logger . debug ( line ) ;
297- options . onLog ?.( line ) ;
298318
299- if ( line . startsWith ( builder + " " ) ) {
319+ if ( line === builder ) {
300320 builderExists = true ;
301321 }
302322 }
@@ -398,6 +418,8 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
398418 ? false
399419 : true ;
400420
421+ await ensureQemuRegistered ( options . imagePlatform ) ;
422+
401423 const args = [
402424 "buildx" ,
403425 "build" ,
@@ -412,6 +434,10 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
412434 addHost ? `--add-host=${ addHost } ` : undefined ,
413435 shouldPush ? "--push" : undefined ,
414436 options . loadImage ? "--load" : undefined ,
437+ "--provenance" ,
438+ "false" ,
439+ "--metadata-file" ,
440+ "metadata.json" ,
415441 "--build-arg" ,
416442 `TRIGGER_PROJECT_ID=${ options . projectId } ` ,
417443 "--build-arg" ,
@@ -437,9 +463,7 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
437463 "." , // The build context
438464 ] . filter ( Boolean ) as string [ ] ;
439465
440- logger . debug ( `docker ${ args . join ( " " ) } ` , {
441- cwd : options . cwd ,
442- } ) ;
466+ logger . debug ( `docker ${ args . join ( " " ) } ` , { cwd : options . cwd } ) ;
443467
444468 const errors : string [ ] = [ ] ;
445469
@@ -463,7 +487,21 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
463487 } ;
464488 }
465489
466- const digest = extractImageDigest ( errors ) ;
490+ const metadataPath = join ( options . cwd , "metadata.json" ) ;
491+ const rawMetadata = await safeReadJSONFile ( metadataPath ) ;
492+
493+ const meta = BuildKitMetadata . safeParse ( rawMetadata ) ;
494+
495+ let digest : string | undefined ;
496+ if ( ! meta . success ) {
497+ logger . error ( "Failed to parse metadata.json" , {
498+ errors : meta . error . message ,
499+ path : metadataPath ,
500+ } ) ;
501+ } else {
502+ logger . debug ( "Parsed metadata.json" , { metadata : meta . data , path : metadataPath } ) ;
503+ digest = meta . data [ "containerimage.digest" ] ;
504+ }
467505
468506 // Get the image size
469507 const sizeProcess = x ( "docker" , [ "image" , "inspect" , options . imageTag , "--format={{.Size}}" ] , {
@@ -500,22 +538,6 @@ function extractLogs(outputs: string[]) {
500538 return cleanedOutputs . map ( ( line ) => line . trim ( ) ) . join ( "\n" ) ;
501539}
502540
503- function extractImageDigest ( outputs : string [ ] ) : string | undefined {
504- const imageDigestRegex = / p u s h i n g m a n i f e s t f o r .+ (?< digest > s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } ) / ;
505-
506- for ( const line of outputs ) {
507- const imageDigestMatch = line . match ( imageDigestRegex ) ;
508-
509- const digest = imageDigestMatch ?. groups ?. digest ;
510-
511- if ( digest ) {
512- return digest ;
513- }
514- }
515-
516- return ;
517- }
518-
519541export type GenerateContainerfileOptions = {
520542 runtime : BuildRuntime ;
521543 build : BuildManifest [ "build" ] ;
@@ -819,3 +841,59 @@ function getAddHost(apiUrl: string) {
819841
820842 return ;
821843}
844+
845+ function isQemuRegistered ( ) {
846+ try {
847+ // Check a single QEMU handler
848+ const binfmt = readFileSync ( "/proc/sys/fs/binfmt_misc/qemu-aarch64" , "utf8" ) ;
849+ return binfmt . includes ( "enabled" ) ;
850+ } catch ( e ) {
851+ return false ;
852+ }
853+ }
854+
855+ function isMultiPlatform ( imagePlatform : string ) {
856+ return imagePlatform . split ( "," ) . length > 1 ;
857+ }
858+
859+ async function ensureQemuRegistered ( imagePlatform : string ) {
860+ if ( isLinux && isMultiPlatform ( imagePlatform ) && ! isQemuRegistered ( ) ) {
861+ logger . debug ( "Registering QEMU for multi-platform build..." ) ;
862+
863+ const ensureQemuProcess = x ( "docker" , [
864+ "run" ,
865+ "--rm" ,
866+ "--privileged" ,
867+ "multiarch/qemu-user-static" ,
868+ "--reset" ,
869+ "-p" ,
870+ "yes" ,
871+ ] ) ;
872+
873+ const logs : string [ ] = [ ] ;
874+ for await ( const line of ensureQemuProcess ) {
875+ logger . debug ( line ) ;
876+ logs . push ( line ) ;
877+ }
878+
879+ if ( ensureQemuProcess . exitCode !== 0 ) {
880+ logger . error ( "Failed to register QEMU for multi-platform build" , {
881+ exitCode : ensureQemuProcess . exitCode ,
882+ logs : logs . join ( "\n" ) ,
883+ } ) ;
884+ }
885+ }
886+ }
887+
888+ const BuildKitMetadata = z . object ( {
889+ "buildx.build.ref" : z . string ( ) . optional ( ) ,
890+ "containerimage.descriptor" : z
891+ . object ( {
892+ mediaType : z . string ( ) ,
893+ digest : z . string ( ) ,
894+ size : z . number ( ) ,
895+ } )
896+ . optional ( ) ,
897+ "containerimage.digest" : z . string ( ) . optional ( ) ,
898+ "image.name" : z . string ( ) . optional ( ) ,
899+ } ) ;
0 commit comments