@@ -13,10 +13,12 @@ import {
1313} from "@codesandbox/sdk" ;
1414import { VmUpdateSpecsRequest } from "../../api-clients/client" ;
1515import { getDefaultTemplateId , retryWithDelay } from "../../utils/api" ;
16- import { getInferredApiKey } from "../../utils/constants" ;
16+ import { getInferredApiKey , getInferredRegistryUrl , isBetaAllowed , isLocalEnvironment } from "../../utils/constants" ;
1717import { hashDirectory as getFilePaths } from "../utils/files" ;
1818import { mkdir , writeFile } from "fs/promises" ;
1919import { sleep } from "../../utils/sleep" ;
20+ import { buildDockerImage , prepareDockerBuild , pushDockerImage } from "../utils/docker" ;
21+ import { randomUUID } from "crypto" ;
2022
2123export type BuildCommandArgs = {
2224 directory : string ;
@@ -125,6 +127,12 @@ export const buildCommand: yargs.CommandModule<
125127 describe : "Relative path to log file, if any" ,
126128 type : "string" ,
127129 } )
130+ . option ( "beta" , {
131+ describe : "Use the beta Docker build process" ,
132+ type : "boolean" ,
133+ // TOOD: Remove after releasing to customers as beta feature
134+ hidden : true , // Do not show this flag in help
135+ } )
128136 . positional ( "directory" , {
129137 describe : "Path to the project that we'll create a snapshot from" ,
130138 type : "string" ,
@@ -174,6 +182,17 @@ export const buildCommand: yargs.CommandModule<
174182 } ) ,
175183
176184 handler : async ( argv ) => {
185+
186+ // Beta build process using Docker
187+ // This uses the new architecture using bartender and gvisor
188+ if ( argv . beta && isBetaAllowed ( ) ) {
189+ return betaCodeSandboxBuild ( argv ) ;
190+ } else if ( argv . beta && ! isBetaAllowed ( ) ) {
191+ console . error ( "The beta flag is not yet available for your account." ) ;
192+ process . exit ( 1 ) ;
193+ }
194+
195+ // Existing build process
177196 const apiKey = getInferredApiKey ( ) ;
178197 const api = new API ( { apiKey, instrumentation : instrumentedFetch } ) ;
179198 const sdk = new CodeSandbox ( apiKey ) ;
@@ -234,8 +253,7 @@ export const buildCommand: yargs.CommandModule<
234253 spinner . start (
235254 updateSpinnerMessage (
236255 index ,
237- `Running setup ${ steps . indexOf ( step ) + 1 } / ${
238- steps . length
256+ `Running setup ${ steps . indexOf ( step ) + 1 } / ${ steps . length
239257 } - ${ step . name } ...`
240258 )
241259 ) ;
@@ -448,9 +466,9 @@ export const buildCommand: yargs.CommandModule<
448466 argv . ci
449467 ? String ( error )
450468 : "Failed, please manually verify at https://codesandbox.io/s/" +
451- id +
452- " - " +
453- String ( error )
469+ id +
470+ " - " +
471+ String ( error )
454472 )
455473 ) ;
456474
@@ -605,3 +623,201 @@ function createAlias(directory: string, alias: string) {
605623 alias,
606624 } ;
607625}
626+
627+ /**
628+ * Build a CodeSandbox Template using Docker for use in gvisor-based sandboxes.
629+ * @param argv arguments to csb build command
630+ */
631+ export async function betaCodeSandboxBuild ( argv : yargs . ArgumentsCamelCase < BuildCommandArgs > ) : Promise < void > {
632+ let dockerFileCleanupFn : ( ( ) => Promise < void > ) | undefined ;
633+ let client : SandboxClient | undefined ;
634+
635+ try {
636+ const apiKey = getInferredApiKey ( ) ;
637+ const api = new API ( { apiKey, instrumentation : instrumentedFetch } ) ;
638+ const sdk = new CodeSandbox ( apiKey ) ;
639+ const sandboxTier = argv . vmTier
640+ ? VMTier . fromName ( argv . vmTier )
641+ : VMTier . Micro ;
642+
643+ const resolvedDirectory = path . resolve ( argv . directory ) ;
644+
645+ const registry = getInferredRegistryUrl ( ) ;
646+ const repository = "templates" ;
647+ const imageName = `image-${ randomUUID ( ) . toLowerCase ( ) } ` ;
648+ const tag = "latest" ;
649+ const fullImageName = `${ registry } /${ repository } /${ imageName } :${ tag } ` ;
650+
651+ let architecture = "amd64" ;
652+ // For dev environments with arm64 (Apple Silicon), use arm64 architecture
653+ if ( process . arch === "arm64" && isLocalEnvironment ( ) ) {
654+ console . log ( "Using arm64 architecture for Docker build" ) ;
655+ architecture = "arm64" ;
656+ }
657+
658+ // Prepare Docker Build
659+ const dockerBuildPrepareSpinner = ora ( { stream : process . stdout } ) ;
660+ dockerBuildPrepareSpinner . start ( "Preparing build environment..." ) ;
661+
662+ let dockerfilePath : string ;
663+
664+ try {
665+ const result = await prepareDockerBuild ( resolvedDirectory , ( output : string ) => {
666+ dockerBuildPrepareSpinner . text = `Preparing build environment: (${ output } )` ;
667+ } ) ;
668+ dockerFileCleanupFn = result . cleanupFn ;
669+ dockerfilePath = result . dockerfilePath ;
670+
671+ dockerBuildPrepareSpinner . succeed ( "Build environment ready." ) ;
672+ } catch ( error ) {
673+ dockerBuildPrepareSpinner . fail ( `Failed to prepare build environment: ${ ( error as Error ) . message } ` ) ;
674+ throw error ;
675+ }
676+
677+
678+ // Docker Build
679+ const dockerBuildSpinner = ora ( { stream : process . stdout } ) ;
680+ dockerBuildSpinner . start ( "Building template docker image..." ) ;
681+ try {
682+ await buildDockerImage ( {
683+ dockerfilePath,
684+ imageName : fullImageName ,
685+ context : resolvedDirectory ,
686+ architecture,
687+ onOutput : ( output : string ) => {
688+ const cleanOutput = stripAnsiCodes ( output ) ;
689+ dockerBuildSpinner . text = `Building template Docker image: (${ cleanOutput } )` ;
690+ } ,
691+ } ) ;
692+ } catch ( error ) {
693+ dockerBuildSpinner . fail ( `Failed to build template Docker image: ${ ( error as Error ) . message } ` ) ;
694+ throw error ;
695+ }
696+ dockerBuildSpinner . succeed ( "Template Docker image built successfully." ) ;
697+
698+ // Push Docker Image
699+ const imagePushSpinner = ora ( { stream : process . stdout } ) ;
700+ imagePushSpinner . start ( "Pushing template Docker image to CodeSandbox..." ) ;
701+ try {
702+ await pushDockerImage (
703+ fullImageName ,
704+ ( output : string ) => {
705+ const cleanOutput = stripAnsiCodes ( output ) ;
706+ imagePushSpinner . text = `Pushing template Docker image to CodeSandbox: (${ cleanOutput } )` ;
707+ } ,
708+ ) ;
709+ } catch ( error ) {
710+ imagePushSpinner . fail ( `Failed to push template Docker image: ${ ( error as Error ) . message } ` ) ;
711+ throw error ;
712+ }
713+ imagePushSpinner . succeed ( "Template Docker image pushed to CodeSandbox." ) ;
714+
715+
716+ // Create Template with Docker Image
717+ const templateData = await api . createTemplate ( {
718+ forkOf : argv . fromSandbox || getDefaultTemplateId ( api . getClient ( ) ) ,
719+ title : argv . name ,
720+ // We filter out sdk-templates on the dashboard
721+ tags : [ "sdk-template" ] ,
722+ // @ts -ignore
723+ image : {
724+ registry : registry ,
725+ repository : "templates" ,
726+ name : imageName ,
727+ tag : "latest" ,
728+ architecture : architecture
729+ } ,
730+ } ) ;
731+
732+ // Create a memory snapshot from the template sandboxes
733+ const templateBuildSpinner = ora ( { stream : process . stdout } ) ;
734+ templateBuildSpinner . start ( "Preparing template snapshot..." ) ;
735+
736+ const sandboxId = templateData . sandboxes [ 0 ] . id ;
737+ try {
738+ templateBuildSpinner . text = "Preparing template snapshot: Starting sandbox to create snapshot..." ;
739+ const sandbox = await sdk . sandboxes . resume ( sandboxId ) ;
740+
741+ templateBuildSpinner . text = "Preparing template snapshot: Connecting to sandbox..." ;
742+ client = await sandbox . connect ( )
743+
744+ if ( argv . ports && argv . ports . length > 0 ) {
745+ templateBuildSpinner . text = `Preparing template snapshot: Waiting for ports ${ argv . ports . join ( ', ' ) } to be ready...` ;
746+ await Promise . all (
747+ argv . ports . map ( async ( port ) => {
748+ if ( ! client ) throw new Error ( 'Failed to connect to sandbox to wait for ports' ) ;
749+ const portInfo = await client . ports . waitForPort ( port , {
750+ timeoutMs : 10_000 ,
751+ } ) ;
752+ } )
753+ ) ;
754+ } else {
755+ templateBuildSpinner . text = `Preparing template snapshot: No ports specified, waiting 10 seconds for tasks to run...` ;
756+ await sleep ( 10000 ) ;
757+ }
758+
759+ templateBuildSpinner . text = "Preparing template snapshot: Sandbox is ready. Creating snapshot..." ;
760+ await sdk . sandboxes . hibernate ( sandboxId ) ;
761+
762+ templateBuildSpinner . succeed ( "Template snapshot created." ) ;
763+
764+ } catch ( error ) {
765+ templateBuildSpinner . text = "Preparing template snapshot: Failed to create snapshot. Cleaning up..." ;
766+ await sdk . sandboxes . shutdown ( sandboxId ) ;
767+ templateBuildSpinner . fail ( `Failed to create template reference and example: ${ ( error as Error ) . message } ` ) ;
768+ throw error ;
769+ }
770+
771+ // Create alias if needed and output final instructions
772+ const templateFinaliseSpinner = ora ( { stream : process . stdout } ) ;
773+ templateFinaliseSpinner . start (
774+ `\n\nCreating template reference and example...`
775+ ) ;
776+ let referenceString ;
777+ let id ;
778+
779+ // Create alias if needed
780+ if ( argv . alias ) {
781+ const alias = createAlias ( resolvedDirectory , argv . alias ) ;
782+ await api . assignVmTagAlias ( alias . namespace , alias . alias , {
783+ tag_id : templateData . tag ,
784+ } ) ;
785+
786+ id = `${ alias . namespace } @${ alias . alias } ` ;
787+ referenceString = `Alias ${ id } now referencing: ${ templateData . tag } ` ;
788+ } else {
789+ id = templateData . tag ;
790+ referenceString = `Template created with tag: ${ templateData . tag } ` ;
791+ }
792+
793+ templateFinaliseSpinner . succeed ( `${ referenceString } \n\n
794+ Create sandbox from template using
795+
796+ SDK:
797+
798+ sdk.sandboxes.create({
799+ id: "${ id } "
800+ })
801+
802+ CLI:
803+
804+ csb sandboxes fork ${ id } \n`
805+
806+ ) ;
807+
808+ process . exit ( 0 ) ;
809+ } catch ( error ) {
810+ console . error ( error ) ;
811+ process . exit ( 1 ) ;
812+ } finally {
813+ // Cleanup temporary Dockerfile if created
814+ if ( dockerFileCleanupFn ) {
815+ await dockerFileCleanupFn ( ) ;
816+ }
817+ if ( client ) {
818+ await client . disconnect ( ) ;
819+ client . dispose ( ) ;
820+ client = undefined ;
821+ }
822+ }
823+ }
0 commit comments