1
1
import { select } from '@inquirer/prompts'
2
- import CodeBlockWriter from 'code-block-writer'
3
2
import * as commander from 'commander'
4
3
import { Template , TemplateBuilder , TemplateFinal } from 'e2b'
5
4
import * as fs from 'fs'
5
+ import Handlebars from 'handlebars'
6
6
import * as path from 'path'
7
7
import { E2BConfig , getConfigPath , loadConfig } from 'src/config'
8
8
import { defaultDockerfileName } from 'src/docker/constants'
@@ -43,116 +43,114 @@ interface TemplateJSON {
43
43
} >
44
44
}
45
45
46
- /**
47
- * Convert the JSON representation from Template.toJSON() to TypeScript code
48
- */
49
- function jsonToTypeScript (
50
- json : TemplateJSON ,
51
- alias : string ,
52
- cpuCount ?: number ,
53
- memoryMB ?: number
54
- ) : { templateContent : string ; buildContent : string } {
55
- const templateWriter = new CodeBlockWriter ( { indentNumberOfSpaces : 2 } )
46
+ // Register Handlebars helpers
47
+ Handlebars . registerHelper ( 'eq' , function ( a : any , b : any , options : any ) {
48
+ if ( a === b ) {
49
+ // @ts -ignore - this context is provided by Handlebars
50
+ return options . fn ( this )
51
+ }
52
+ return ''
53
+ } )
56
54
57
- // Template file
58
- templateWriter . writeLine ( "import { Template } from 'e2b'" )
59
- templateWriter . blankLine ( )
60
- templateWriter . write ( 'export const template = Template()' )
55
+ Handlebars . registerHelper ( 'escapeQuotes' , function ( str ) {
56
+ return str ? str . replace ( / ' / g, "\\'" ) : str
57
+ } )
61
58
62
- // Handle base image or template
63
- if ( json . fromImage ) {
64
- templateWriter . newLine ( ) . indent ( ) . write ( `.fromImage('${ json . fromImage } ')` )
65
- } else {
66
- throw new Error ( 'Unsupported template Dockerfile' )
67
- }
59
+ Handlebars . registerHelper ( 'escapeDoubleQuotes' , function ( str ) {
60
+ return str ? str . replace ( / " / g, '\\"' ) : str
61
+ } )
62
+
63
+ // Transform template JSON data for Handlebars
64
+ function transformTemplateData ( json : TemplateJSON ) {
65
+ const transformedSteps : any [ ] = [ ]
68
66
69
- // Process steps
70
67
for ( const step of json . steps ) {
71
68
switch ( step . type ) {
72
- case 'WORKDIR' :
73
- templateWriter
74
- . newLine ( )
75
- . indent ( )
76
- . write ( `.setWorkdir('${ step . args [ 0 ] } ')` )
77
- break
78
- case 'USER' :
79
- templateWriter . newLine ( ) . indent ( ) . write ( `.setUser('${ step . args [ 0 ] } ')` )
80
- break
81
69
case 'ENV' : {
82
- const envs : Record < string , string > = { }
70
+ // Keep all environment variables from one ENV instruction together
71
+ const envVars : Record < string , string > = { }
83
72
for ( let i = 0 ; i < step . args . length ; i += 2 ) {
84
73
if ( i + 1 < step . args . length ) {
85
- envs [ step . args [ i ] ] = step . args [ i + 1 ]
74
+ envVars [ step . args [ i ] ] = step . args [ i + 1 ]
86
75
}
87
76
}
88
- if ( Object . keys ( envs ) . length > 0 ) {
89
- templateWriter
90
- . newLine ( )
91
- . setIndentationLevel ( 1 )
92
- . write ( '.setEnvs(' )
93
- . inlineBlock ( ( ) => {
94
- for ( const [ key , value ] of Object . entries ( envs ) ) {
95
- templateWriter . write ( `'${ key } ': '${ value } ',` )
96
- }
97
- } )
98
- . write ( ')' )
99
- . setIndentationLevel ( 0 )
100
- }
101
- break
102
- }
103
- case 'RUN' : {
104
- templateWriter . newLine ( ) . indent ( ) . write ( `.runCmd('${ step . args [ 0 ] } ')` )
77
+ transformedSteps . push ( {
78
+ type : 'ENV' ,
79
+ envVars,
80
+ } )
105
81
break
106
82
}
107
- case 'COPY' :
83
+ case 'COPY' : {
108
84
if ( step . args . length >= 2 ) {
109
85
const src = step . args [ 0 ]
110
86
let dest = step . args [ step . args . length - 1 ]
111
- // Normalize empty or . destinations
112
87
if ( ! dest || dest === '' ) {
113
88
dest = '.'
114
89
}
115
- templateWriter . newLine ( ) . indent ( ) . write ( `.copy('${ src } ', '${ dest } ')` )
90
+ transformedSteps . push ( {
91
+ type : 'COPY' ,
92
+ src,
93
+ dest,
94
+ } )
116
95
}
117
96
break
97
+ }
118
98
default :
119
- // For unsupported instructions, add a comment
120
- templateWriter
121
- . newLine ( )
122
- . indent ( )
123
- . write ( `// UNSUPPORTED: ${ step . type } ${ step . args . join ( ' ' ) } ` )
99
+ transformedSteps . push ( {
100
+ type : step . type ,
101
+ args : step . args ,
102
+ } )
124
103
}
125
104
}
126
105
127
- // Handle start and ready commands from config
128
- if ( json . startCmd && json . readyCmd ) {
129
- const startCmd = json . startCmd . replace ( / ' / g, "\\'" )
130
- const readyCmd = json . readyCmd . replace ( / ' / g, "\\'" )
131
- templateWriter
132
- . newLine ( )
133
- . indent ( )
134
- . write ( `.setStartCmd('${ startCmd } ', '${ readyCmd } ')` )
135
- } else if ( json . readyCmd ) {
136
- const readyCmd = json . readyCmd . replace ( / ' / g, "\\'" )
137
- templateWriter . newLine ( ) . indent ( ) . write ( `.setReadyCmd('${ readyCmd } ')` )
106
+ return {
107
+ ...json ,
108
+ steps : transformedSteps ,
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Convert the JSON representation from Template.toJSON() to TypeScript code
114
+ */
115
+ function jsonToTypeScript (
116
+ json : TemplateJSON ,
117
+ alias : string ,
118
+ cpuCount ?: number ,
119
+ memoryMB ?: number
120
+ ) : { templateContent : string ; buildContent : string } {
121
+ const transformedData = transformTemplateData ( json )
122
+
123
+ // Load and compile templates
124
+ // When running from dist/index.js, templates are in dist/templates/
125
+ const templatesDir = path . join ( __dirname , 'templates' )
126
+ const templateSource = fs . readFileSync (
127
+ path . join ( templatesDir , 'typescript-template.hbs' ) ,
128
+ 'utf8'
129
+ )
130
+ const buildSource = fs . readFileSync (
131
+ path . join ( templatesDir , 'typescript-build.hbs' ) ,
132
+ 'utf8'
133
+ )
134
+
135
+ const templateTemplate = Handlebars . compile ( templateSource )
136
+ const buildTemplate = Handlebars . compile ( buildSource )
137
+
138
+ // Generate content
139
+ const templateData = {
140
+ ...transformedData ,
138
141
}
139
142
140
- // Generate build script
141
- const buildWriter = new CodeBlockWriter ( { indentNumberOfSpaces : 2 } )
143
+ const templateContent = templateTemplate ( templateData )
142
144
143
- buildWriter . writeLine ( "import { Template } from 'e2b'" )
144
- buildWriter . writeLine ( "import { template } from './template'" )
145
- buildWriter . blankLine ( )
146
- buildWriter . write ( 'await Template.build(template, ' ) . inlineBlock ( ( ) => {
147
- buildWriter . writeLine ( `alias: '${ alias } ',` )
148
- if ( cpuCount ) buildWriter . writeLine ( `cpuCount: ${ cpuCount } ,` )
149
- if ( memoryMB ) buildWriter . writeLine ( `memoryMB: ${ memoryMB } ,` )
145
+ const buildContent = buildTemplate ( {
146
+ alias,
147
+ cpuCount,
148
+ memoryMB,
150
149
} )
151
- buildWriter . write ( ')' )
152
150
153
151
return {
154
- templateContent : templateWriter . toString ( ) ,
155
- buildContent : buildWriter . toString ( ) ,
152
+ templateContent : templateContent . trim ( ) ,
153
+ buildContent : buildContent . trim ( ) ,
156
154
}
157
155
}
158
156
@@ -166,135 +164,38 @@ function jsonToPython(
166
164
memoryMB ?: number ,
167
165
isAsync : boolean = false
168
166
) : { templateContent : string ; buildContent : string } {
169
- const templateWriter = new CodeBlockWriter ( {
170
- indentNumberOfSpaces : 4 ,
171
- useTabs : false ,
172
- } )
173
- const templateImportClass = isAsync ? 'AsyncTemplate' : 'Template'
174
-
175
- // Template file
176
- templateWriter . writeLine ( `from e2b import ${ templateImportClass } ` )
177
- templateWriter . blankLine ( )
178
- templateWriter . write ( 'template = (' )
179
- templateWriter . newLine ( ) . indent ( ) . write ( `${ templateImportClass } ()` )
180
-
181
- // Handle base image or template
182
- if ( json . fromImage ) {
183
- templateWriter . newLine ( ) . indent ( ) . write ( `.from_image("${ json . fromImage } ")` )
184
- } else {
185
- throw new Error ( 'Unsupported template Dockerfile' )
186
- }
187
-
188
- // Process steps
189
- for ( const step of json . steps ) {
190
- switch ( step . type ) {
191
- case 'WORKDIR' :
192
- templateWriter
193
- . newLine ( )
194
- . indent ( )
195
- . write ( `.set_workdir("${ step . args [ 0 ] } ")` )
196
- break
197
- case 'USER' :
198
- templateWriter . newLine ( ) . indent ( ) . write ( `.set_user("${ step . args [ 0 ] } ")` )
199
- break
200
- case 'ENV' : {
201
- templateWriter . newLine ( ) . indent ( ) . write ( '.set_envs({' )
202
- for ( let i = 0 ; i < step . args . length ; i += 2 ) {
203
- if ( i + 1 < step . args . length ) {
204
- templateWriter
205
- . newLine ( )
206
- . indent ( 2 )
207
- . write ( `"${ step . args [ i ] } ": "${ step . args [ i + 1 ] } ",` )
208
- }
209
- }
210
- templateWriter . newLine ( ) . indent ( ) . write ( '})' )
211
- break
212
- }
213
- case 'RUN' : {
214
- templateWriter . newLine ( ) . indent ( ) . write ( `.run_cmd("${ step . args [ 0 ] } ")` )
215
- break
216
- }
217
- case 'COPY' :
218
- if ( step . args . length >= 2 ) {
219
- const src = step . args [ 0 ]
220
- let dest = step . args [ step . args . length - 1 ]
221
- // Normalize empty or . destinations
222
- if ( ! dest || dest === '' ) {
223
- dest = '.'
224
- }
225
- templateWriter . newLine ( ) . indent ( ) . write ( `.copy("${ src } ", "${ dest } ")` )
226
- }
227
- break
228
- default :
229
- // For unsupported instructions, add a comment
230
- templateWriter
231
- . newLine ( )
232
- . indent ( )
233
- . write ( `// UNSUPPORTED: ${ step . type } ${ step . args . join ( ' ' ) } ` )
234
- }
235
- }
236
-
237
- // Handle start and ready commands from config
238
- if ( json . startCmd && json . readyCmd ) {
239
- const startCmd = json . startCmd . replace ( / " / g, '\\"' )
240
- const readyCmd = json . readyCmd . replace ( / " / g, '\\"' )
241
- templateWriter
242
- . newLine ( )
243
- . indent ( )
244
- . write ( `.set_start_cmd("${ startCmd } ", "${ readyCmd } ")` )
245
- } else if ( json . readyCmd ) {
246
- const readyCmd = json . readyCmd . replace ( / " / g, '\\"' )
247
- templateWriter . newLine ( ) . indent ( ) . write ( `.set_ready_cmd("${ readyCmd } ")` )
248
- }
167
+ const transformedData = transformTemplateData ( json )
168
+
169
+ // Load and compile templates
170
+ // When running from dist/index.js, templates are in dist/templates/
171
+ const templatesDir = path . join ( __dirname , 'templates' )
172
+ const templateSource = fs . readFileSync (
173
+ path . join ( templatesDir , 'python-template.hbs' ) ,
174
+ 'utf8'
175
+ )
176
+ const buildSource = fs . readFileSync (
177
+ path . join ( templatesDir , `python-build-${ isAsync ? 'async' : 'sync' } .hbs` ) ,
178
+ 'utf8'
179
+ )
249
180
250
- templateWriter . writeLine ( ')' )
181
+ const templateTemplate = Handlebars . compile ( templateSource )
182
+ const buildTemplate = Handlebars . compile ( buildSource )
251
183
252
- // Generate build script
253
- const buildWriter = new CodeBlockWriter ( {
254
- indentNumberOfSpaces : 4 ,
255
- useTabs : false ,
184
+ // Generate content
185
+ const templateContent = templateTemplate ( {
186
+ ... transformedData ,
187
+ isAsync ,
256
188
} )
257
189
258
- if ( isAsync ) {
259
- buildWriter . writeLine ( 'import asyncio' )
260
- buildWriter . writeLine ( `from e2b import ${ templateImportClass } ` )
261
- buildWriter . writeLine ( 'from template import template' )
262
- buildWriter . blankLine ( )
263
- buildWriter . blankLine ( )
264
- buildWriter . writeLine ( 'async def main():' )
265
- buildWriter . setIndentationLevel ( 1 )
266
- buildWriter . writeLine ( `await ${ templateImportClass } .build(` )
267
- buildWriter . setIndentationLevel ( 2 )
268
- buildWriter . writeLine ( 'template,' )
269
- buildWriter . writeLine ( `alias="${ alias } ",` )
270
- if ( cpuCount ) buildWriter . writeLine ( `cpu_count=${ cpuCount } ,` )
271
- if ( memoryMB ) buildWriter . writeLine ( `memory_mb=${ memoryMB } ,` )
272
- buildWriter . setIndentationLevel ( 1 )
273
- buildWriter . writeLine ( ')' )
274
- buildWriter . blankLine ( )
275
- buildWriter . blankLine ( )
276
- buildWriter . setIndentationLevel ( 0 )
277
- buildWriter . writeLine ( 'if __name__ == "__main__":' )
278
- buildWriter . setIndentationLevel ( 1 )
279
- buildWriter . writeLine ( 'asyncio.run(main())' )
280
- } else {
281
- buildWriter . writeLine ( `from e2b import ${ templateImportClass } ` )
282
- buildWriter . writeLine ( 'from template import template' )
283
- buildWriter . blankLine ( )
284
- buildWriter . blankLine ( )
285
- buildWriter . writeLine ( `${ templateImportClass } .build(` )
286
- buildWriter . setIndentationLevel ( 1 )
287
- buildWriter . writeLine ( 'template,' )
288
- buildWriter . writeLine ( `alias="${ alias } ",` )
289
- if ( cpuCount ) buildWriter . writeLine ( `cpu_count=${ cpuCount } ,` )
290
- if ( memoryMB ) buildWriter . writeLine ( `memory_mb=${ memoryMB } ,` )
291
- buildWriter . setIndentationLevel ( 0 )
292
- buildWriter . writeLine ( ')' )
293
- }
190
+ const buildContent = buildTemplate ( {
191
+ alias,
192
+ cpuCount,
193
+ memoryMB,
194
+ } )
294
195
295
196
return {
296
- templateContent : templateWriter . toString ( ) ,
297
- buildContent : buildWriter . toString ( ) ,
197
+ templateContent : templateContent . trim ( ) ,
198
+ buildContent : buildContent . trim ( ) ,
298
199
}
299
200
}
300
201
0 commit comments