Skip to content

Commit 4b7edde

Browse files
committed
Change javascript kernel to Deno
1 parent 51adafc commit 4b7edde

File tree

10 files changed

+225
-21
lines changed

10 files changed

+225
-21
lines changed

js/tests/languages/js.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { expect } from 'vitest'
2+
3+
import { sandboxTest } from '../setup'
4+
5+
sandboxTest('js simple', async ({ sandbox }) => {
6+
const result = await sandbox.runCode('console.log("Hello, World!")', {language: "js"})
7+
8+
expect(result.logs.stdout.join().trim()).toEqual('Hello, World!')
9+
})
10+
11+
sandboxTest('js import', async ({ sandbox }) => {
12+
const result = await sandbox.runCode('import isOdd from "npm:is-odd"\nisOdd(3)', {language: "js"})
13+
14+
expect(result.results[0].text).toEqual('true')
15+
})
16+
17+
sandboxTest('js top level await', async ({ sandbox }) => {
18+
const result = await sandbox.runCode(`
19+
async function main() {
20+
return 'Hello, World!'
21+
}
22+
23+
await main()
24+
`, {language: "js"})
25+
expect(result.results[0].text).toEqual('Hello, World!')
26+
})
27+
28+
sandboxTest('js es6', async ({ sandbox }) => {
29+
const result = await sandbox.runCode(`
30+
const add = (x, y) => x + y;
31+
add(1, 2)
32+
`, {language: "js"})
33+
expect(result.results[0].text).toEqual('3')
34+
})
35+
36+
37+
sandboxTest('js context', async ({ sandbox }) => {
38+
await sandbox.runCode('const z = 1', {language: "js"})
39+
const result = await sandbox.runCode('z', {language: "js"})
40+
expect(result.results[0].text).toEqual('1')
41+
})
42+
43+
sandboxTest('js cwd', async ({ sandbox }) => {
44+
const result = await sandbox.runCode('process.cwd()', {language: "js"})
45+
expect(result.results[0].text).toEqual('/home/user')
46+
47+
const ctx = await sandbox.createCodeContext( {cwd: '/home', language: "js"})
48+
const result2 = await sandbox.runCode('process.cwd()', {context: ctx})
49+
expect(result2.results[0].text).toEqual('/home')
50+
})
51+
52+
sandboxTest('ts simple', async ({ sandbox }) => {
53+
const result = await sandbox.runCode(`
54+
function subtract(x: number, y: number): number {
55+
return x - y;
56+
}
57+
58+
subtract(1, 2)
59+
`, {language: "ts"})
60+
61+
expect(result.results[0].text).toEqual('-1')
62+
})

python/tests/languages/test_js.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from e2b_code_interpreter import AsyncSandbox
2+
3+
4+
async def test_javascript(async_sandbox: AsyncSandbox):
5+
code = """
6+
console.log('Hello, World!')
7+
"""
8+
execution = await async_sandbox.run_code(code, language="js")
9+
assert execution.logs.stdout == ["Hello, World!\n"]
10+
11+
12+
async def test_import(async_sandbox: AsyncSandbox):
13+
code = """
14+
import isOdd from 'npm:is-odd'
15+
isOdd(3)
16+
"""
17+
execution = await async_sandbox.run_code(code, language="js")
18+
assert execution.results[0].text == "true"
19+
20+
21+
async def test_toplevel_await(async_sandbox: AsyncSandbox):
22+
code = """
23+
async function main() {
24+
return 'Hello, World!'
25+
}
26+
27+
await main()
28+
"""
29+
execution = await async_sandbox.run_code(code, language="js")
30+
assert execution.results[0].text == "Hello, World!"
31+
32+
33+
async def test_es6(async_sandbox: AsyncSandbox):
34+
code = """
35+
const add = (x, y) => x + y;
36+
add(1, 2);
37+
"""
38+
execution = await async_sandbox.run_code(code, language="js")
39+
assert execution.results[0].text == "3"
40+
41+
42+
async def test_context(async_sandbox: AsyncSandbox):
43+
await async_sandbox.run_code("const x = 1", language="js")
44+
execution = await async_sandbox.run_code("x", language="js")
45+
assert execution.results[0].text == "1"
46+
47+
48+
async def test_cwd(async_sandbox: AsyncSandbox):
49+
execution = await async_sandbox.run_code("process.cwd()", language="js")
50+
assert execution.results[0].text == "/home/user"
51+
52+
ctx = await async_sandbox.create_code_context("/home", language="js")
53+
execution = await async_sandbox.run_code("process.cwd()", context=ctx)
54+
assert execution.results[0].text == "/home"
55+
56+
57+
async def test_typescript(async_sandbox: AsyncSandbox):
58+
execution = await async_sandbox.run_code(
59+
"""
60+
function subtract(x: number, y: number): number {
61+
return x - y;
62+
}
63+
64+
subtract(1, 2);
65+
""",
66+
language="ts",
67+
)
68+
assert execution.results[0].text == "-1"

python/tests/sync/test_default_kernels.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@
44
def test_js_kernel(sandbox: Sandbox):
55
execution = sandbox.run_code("console.log('Hello, World!')", language="js")
66
assert execution.logs.stdout == ["Hello, World!\n"]
7+
8+
9+
def test_r_kernel(sandbox: Sandbox):
10+
execution = sandbox.run_code('print("Hello, World!")', language="r")
11+
assert execution.logs.stdout == ['[1] "Hello, World!"\n']
12+
13+
14+
def test_java_kernel(sandbox: Sandbox):
15+
execution = sandbox.run_code('System.out.println("Hello, World!")', language="java")
16+
assert execution.logs.stdout[0] == "Hello, World!"

template/Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ RUN R -e "install.packages('IRkernel')"
2323
RUN R -e "IRkernel::installspec(user = FALSE, name = 'r', displayname = 'R')"
2424

2525
# Javascript Kernel
26-
RUN npm install -g node-gyp
27-
RUN npm install -g --unsafe-perm ijavascript
28-
RUN ijsinstall --install=global
26+
COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno
27+
RUN chmod +x /usr/bin/deno
28+
RUN deno jupyter --unstable --install
29+
COPY ./deno.json /root/.local/share/jupyter/kernels/deno/kernel.json
2930

3031
# Bash Kernel
3132
RUN pip install bash_kernel

template/deno.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"argv": [
3+
"/usr/bin/deno",
4+
"jupyter",
5+
"--kernel",
6+
"--conn",
7+
"{connection_file}"
8+
],
9+
"display_name": "Deno",
10+
"env": {
11+
"NO_COLOR": "1"
12+
},
13+
"language": "typescript"
14+
}

template/server/api/models/result.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ def __init__(self, is_main_result: bool, data: [str, str]):
4545
self.is_main_result = is_main_result
4646

4747
self.text = data.pop("text/plain", None)
48-
if self.text and self.text.startswith("'") and self.text.endswith("'"):
48+
if self.text and (
49+
(self.text.startswith("'") and self.text.endswith("'"))
50+
or (self.text.startswith('"') and self.text.endswith('"'))
51+
):
4952
self.text = self.text[1:-1]
5053

5154
self.html = data.pop("text/html", None)

template/server/contexts.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ def normalize_language(language: Optional[str]) -> str:
2020

2121
if language == "js":
2222
return "javascript"
23+
if language == "ts":
24+
return "typescript"
2325

2426
return language
2527

2628

2729
async def create_context(client, websockets: dict, language: str, cwd: str) -> Context:
30+
if language == "javascript" or language == "typescript":
31+
language = "deno"
32+
2833
data = {
2934
"path": str(uuid.uuid4()),
3035
"kernel": {"name": language},
@@ -53,7 +58,7 @@ async def create_context(client, websockets: dict, language: str, cwd: str) -> C
5358

5459
logger.info(f"Setting working directory to {cwd}")
5560
try:
56-
await ws.change_current_directory(cwd)
61+
await ws.change_current_directory(cwd, language)
5762
except ExecutionError as e:
5863
return PlainTextResponse(
5964
"Failed to set working directory",

template/server/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ async def post_execute(request: ExecutionRequest):
8888
if not context_id:
8989
context = await create_context(client, websockets, language, "/home/user")
9090
context_id = context.id
91+
default_websockets[language] = context_id
9192

9293
elif request.context_id:
9394
context_id = request.context_id
@@ -104,7 +105,10 @@ async def post_execute(request: ExecutionRequest):
104105
)
105106

106107
return StreamingListJsonResponse(
107-
ws.execute(request.code, env_vars=request.env_vars)
108+
ws.execute(
109+
request.code,
110+
env_vars=request.env_vars,
111+
)
108112
)
109113

110114

template/server/messaging.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import json
23
import logging
34
import uuid
@@ -95,14 +96,21 @@ def _get_execute_request(
9596
"session": self.session_id,
9697
"msg_type": "execute_request",
9798
"version": "5.3",
99+
"date": datetime.datetime.now(datetime.timezone.utc).isoformat(),
98100
},
99101
"parent_header": {},
100-
"metadata": {},
102+
"metadata": {
103+
"trusted": True,
104+
"deletedCells": [],
105+
"recordTiming": False,
106+
"cellId": str(uuid.uuid4()),
107+
},
101108
"content": {
102109
"code": code,
103110
"silent": background,
104111
"store_history": True,
105112
"user_expressions": {},
113+
"stop_on_error": True,
106114
"allow_stdin": False,
107115
},
108116
}
@@ -127,10 +135,25 @@ async def _wait_for_result(self, message_id: str):
127135

128136
yield output.model_dump(exclude_none=True)
129137

130-
async def change_current_directory(self, path: Union[str, StrictStr]):
138+
async def change_current_directory(
139+
self, path: Union[str, StrictStr], language: str
140+
):
131141
message_id = str(uuid.uuid4())
132142
self._executions[message_id] = Execution(in_background=True)
133-
request = self._get_execute_request(message_id, f"%cd {path}", True)
143+
if language == "python":
144+
request = self._get_execute_request(message_id, f"%cd {path}", True)
145+
elif language == "deno":
146+
request = self._get_execute_request(
147+
message_id, f"Deno.chdir('{path}')", True
148+
)
149+
elif language == "r":
150+
request = self._get_execute_request(message_id, f"setwd('{path}')", True)
151+
elif language == "java":
152+
request = self._get_execute_request(
153+
message_id, f"System.setProperty('user.dir', '{path}')", True
154+
)
155+
else:
156+
return
134157

135158
await self._ws.send(request)
136159

@@ -165,11 +188,12 @@ async def execute(
165188
indent = len(line) - len(line.lstrip())
166189
break
167190

168-
code = (
169-
indent * " "
170-
+ f"os.environ.set_envs_for_execution({vars_to_set})\n"
171-
+ code
172-
)
191+
if self.language == "python":
192+
code = (
193+
indent * " "
194+
+ f"os.environ.set_envs_for_execution({vars_to_set})\n"
195+
+ code
196+
)
173197

174198
logger.info(code)
175199
request = self._get_execute_request(message_id, code, False)
@@ -192,7 +216,9 @@ async def _receive_message(self):
192216
async for message in self._ws:
193217
await self._process_message(json.loads(message))
194218
except Exception as e:
195-
logger.error(f"WebSocket received error while receiving messages: {e}")
219+
logger.error(
220+
f"WebSocket received error while receiving messages: {type(e)}: {str(e)}"
221+
)
196222

197223
async def _process_message(self, data: dict):
198224
"""
@@ -308,11 +334,21 @@ async def _process_message(self, data: dict):
308334
execution.errored = True
309335
await queue.put(
310336
Error(
311-
name=data["content"]["ename"],
312-
value=data["content"]["evalue"],
313-
traceback="".join(data["content"]["traceback"]),
337+
name=data["content"].get("ename", ""),
338+
value=data["content"].get("evalue", ""),
339+
traceback="".join(data["content"].get("traceback", [])),
314340
)
315341
)
342+
elif data["content"]["status"] == "abort":
343+
logger.debug(f"Execution {parent_msg_ig} was aborted")
344+
await queue.put(
345+
Error(
346+
name="ExecutionAborted",
347+
value="Execution was aborted",
348+
traceback="",
349+
)
350+
)
351+
await queue.put(EndOfExecution())
316352
elif data["content"]["status"] == "ok":
317353
pass
318354

template/test.Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ COPY ./template/requirements.txt requirements.txt
1919
RUN pip install --no-cache-dir -r requirements.txt && ipython kernel install --name "python3" --user
2020

2121
# Javascript Kernel
22-
RUN npm install -g node-gyp
23-
RUN npm install -g --unsafe-perm ijavascript
24-
RUN ijsinstall --install=global
22+
COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno
23+
RUN chmod +x /usr/bin/deno
24+
RUN deno jupyter --unstable --install
25+
COPY ./template/deno.json /root/.local/share/jupyter/kernels/deno/kernel.json
2526

2627
# Create separate virtual environment for server
2728
RUN python -m venv $SERVER_PATH/.venv

0 commit comments

Comments
 (0)