Skip to content

Commit 74f4d76

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

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

.changeset/selfish-bags-refuse.md

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

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)