Skip to content

Commit 6ef4fb1

Browse files
committed
feat: add build v2 command that uses template SDK
1 parent c4ce97e commit 6ef4fb1

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import * as boxen from 'boxen'
2+
import * as commander from 'commander'
3+
import { Template, TemplateClass } from 'e2b'
4+
import { connectionConfig, ensureAccessToken, ensureAPIKey } from 'src/api'
5+
import {
6+
defaultDockerfileName,
7+
fallbackDockerfileName,
8+
} from 'src/docker/constants'
9+
import { pathOption } from 'src/options'
10+
import { getRoot } from 'src/utils/filesystem'
11+
import {
12+
asFormattedSandboxTemplate,
13+
asLocal,
14+
asLocalRelative,
15+
asPrimary,
16+
asPython,
17+
asTypescript,
18+
withDelimiter,
19+
} from 'src/utils/format'
20+
import { getDockerfile } from './build'
21+
22+
export const buildV2Command = new commander.Command('build-v2')
23+
.description(
24+
`build sandbox template using the E2B Template SDK. This command reads a Dockerfile and builds it directly without requiring ${asLocal(
25+
'e2b.toml'
26+
)} configuration files.`
27+
)
28+
.argument(
29+
'<template-name>',
30+
'template name to create or rebuild. The template name must be lowercase and contain only letters, numbers, dashes and underscores.'
31+
)
32+
.addOption(pathOption)
33+
.option(
34+
'-d, --dockerfile <file>',
35+
`specify path to Dockerfile. By default E2B tries to find ${asLocal(
36+
defaultDockerfileName
37+
)} or ${asLocal(fallbackDockerfileName)} in root directory.`
38+
)
39+
.option(
40+
'-c, --cmd <start-command>',
41+
'specify command that will be executed when the sandbox is started.'
42+
)
43+
.option(
44+
'--ready-cmd <ready-command>',
45+
'specify command that will need to exit 0 for the template to be ready.'
46+
)
47+
.option(
48+
'--cpu-count <cpu-count>',
49+
'specify the number of CPUs that will be used to run the sandbox. The default value is 2.',
50+
parseInt
51+
)
52+
.option(
53+
'--memory-mb <memory-mb>',
54+
'specify the amount of memory in megabytes that will be used to run the sandbox. Must be an even number. The default value is 512.',
55+
parseInt
56+
)
57+
.option('--no-cache', 'skip cache when building the template.')
58+
.alias('bd-v2')
59+
.action(
60+
async (
61+
templateName: string,
62+
opts: {
63+
path?: string
64+
dockerfile?: string
65+
cmd?: string
66+
readyCmd?: string
67+
cpuCount?: number
68+
memoryMb?: number
69+
noCache?: boolean
70+
}
71+
) => {
72+
try {
73+
// Ensure we have access token
74+
ensureAccessToken()
75+
process.stdout.write('\n')
76+
77+
// Validate template name
78+
if (!/^[a-z0-9-_]+$/.test(templateName)) {
79+
console.error(
80+
`Template name ${asLocal(
81+
templateName
82+
)} is not valid. Template name can only contain lowercase letters, numbers, dashes and underscores.`
83+
)
84+
process.exit(1)
85+
}
86+
87+
// Validate memory
88+
if (opts.memoryMb && opts.memoryMb % 2 !== 0) {
89+
console.error(
90+
`The memory in megabytes must be an even number. You provided ${asLocal(
91+
opts.memoryMb.toFixed(0)
92+
)}.`
93+
)
94+
process.exit(1)
95+
}
96+
97+
const root = getRoot(opts.path)
98+
99+
// Use options directly
100+
const dockerfile = opts.dockerfile
101+
const startCmd = opts.cmd
102+
const readyCmd = opts.readyCmd
103+
const cpuCount = opts.cpuCount
104+
const memoryMB = opts.memoryMb
105+
106+
// Get Dockerfile content
107+
const { dockerfileContent, dockerfileRelativePath } = getDockerfile(
108+
root,
109+
dockerfile
110+
)
111+
112+
console.log(
113+
`Found ${asLocalRelative(
114+
dockerfileRelativePath
115+
)} that will be used to build the sandbox template.`
116+
)
117+
118+
// Initialize template builder with file context and parse Dockerfile
119+
const baseTemplate = Template({
120+
fileContextPath: root,
121+
}).fromDockerfile(dockerfileContent)
122+
123+
// Apply start/ready commands if provided
124+
let finalTemplate: TemplateClass = baseTemplate
125+
if (startCmd && readyCmd) {
126+
finalTemplate = baseTemplate.setStartCmd(startCmd, readyCmd)
127+
} else if (readyCmd) {
128+
finalTemplate = baseTemplate.setReadyCmd(readyCmd)
129+
} else {
130+
console.error('Both start and ready commands must be provided.')
131+
process.exit(1)
132+
}
133+
134+
console.log('\nBuilding sandbox template...\n')
135+
136+
// Prepare API credentials for SDK
137+
const apiKey = ensureAPIKey()
138+
const domain = connectionConfig.domain
139+
140+
// Build the template using SDK
141+
try {
142+
await Template.build(finalTemplate, {
143+
alias: templateName,
144+
cpuCount: cpuCount,
145+
memoryMB: memoryMB,
146+
skipCache: opts.noCache,
147+
apiKey: apiKey,
148+
domain: domain,
149+
onBuildLogs: (logEntry) => console.log(logEntry.toString()),
150+
})
151+
} catch (error) {
152+
console.error('\n❌ Template build failed.')
153+
if (error instanceof Error) {
154+
console.error('Error:', error.message)
155+
}
156+
process.exit(1)
157+
}
158+
159+
// Display success message with examples
160+
const pythonExample = asPython(`from e2b import Sandbox, AsyncSandbox
161+
162+
# Create sync sandbox
163+
sandbox = Sandbox.create("${templateName}")
164+
165+
# Create async sandbox
166+
sandbox = await AsyncSandbox.create("${templateName}")`)
167+
168+
const typescriptExample = asTypescript(`import { Sandbox } from 'e2b'
169+
170+
// Create sandbox
171+
const sandbox = await Sandbox.create('${templateName}')`)
172+
173+
const examplesMessage = `You can now use the template to create custom sandboxes.\nLearn more on ${asPrimary(
174+
'https://e2b.dev/docs'
175+
)}`
176+
177+
const exampleHeader = boxen.default(examplesMessage, {
178+
padding: {
179+
bottom: 1,
180+
top: 1,
181+
left: 2,
182+
right: 2,
183+
},
184+
margin: {
185+
top: 1,
186+
bottom: 1,
187+
left: 0,
188+
right: 0,
189+
},
190+
fullscreen(width) {
191+
return [width, 0]
192+
},
193+
float: 'left',
194+
})
195+
196+
const exampleUsage = `${withDelimiter(
197+
pythonExample,
198+
'Python SDK'
199+
)}\n${withDelimiter(typescriptExample, 'JS SDK', true)}`
200+
201+
console.log(
202+
`\n✅ Building sandbox template ${asFormattedSandboxTemplate({
203+
templateID: templateName,
204+
})} finished.\n${exampleHeader}\n${exampleUsage}\n`
205+
)
206+
207+
process.exit(0)
208+
} catch (err: any) {
209+
console.error(err)
210+
process.exit(1)
211+
}
212+
}
213+
)

packages/cli/src/commands/template/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { deleteCommand } from './delete'
77
import { publishCommand, unPublishCommand } from './publish'
88
import { migrateCommand } from './migrate'
99
import { initV2Command } from './init-v2'
10+
import { buildV2Command } from './build-v2'
1011

1112
export const templateCommand = new commander.Command('template')
1213
.description('manage sandbox templates')
1314
.alias('tpl')
1415
.addCommand(buildCommand)
16+
.addCommand(buildV2Command)
1517
.addCommand(listCommand)
1618
.addCommand(initCommand)
1719
.addCommand(initV2Command)

0 commit comments

Comments
 (0)