chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,71 @@
import { spawnSync } from 'node:child_process'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
const apiKey = process.env.E2B_API_KEY
const domain = process.env.E2B_DOMAIN || 'e2b.app'
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
const templateName = `cli-create-api-key-test-${Date.now()}`
describe('template create cli backend integration', () => {
let testDir: string
beforeAll(async () => {
if (!apiKey) {
throw new Error(
'E2B_API_KEY must be set to run template create backend tests'
)
}
testDir = await fs.mkdtemp('e2b-create-test-')
await fs.writeFile(
path.join(testDir, 'e2b.Dockerfile'),
'FROM ubuntu:latest\n'
)
})
afterAll(async () => {
if (!testDir) return
runCli(['template', 'delete', '--yes', templateName])
await fs.rm(testDir, { recursive: true, force: true })
})
test(
'template create succeeds with E2B_API_KEY alone (no E2B_ACCESS_TOKEN)',
{ timeout: 300_000 },
() => {
const result = runCli([
'template',
'create',
templateName,
'--path',
testDir,
])
const output = String(result.stdout || '') + String(result.stderr || '')
expect(result.status, output).toBe(0)
// Success marker printed by create.ts on a finished build; the failure
// path prints "❌ Template build failed." instead.
expect(output).toContain('✅ Building sandbox template')
expect(output).not.toContain('❌ Template build failed')
// Auth never fell through to the access-token error box.
expect(output).not.toMatch(/You must be logged in/)
}
)
})
function runCli(args: string[]): ReturnType<typeof spawnSync> {
// Intentionally exclude E2B_ACCESS_TOKEN from the child env so this test
// verifies the API-key-only auth path end-to-end.
return spawnSync('node', [cliPath, ...args], {
env: {
PATH: process.env.PATH,
HOME: process.env.HOME,
E2B_DOMAIN: domain,
E2B_API_KEY: apiKey,
},
encoding: 'utf8',
timeout: 300_000,
})
}
@@ -0,0 +1,68 @@
import * as fs from 'fs/promises'
import * as path from 'path'
import { execSync } from 'child_process'
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
describe('Template Delete --config', () => {
let testDir: string
beforeEach(async () => {
testDir = await fs.mkdtemp('e2b-delete-config-test-')
const defaultConfig = `template_id = "default-template-id"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), defaultConfig)
const customConfig = `template_id = "custom-template-id"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'custom.toml'), customConfig)
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), 'FROM alpine:3.18')
})
afterEach(async () => {
if (testDir) {
await fs.rm(testDir, { recursive: true, force: true })
}
})
test('uses the config file passed via --config even when e2b.toml exists', async () => {
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
let output = ''
try {
output = execSync(
`node "${cliPath}" template delete --yes --path "${testDir}" --config custom.toml 2>&1`,
{ encoding: 'utf-8', stdio: 'pipe', timeout: 10_000 }
)
} catch (err: any) {
output = (err?.stdout ?? '') + (err?.stderr ?? '')
}
expect(output).toContain('Sandbox templates to delete')
expect(output).toContain('custom-template-id')
expect(output).toContain('custom.toml')
expect(output).not.toContain('default-template-id')
})
test('uses the default e2b.toml when --config is not provided', async () => {
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
let output = ''
try {
output = execSync(
`node "${cliPath}" template delete --yes --path "${testDir}" 2>&1`,
{ encoding: 'utf-8', stdio: 'pipe', timeout: 10_000 }
)
} catch (err: any) {
output = (err?.stdout ?? '') + (err?.stderr ?? '')
}
expect(output).toContain('Sandbox templates to delete')
expect(output).toContain('default-template-id')
expect(output).toContain('e2b.toml')
expect(output).not.toContain('custom-template-id')
})
})
@@ -0,0 +1,10 @@
FROM python:3.11-slim
RUN apt-get update && apt-get install -y gcc g++ make libpq-dev && rm -rf /var/lib/apt/lists/*
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
RUN useradd -m -u 1000 appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY app.py .
USER appuser
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:application"]
@@ -0,0 +1,3 @@
template_id = "complex-python"
dockerfile = "e2b.Dockerfile"
template_name = "complex-python-app"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"complex-python-app-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"complex-python-app",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,20 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("python:3.11-slim")
.set_user("root")
.set_workdir("/")
.run_cmd("apt-get update && apt-get install -y gcc g++ make libpq-dev && rm -rf /var/lib/apt/lists/*")
.set_envs({
"PYTHONDONTWRITEBYTECODE": "1",
"PYTHONUNBUFFERED": "1",
})
.run_cmd("useradd -m -u 1000 appuser")
.set_workdir("/app")
.copy("requirements.txt", ".")
.run_cmd("pip install --upgrade pip && pip install -r requirements.txt")
.copy("app.py", ".")
.set_user("appuser")
.set_start_cmd("sudo gunicorn --bind 0.0.0.0:8000 app:application", "sleep 20")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"complex-python-app-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"complex-python-app",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,20 @@
from e2b import Template
template = (
Template()
.from_image("python:3.11-slim")
.set_user("root")
.set_workdir("/")
.run_cmd("apt-get update && apt-get install -y gcc g++ make libpq-dev && rm -rf /var/lib/apt/lists/*")
.set_envs({
"PYTHONDONTWRITEBYTECODE": "1",
"PYTHONUNBUFFERED": "1",
})
.run_cmd("useradd -m -u 1000 appuser")
.set_workdir("/app")
.copy("requirements.txt", ".")
.run_cmd("pip install --upgrade pip && pip install -r requirements.txt")
.copy("app.py", ".")
.set_user("appuser")
.set_start_cmd("sudo gunicorn --bind 0.0.0.0:8000 app:application", "sleep 20")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'complex-python-app-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'complex-python-app', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,18 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('python:3.11-slim')
.setUser('root')
.setWorkdir('/')
.runCmd('apt-get update && apt-get install -y gcc g++ make libpq-dev && rm -rf /var/lib/apt/lists/*')
.setEnvs({
'PYTHONDONTWRITEBYTECODE': '1',
'PYTHONUNBUFFERED': '1',
})
.runCmd('useradd -m -u 1000 appuser')
.setWorkdir('/app')
.copy('requirements.txt', '.')
.runCmd('pip install --upgrade pip && pip install -r requirements.txt')
.copy('app.py', '.')
.setUser('appuser')
.setStartCmd('sudo gunicorn --bind 0.0.0.0:8000 app:application', 'sleep 20')
@@ -0,0 +1,4 @@
FROM alpine:latest
COPY package.json /app/
COPY src/index.js ./src/
COPY config.json /etc/app/config.json
@@ -0,0 +1,2 @@
template_id = "copy-test"
dockerfile = "e2b.Dockerfile"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"copy-test-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"copy-test",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,13 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("alpine:latest")
.set_user("root")
.set_workdir("/")
.copy("package.json", "/app/")
.copy("src/index.js", "./src/")
.copy("config.json", "/etc/app/config.json")
.set_user("user")
.set_workdir("/home/user")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"copy-test-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"copy-test",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,13 @@
from e2b import Template
template = (
Template()
.from_image("alpine:latest")
.set_user("root")
.set_workdir("/")
.copy("package.json", "/app/")
.copy("src/index.js", "./src/")
.copy("config.json", "/etc/app/config.json")
.set_user("user")
.set_workdir("/home/user")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'copy-test-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'copy-test', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,11 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('alpine:latest')
.setUser('root')
.setWorkdir('/')
.copy('package.json', '/app/')
.copy('src/index.js', './src/')
.copy('config.json', '/etc/app/config.json')
.setUser('user')
.setWorkdir('/home/user')
@@ -0,0 +1,3 @@
FROM node:18
WORKDIR /app
COPY server.js .
@@ -0,0 +1,4 @@
template_id = "custom-app"
dockerfile = "e2b.Dockerfile"
start_cmd = "node server.js"
ready_cmd = "curl -f http://localhost:3000/health || exit 1"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"custom-app-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"custom-app",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,12 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("node:18")
.set_user("root")
.set_workdir("/")
.set_workdir("/app")
.copy("server.js", ".")
.set_user("user")
.set_start_cmd("sudo node server.js", "curl -f http://localhost:3000/health || exit 1")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"custom-app-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"custom-app",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,12 @@
from e2b import Template
template = (
Template()
.from_image("node:18")
.set_user("root")
.set_workdir("/")
.set_workdir("/app")
.copy("server.js", ".")
.set_user("user")
.set_start_cmd("sudo node server.js", "curl -f http://localhost:3000/health || exit 1")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'custom-app-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'custom-app', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('node:18')
.setUser('root')
.setWorkdir('/')
.setWorkdir('/app')
.copy('server.js', '.')
.setUser('user')
.setStartCmd('sudo node server.js', 'curl -f http://localhost:3000/health || exit 1')
@@ -0,0 +1 @@
FROM ubuntu:latest
@@ -0,0 +1,2 @@
template_id = "minimal-template"
dockerfile = "e2b.Dockerfile"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"minimal-template-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"minimal-template",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,10 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("ubuntu:latest")
.set_user("root")
.set_workdir("/")
.set_user("user")
.set_workdir("/home/user")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"minimal-template-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"minimal-template",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template
template = (
Template()
.from_image("ubuntu:latest")
.set_user("root")
.set_workdir("/")
.set_user("user")
.set_workdir("/home/user")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'minimal-template-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'minimal-template', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,8 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('ubuntu:latest')
.setUser('root')
.setWorkdir('/')
.setUser('user')
.setWorkdir('/home/user')
@@ -0,0 +1,9 @@
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "index.js"]
@@ -0,0 +1,2 @@
template_id = "multi-stage"
dockerfile = "e2b.Dockerfile"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"multi-stage-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"multi-stage",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,6 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("my-custom-image")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"multi-stage-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"multi-stage",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,6 @@
from e2b import Template
template = (
Template()
.from_image("my-custom-image")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'multi-stage-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'multi-stage', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,4 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('my-custom-image')
@@ -0,0 +1,6 @@
FROM node:18
ENV NODE_ENV=production
ENV PORT 3000
ENV DEBUG=false LOG_LEVEL=info API_URL=https://api.example.com
ENV SINGLE_VAR single_value
WORKDIR /app
@@ -0,0 +1,2 @@
template_id = "env-test"
dockerfile = "e2b.Dockerfile"
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"env-test-dev",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,15 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"env-test",
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,24 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("node:18")
.set_user("root")
.set_workdir("/")
.set_envs({
"NODE_ENV": "production",
})
.set_envs({
"PORT": "3000",
})
.set_envs({
"DEBUG": "false",
"LOG_LEVEL": "info",
"API_URL": "https://api.example.com",
})
.set_envs({
"SINGLE_VAR": "single_value",
})
.set_workdir("/app")
.set_user("user")
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"env-test-dev",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,10 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"env-test",
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,24 @@
from e2b import Template
template = (
Template()
.from_image("node:18")
.set_user("root")
.set_workdir("/")
.set_envs({
"NODE_ENV": "production",
})
.set_envs({
"PORT": "3000",
})
.set_envs({
"DEBUG": "false",
"LOG_LEVEL": "info",
"API_URL": "https://api.example.com",
})
.set_envs({
"SINGLE_VAR": "single_value",
})
.set_workdir("/app")
.set_user("user")
)
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'env-test-dev', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,10 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'env-test', {
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,22 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('node:18')
.setUser('root')
.setWorkdir('/')
.setEnvs({
'NODE_ENV': 'production',
})
.setEnvs({
'PORT': '3000',
})
.setEnvs({
'DEBUG': 'false',
'LOG_LEVEL': 'info',
'API_URL': 'https://api.example.com',
})
.setEnvs({
'SINGLE_VAR': 'single_value',
})
.setWorkdir('/app')
.setUser('user')
@@ -0,0 +1,6 @@
FROM python:3.11
WORKDIR /app
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ENV PYTHONUNBUFFERED=1
CMD ["python", "app.py"]
@@ -0,0 +1,5 @@
template_id = "start-cmd"
dockerfile = "e2b.Dockerfile"
cpu_count = 2
memory_mb = 1024
start_cmd = "node server.js"
@@ -0,0 +1,17 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"start-cmd-dev",
cpu_count=2,
memory_mb=1024,
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,17 @@
import asyncio
from e2b import AsyncTemplate, default_build_logger
from template import template
async def main():
await AsyncTemplate.build(
template,
"start-cmd",
cpu_count=2,
memory_mb=1024,
on_build_logs=default_build_logger(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,16 @@
from e2b import AsyncTemplate
template = (
AsyncTemplate()
.from_image("python:3.11")
.set_user("root")
.set_workdir("/")
.set_workdir("/app")
.run_cmd("pip install --upgrade pip")
.run_cmd("pip install -r requirements.txt")
.set_envs({
"PYTHONUNBUFFERED": "1",
})
.set_user("user")
.set_start_cmd("sudo node server.js", "sleep 20")
)
@@ -0,0 +1,12 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"start-cmd-dev",
cpu_count=2,
memory_mb=1024,
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,12 @@
from e2b import Template, default_build_logger
from template import template
if __name__ == "__main__":
Template.build(
template,
"start-cmd",
cpu_count=2,
memory_mb=1024,
on_build_logs=default_build_logger(),
)
@@ -0,0 +1,16 @@
from e2b import Template
template = (
Template()
.from_image("python:3.11")
.set_user("root")
.set_workdir("/")
.set_workdir("/app")
.run_cmd("pip install --upgrade pip")
.run_cmd("pip install -r requirements.txt")
.set_envs({
"PYTHONUNBUFFERED": "1",
})
.set_user("user")
.set_start_cmd("sudo node server.js", "sleep 20")
)
@@ -0,0 +1,12 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'start-cmd-dev', {
cpuCount: 2,
memoryMB: 1024,
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,12 @@
import { Template, defaultBuildLogger } from 'e2b'
import { template } from './template'
async function main() {
await Template.build(template, 'start-cmd', {
cpuCount: 2,
memoryMB: 1024,
onBuildLogs: defaultBuildLogger(),
});
}
main().catch(console.error);
@@ -0,0 +1,14 @@
import { Template } from 'e2b'
export const template = Template()
.fromImage('python:3.11')
.setUser('root')
.setWorkdir('/')
.setWorkdir('/app')
.runCmd('pip install --upgrade pip')
.runCmd('pip install -r requirements.txt')
.setEnvs({
'PYTHONUNBUFFERED': '1',
})
.setUser('user')
.setStartCmd('sudo node server.js', 'sleep 20')
@@ -0,0 +1,366 @@
import { execSync } from 'child_process'
import { existsSync } from 'fs'
import * as fs from 'fs/promises'
import * as path from 'path'
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
import { Language } from '../../../src/commands/template/generators'
describe('Template Init', () => {
let testDir: string
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
beforeEach(async () => {
// Use Node.js built-in temp directory handling
testDir = await fs.mkdtemp('e2b-init-test-')
})
afterEach(async () => {
// Clean up test directory
if (testDir) {
await fs.rm(testDir, { recursive: true, force: true })
}
})
describe('CLI Options', () => {
Object.values(Language).forEach((language) => {
test(`should generate files with --name and --language ${language}`, async () => {
const templateName = 'my-test-template'
// Run init command with CLI options
execSync(
`node "${cliPath}" template init --name "${templateName}" --language "${language}" --path "${testDir}"`,
{ stdio: 'inherit' }
)
// Verify template directory was created
const templateDir = path.join(testDir, templateName)
expect(existsSync(templateDir)).toBe(true)
// Verify files were created in the template directory
const expectedFiles = getExpectedFiles(language)
for (const file of expectedFiles) {
const filePath = path.join(templateDir, file)
expect(existsSync(filePath)).toBe(true)
// Verify file content is not empty
const content = await fs.readFile(filePath, 'utf8')
expect(content.trim().length).toBeGreaterThan(0)
}
// Verify template name is used in build files
await verifyTemplateNameInBuildFiles(
templateDir,
language,
templateName
)
})
})
test('should validate template name format', async () => {
// Matches the server-side rule in e2b-dev/infra (id.identifierRegex):
// only lowercase letters, numbers, dashes and underscores are allowed.
const invalidNames = [
'invalid space', // contains space
'invalid.dot', // contains a dot
'', // empty
'a'.repeat(129), // exceeds the 128 character limit
]
for (const invalidName of invalidNames) {
expect(() => {
execSync(
`node "${cliPath}" template init --name "${invalidName}" --language "typescript" --path "${testDir}"`,
{ stdio: 'pipe' }
)
}).toThrow()
}
})
test('should validate language parameter', async () => {
expect(() => {
execSync(
`node "${cliPath}" template init --name "test" --language "invalid-lang" --path "${testDir}"`,
{ stdio: 'pipe' }
)
}).toThrow()
})
test('should work with valid template names', async () => {
const validNames = [
'a', // single character
'abc', // simple
'my-template', // with hyphens
'my_template', // with underscores
'my-template_name', // with hyphens and underscores
'test123', // with numbers
'123test', // starting with number
'a-b-c-d-e', // multiple hyphens
'a_b_c_d_e', // multiple underscores
'api-server_v2', // mixed hyphens and underscores
]
for (const validName of validNames) {
// Clean directory before each test
const files = await fs.readdir(testDir)
for (const file of files) {
await fs.rm(path.join(testDir, file), {
recursive: true,
force: true,
})
}
// Should not throw
execSync(
`node "${cliPath}" template init --name "${validName}" --language "typescript" --path "${testDir}"`,
{ stdio: 'pipe' }
)
// Verify template directory was created
const templateDir = path.join(testDir, validName)
expect(existsSync(templateDir)).toBe(true)
// Verify files were created in the template directory
const expectedFiles = getExpectedFiles(Language.TypeScript)
for (const file of expectedFiles) {
expect(existsSync(path.join(templateDir, file))).toBe(true)
}
}
})
})
describe('Package.json Integration', () => {
test('should add scripts to existing package.json', async () => {
// Create a package.json file in the parent directory
const packageJson = {
name: 'test-project',
version: '1.0.0',
scripts: {
test: 'echo "test"',
},
}
const packageJsonPath = path.join(testDir, 'package.json')
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
// Run init command
execSync(
`node "${cliPath}" template init --name "test-template" --language "typescript" --path "${testDir}"`,
{ stdio: 'inherit' }
)
// Verify package.json was updated (it should remain in the parent directory)
const updatedPackageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
)
expect(updatedPackageJson.scripts).toHaveProperty('e2b:build:dev')
expect(updatedPackageJson.scripts).toHaveProperty('e2b:build:prod')
expect(updatedPackageJson.scripts.test).toBe('echo "test"') // existing script preserved
})
test('should work without package.json', async () => {
// Run init command without package.json
execSync(
`node "${cliPath}" template init --name "test-template" --language "typescript" --path "${testDir}"`,
{ stdio: 'inherit' }
)
// Verify template directory was created
const templateDir = path.join(testDir, 'test-template')
expect(existsSync(templateDir)).toBe(true)
// Verify files were created in the template directory
const expectedFiles = getExpectedFiles(Language.TypeScript)
for (const file of expectedFiles) {
expect(existsSync(path.join(templateDir, file))).toBe(true)
}
// Verify package.json was created in the template directory
const createdPackageJSON = JSON.parse(
await fs.readFile(path.join(templateDir, 'package.json'), 'utf8')
)
expect(createdPackageJSON.scripts).toHaveProperty('e2b:build:dev')
expect(createdPackageJSON.scripts).toHaveProperty('e2b:build:prod')
})
})
describe('Makefile Integration', () => {
test('should add scripts to existing Makefile', async () => {
// Create a Makefile file in the parent directory
const makefile = `
.PHONY: build
build/%:
\tCGO_ENABLED=1 go build
`
const makefilePath = path.join(testDir, 'Makefile')
await fs.writeFile(makefilePath, makefile)
// Run init command
execSync(
`node "${cliPath}" template init --name "test-template" --language "python-sync" --path "${testDir}"`,
{ stdio: 'inherit' }
)
// Verify Makefile was updated (it should remain in the parent directory)
const updatedMakefile = await fs.readFile(makefilePath, 'utf8')
expect(updatedMakefile).toContain('e2b:build:dev')
expect(updatedMakefile).toContain('e2b:build:prod')
expect(updatedMakefile).toContain(`.PHONY: build
build/%:
\tCGO_ENABLED=1 go build`) // existing script preserved
})
test('should work without Makefile', async () => {
// Run init command without Makefile
execSync(
`node "${cliPath}" template init --name "test-template" --language "python-sync" --path "${testDir}"`,
{ stdio: 'inherit' }
)
// Verify template directory was created
const templateDir = path.join(testDir, 'test-template')
expect(existsSync(templateDir)).toBe(true)
// Verify files were created in the template directory
const expectedFiles = getExpectedFiles(Language.PythonSync)
for (const file of expectedFiles) {
expect(existsSync(path.join(templateDir, file))).toBe(true)
}
// Verify Makefile was created in the template directory
const createdMakefile = await fs.readFile(
path.join(templateDir, 'Makefile'),
'utf8'
)
expect(createdMakefile).toContain('e2b:build:dev')
expect(createdMakefile).toContain('e2b:build:prod')
})
})
describe('File Content Validation', () => {
test('should generate correct TypeScript template content', async () => {
execSync(
`node "${cliPath}" template init --name "test-ts" --language "typescript" --path "${testDir}"`,
{ stdio: 'inherit' }
)
const templateDir = path.join(testDir, 'test-ts')
const templateContent = await fs.readFile(
path.join(templateDir, 'template.ts'),
'utf8'
)
// Verify basic structure
expect(templateContent).toContain("import { Template } from 'e2b'")
expect(templateContent).toContain('export const template = Template()')
expect(templateContent).toContain('fromImage')
expect(templateContent).toContain('e2bdev/base')
})
test('should generate correct Python template content', async () => {
execSync(
`node "${cliPath}" template init --name "test-py" --language "python-sync" --path "${testDir}"`,
{ stdio: 'inherit' }
)
const templateDir = path.join(testDir, 'test-py')
const templateContent = await fs.readFile(
path.join(templateDir, 'template.py'),
'utf8'
)
// Verify basic structure
expect(templateContent).toContain('from e2b import Template')
expect(templateContent).toContain('template = (')
expect(templateContent).toContain('Template()')
expect(templateContent).toContain('from_image')
expect(templateContent).toContain('e2bdev/base')
})
test('should generate correct async Python template content', async () => {
execSync(
`node "${cliPath}" template init --name "test-py-async" --language "python-async" --path "${testDir}"`,
{ stdio: 'inherit' }
)
const templateContent = await fs.readFile(
path.join(testDir, 'test-py-async', 'template.py'),
'utf8'
)
// Verify async structure
expect(templateContent).toContain('from e2b import AsyncTemplate')
expect(templateContent).toContain('AsyncTemplate()')
})
})
describe('Directory Conflict Handling', () => {
test('should fail when directory already exists', async () => {
// Create a directory that would conflict
const conflictDir = path.join(testDir, 'test')
await fs.mkdir(conflictDir)
await fs.writeFile(
path.join(conflictDir, 'existing.txt'),
'existing content'
)
// Should fail when trying to create template with existing directory name
expect(() => {
execSync(
`node "${cliPath}" template init --name "test" --language "typescript" --path "${testDir}"`,
{ stdio: 'pipe' }
)
}).toThrow()
// Verify original directory is preserved and unchanged
expect(existsSync(path.join(conflictDir, 'existing.txt'))).toBe(true)
// Verify no template files were created in the existing directory
expect(existsSync(path.join(conflictDir, 'template.ts'))).toBe(false)
})
})
})
// Helper functions
function getExpectedFiles(language: Language): string[] {
const extension = language === Language.TypeScript ? '.ts' : '.py'
const buildDevName =
language === Language.TypeScript ? 'build.dev' : 'build_dev'
const buildProdName =
language === Language.TypeScript ? 'build.prod' : 'build_prod'
return [
`template${extension}`,
`${buildDevName}${extension}`,
`${buildProdName}${extension}`,
]
}
async function verifyTemplateNameInBuildFiles(
testDir: string,
language: Language,
templateName: string
): Promise<void> {
const extension = language === Language.TypeScript ? '.ts' : '.py'
const buildDevName =
language === Language.TypeScript ? 'build.dev' : 'build_dev'
const buildProdName =
language === Language.TypeScript ? 'build.prod' : 'build_prod'
// Check dev build file contains template name with -dev suffix
const devContent = await fs.readFile(
path.join(testDir, `${buildDevName}${extension}`),
'utf8'
)
expect(devContent).toContain(`${templateName}-dev`)
// Check prod build file contains template name
const prodContent = await fs.readFile(
path.join(testDir, `${buildProdName}${extension}`),
'utf8'
)
expect(prodContent).toContain(templateName)
expect(prodContent).not.toContain(`${templateName}-dev`) // should not have -dev suffix
}
@@ -0,0 +1,351 @@
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
import * as fs from 'fs/promises'
import * as path from 'path'
import { execSync } from 'child_process'
import { Language } from '../../../src/commands/template/generators'
interface FileNames {
template: string
buildDev: string
buildProd: string
}
function getFileNames(language: Language): FileNames {
switch (language) {
case Language.TypeScript: {
return {
template: 'template.ts',
buildDev: 'build.dev.ts',
buildProd: 'build.prod.ts',
}
}
case Language.PythonSync:
case Language.PythonAsync: {
return {
template: 'template.py',
buildDev: 'build_dev.py',
buildProd: 'build_prod.py',
}
}
default:
throw new Error(`Unsupported language: ${language}`)
}
}
describe('Template Migration', () => {
let testDir: string
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
const fixturesDir = path.join(__dirname, 'fixtures')
beforeEach(async () => {
// Use Node.js built-in temp directory handling
testDir = await fs.mkdtemp('e2b-migrate-test-')
})
afterEach(async () => {
// Clean up test directory
if (testDir) {
await fs.rm(testDir, { recursive: true, force: true })
}
})
// Run tests for each test case
describe('Migration Test Cases', () => {
// Test case names correspond to fixture directory names
const testCases = [
'complex-python',
'copy-variations',
'custom-commands',
'minimal-dockerfile',
'multiple-env',
'start-cmd',
'multi-stage',
]
testCases.forEach((testCaseName) => {
describe(testCaseName.replace('-', ' '), () => {
test('should migrate to TypeScript', async () => {
await runMigrationTest(testCaseName, Language.TypeScript)
})
test('should migrate to Python sync', async () => {
await runMigrationTest(testCaseName, Language.PythonSync)
})
test('should migrate to Python async', async () => {
await runMigrationTest(testCaseName, Language.PythonAsync)
})
})
})
async function runMigrationTest(testCaseName: string, language: Language) {
const fixtureDir = path.join(fixturesDir, testCaseName)
// Copy fixture files to test directory
await copyFixtureFiles(fixtureDir, testDir)
// Run migration
execSync(`node ${cliPath} template migrate --language ${language}`, {
cwd: testDir,
})
// Determine file extensions and names based on language
const fileNames = getFileNames(language)
// Load expected outputs
const expectedDir = path.join(fixtureDir, 'expected', language)
const expectedTemplate = await fs.readFile(
path.join(expectedDir, fileNames.template),
'utf-8'
)
const expectedBuildDev = await fs.readFile(
path.join(expectedDir, fileNames.buildDev),
'utf-8'
)
const expectedBuildProd = await fs.readFile(
path.join(expectedDir, fileNames.buildProd),
'utf-8'
)
// Check generated files
const templateFile = await fs.readFile(
path.join(testDir, fileNames.template),
'utf-8'
)
expect(templateFile.trim()).toEqual(expectedTemplate.trim())
const buildDevFile = await fs.readFile(
path.join(testDir, fileNames.buildDev),
'utf-8'
)
expect(buildDevFile.trim()).toEqual(expectedBuildDev.trim())
const buildProdFile = await fs.readFile(
path.join(testDir, fileNames.buildProd),
'utf-8'
)
expect(buildProdFile.trim()).toEqual(expectedBuildProd.trim())
if (
language === Language.PythonSync ||
language === Language.PythonAsync
) {
for (const content of [buildDevFile, buildProdFile]) {
expect(content).toContain('from template import template')
expect(content).not.toContain('from .template import template')
}
}
// Check that old files are renamed to .old extensions
const oldDockerfilePath = path.join(testDir, 'e2b.Dockerfile.old')
const oldConfigPath = path.join(testDir, 'e2b.toml.old')
expect(
await fs
.access(oldDockerfilePath)
.then(() => true)
.catch(() => false)
).toBe(true)
expect(
await fs
.access(oldConfigPath)
.then(() => true)
.catch(() => false)
).toBe(true)
// Verify original files no longer exist
const originalDockerfilePath = path.join(testDir, 'e2b.Dockerfile')
const originalConfigPath = path.join(testDir, 'e2b.toml')
expect(
await fs
.access(originalDockerfilePath)
.then(() => true)
.catch(() => false)
).toBe(false)
expect(
await fs
.access(originalConfigPath)
.then(() => true)
.catch(() => false)
).toBe(false)
}
async function copyFixtureFiles(fixtureDir: string, targetDir: string) {
// Copy Dockerfile and config
await fs.copyFile(
path.join(fixtureDir, 'e2b.Dockerfile'),
path.join(targetDir, 'e2b.Dockerfile')
)
await fs.copyFile(
path.join(fixtureDir, 'e2b.toml'),
path.join(targetDir, 'e2b.toml')
)
}
})
describe('Override Options', () => {
test('should apply name, command and resource overrides', async () => {
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
const config = `template_id = "config-name"
dockerfile = "e2b.Dockerfile"
cpu_count = 2
memory_mb = 512`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
execSync(
`node ${cliPath} template migrate --language typescript --name " Overridden-Name " --cmd "node server.js" --ready-cmd "curl localhost:3000" --cpu-count 4 --memory-mb 2048`,
{
cwd: testDir,
}
)
const templateFile = await fs.readFile(
path.join(testDir, 'template.ts'),
'utf-8'
)
expect(templateFile).toContain(
".setStartCmd('sudo node server.js', 'curl localhost:3000')"
)
const buildProdFile = await fs.readFile(
path.join(testDir, 'build.prod.ts'),
'utf-8'
)
expect(buildProdFile).toContain("'overridden-name'")
expect(buildProdFile).not.toContain("'config-name'")
expect(buildProdFile).toContain('cpuCount: 4')
expect(buildProdFile).toContain('memoryMB: 2048')
})
test('should reject an invalid --name', async () => {
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
const config = `template_id = "config-name"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
expect(() => {
execSync(
`node ${cliPath} template migrate --language typescript --name "Invalid Name"`,
{
cwd: testDir,
}
)
}).toThrow()
})
test('should reject non-numeric resource overrides', async () => {
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
const config = `template_id = "config-name"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
expect(() => {
execSync(
`node ${cliPath} template migrate --language typescript --cpu-count abc`,
{
cwd: testDir,
}
)
}).toThrow()
expect(() => {
execSync(
`node ${cliPath} template migrate --language typescript --memory-mb abc`,
{
cwd: testDir,
}
)
}).toThrow()
})
test('should reject an odd memory override', async () => {
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
const config = `template_id = "resources"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
expect(() => {
execSync(
`node ${cliPath} template migrate --language typescript --memory-mb 1023`,
{
cwd: testDir,
}
)
}).toThrow()
})
})
describe('Error Cases', () => {
test('should succeed with warning when config file is missing', async () => {
// Create only Dockerfile, no config
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
// Run migration and expect it to succeed with warning (capture stderr + stdout)
const output = execSync(
`node ${cliPath} template migrate --language typescript 2>&1`,
{
cwd: testDir,
encoding: 'utf-8',
}
)
expect(output).toContain(
'Config file ./e2b.toml not found. Using defaults.'
)
expect(output).toContain('Migration completed successfully')
})
test('should fail gracefully when Dockerfile is missing', async () => {
// Create only config, no Dockerfile
const config = `template_id = "test-app"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
// Run migration and expect it to fail
expect(() => {
execSync(`node ${cliPath} template migrate --language typescript`, {
cwd: testDir,
})
}).toThrow()
})
})
describe('File Collision Handling', () => {
test('should error out if files already exist', async () => {
// Create test Dockerfile
const dockerfile = 'FROM node:18'
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile)
// Create test config
const config = `template_id = "test-app"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), config)
// Create existing files
await fs.writeFile(path.join(testDir, 'template.ts'), '// existing')
await fs.writeFile(path.join(testDir, 'build.dev.ts'), '// existing')
await fs.writeFile(path.join(testDir, 'build.prod.ts'), '// existing')
// Run migration
expect(() => {
execSync(`node ${cliPath} template migrate --language typescript`, {
cwd: testDir,
})
}).toThrow()
const files = await fs.readdir(testDir)
expect(files).toContain('template.ts')
expect(files).toContain('build.dev.ts')
expect(files).toContain('build.prod.ts')
})
})
})
@@ -0,0 +1,74 @@
import * as fs from 'fs/promises'
import * as path from 'path'
import { execSync } from 'child_process'
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
describe('Template Publish --config', () => {
let testDir: string
beforeEach(async () => {
testDir = await fs.mkdtemp('e2b-publish-config-test-')
const defaultConfig = `template_id = "default-template-id"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'e2b.toml'), defaultConfig)
const customConfig = `template_id = "custom-template-id"
dockerfile = "e2b.Dockerfile"`
await fs.writeFile(path.join(testDir, 'custom.toml'), customConfig)
await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), 'FROM alpine:3.18')
})
afterEach(async () => {
if (testDir) {
await fs.rm(testDir, { recursive: true, force: true })
}
})
test('uses the config file passed via --config even when e2b.toml exists', async () => {
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
let output = ''
try {
// This will likely fail on the network call, but we only need stdout up to that point
output = execSync(
`node "${cliPath}" template publish --yes --path "${testDir}" --config custom.toml 2>&1`,
{ encoding: 'utf-8', stdio: 'pipe', timeout: 10_000 }
)
} catch (err: any) {
// Capture partial output from the failed process (expected due to no network)
output = (err?.stdout ?? '') + (err?.stderr ?? '')
}
// It should log the sandbox template selection with the custom template ID and path
expect(output).toContain('Sandbox templates to publish')
expect(output).toContain('custom-template-id')
expect(output).toContain('custom.toml')
// And should not show the default template id when custom is provided
expect(output).not.toContain('default-template-id')
})
test('uses the default e2b.toml when --config is not provided', async () => {
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
let output = ''
try {
output = execSync(
`node "${cliPath}" template publish --yes --path "${testDir}" 2>&1`,
{ encoding: 'utf-8', stdio: 'pipe', timeout: 10_000 }
)
} catch (err: any) {
output = (err?.stdout ?? '') + (err?.stderr ?? '')
}
// Should reference the default config and template id
expect(output).toContain('Sandbox templates to publish')
expect(output).toContain('default-template-id')
expect(output).toContain('e2b.toml')
// Should not mention the custom config/template id
expect(output).not.toContain('custom-template-id')
})
})