Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing dependencies and handling of terminal clients #2

Merged
merged 3 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 31 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,47 @@

[![Github Actions Status](https://github.com/ianthomas23/jupyterlite-terminal/workflows/Build/badge.svg)](https://github.com/ianthomas23/jupyterlite-terminal/actions/workflows/build.yml)

A terminal for JupyterLite
A terminal for JupyterLite.

⚠️ This extension is still in development and not yet ready for general use. ⚠️

![a screenshot showing a terminal running in JupyterLite](https://github.com/ianthomas23/jupyterlite-terminal/assets/591645/1b4ff620-e8f2-4abf-b608-6badd66370ac)

## Requirements

- JupyterLab >= 4.0.0
- JupyterLite >= 0.4.0

## Install

To install the extension, execute:

```bash
pip install jupyterlite_terminal
pip install jupyterlite-terminal
```

You will also need to install the JupyterLite CLI:

```bash
python -m pip install --pre jupyterlite-core
```

## Uninstall
## Usage

After installing `jupyterlite-core` and `jupyterlite-terminal`, create a `jupyter-lite.json` file with the following content to activate the terminal extension:

To remove the extension, execute:
```json
{
"jupyter-lite-schema-version": 0,
"jupyter-config-data": {
"terminalsAvailable": true
}
}
```

Then build a new JupyterLite site:

```bash
pip uninstall jupyterlite_terminal
jupyter lite build
```

## Contributing
Expand Down Expand Up @@ -54,44 +75,18 @@ jlpm watch
jupyter lab
```

With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).

By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
Then build a JupyterLite distribution with the extension installed:

```bash
jupyter lab build --minimize=False
jupyter lite build
```

### Development uninstall
And serve it:

```bash
pip uninstall jupyterlite_terminal
jupyter lite serve
```

In development mode, you will also need to remove the symlink created by `jupyter labextension develop`
command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
folder is located. Then you can remove the symlink named `jupyterlite-terminal` within that folder.

### Testing the extension

#### Frontend tests

This extension is using [Jest](https://jestjs.io/) for JavaScript code testing.

To execute them, execute:

```sh
jlpm
jlpm test
```

#### Integration tests

This extension uses [Playwright](https://playwright.dev/docs/intro) for the integration tests (aka user level tests).
More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab.

More information are provided within the [ui-tests](./ui-tests/README.md) README.

### Packaging the extension

See [RELEASE](RELEASE.md)
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@ianthomas23/cockle": "^0.0.2",
"@jupyterlab/services": "^7.2.0",
"@jupyterlab/terminal": "^4.2.0",
"@jupyterlab/terminal-extension": "^4.2.0",
"@jupyterlite/server": "^0.3.0",
"@lumino/coreutils": "^2.1.2"
"@lumino/coreutils": "^2.1.2",
"mock-socket": "^9.3.1"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
Expand Down
6 changes: 1 addition & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
JupyterLiteServerPlugin,
Router
} from '@jupyterlite/server';
import { ITerminalTracker } from '@jupyterlab/terminal';

import { ITerminals } from './tokens';
import { Terminals } from './terminals';
Expand All @@ -18,15 +17,12 @@ const terminalsPlugin: JupyterLiteServerPlugin<ITerminals> = {
id: 'jupyterlite-terminal:plugin',
description: 'A terminal for JupyterLite',
autoStart: true,
requires: [ITerminalTracker],
provides: ITerminals,
activate: async (app: JupyterLiteServer, tracker: ITerminalTracker) => {
activate: async (app: JupyterLiteServer) => {
console.log(
'JupyterLab extension jupyterlite-terminal:plugin is activated!'
);

console.log('==> ITerminalTracker', tracker);

const { serviceManager } = app;
const { contents, serverSettings, terminals } = serviceManager;
console.log('terminals available:', terminals.isAvailable());
Expand Down
30 changes: 29 additions & 1 deletion src/terminal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { JupyterFileSystem, Shell, IFileSystem } from '@ianthomas23/cockle';

import { JSONPrimitive } from '@lumino/coreutils';

import {
Expand All @@ -16,6 +18,8 @@ export class Terminal implements ITerminal {
*/
constructor(options: ITerminal.IOptions) {
this._name = options.name;
this._fs = new JupyterFileSystem(options.contentsManager);
console.log('==> new Terminal', this._name, this._fs);
}

/**
Expand All @@ -28,14 +32,34 @@ export class Terminal implements ITerminal {
async wsConnect(url: string) {
console.log('==> Terminal.wsConnect', url);

const server = new WebSocketServer(url, { mock: false });
// const server = new WebSocketServer(url, { mock: false });
const server = new WebSocketServer(url);

server.on('connection', async (socket: WebSocketClient) => {
console.log('==> server connection', this, socket);

const outputCallback = async (output: string) => {
console.log('==> recv from shell:', output);
const ret = JSON.stringify(['stdout', output]);
socket.send(ret);
};

this._shell = new Shell(this._fs, outputCallback);
console.log('==> shell', this._shell);

socket.on('message', async (message: any) => {
const data = JSON.parse(message) as JSONPrimitive[];
console.log('==> socket message', data);
const message_type = data[0];
const content = data.slice(1);

if (message_type === 'stdin') {
await this._shell!.input(content[0] as string);
} else if (message_type === 'set_size') {
const rows = content[0] as number;
const columns = content[1] as number;
await this._shell!.setSize(rows, columns);
}
});

socket.on('close', async () => {
Expand All @@ -50,8 +74,12 @@ export class Terminal implements ITerminal {
const res = JSON.stringify(['setup']);
console.log('==> Returning handshake via socket', res);
socket.send(res);

await this._shell!.start();
});
}

private _name: string;
private _fs: IFileSystem;
private _shell?: Shell;
}
87 changes: 86 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,15 @@ __metadata:
languageName: node
linkType: hard

"@ianthomas23/cockle@npm:^0.0.2":
version: 0.0.2
resolution: "@ianthomas23/cockle@npm:0.0.2"
dependencies:
"@jupyterlab/services": ^7.1.6
checksum: 7426122ce9e05cebf6db3c88a06b068eafa65dc8a6ba72384c6a9a62f95d13ebc3a3cb865ecaa6989a8809d0a0fc17155689be7d6067d5f3ffb4e0dbb0a32d33
languageName: node
linkType: hard

"@isaacs/cliui@npm:^8.0.2":
version: 8.0.2
resolution: "@isaacs/cliui@npm:8.0.2"
Expand Down Expand Up @@ -2341,6 +2350,20 @@ __metadata:
languageName: node
linkType: hard

"@jupyterlab/coreutils@npm:^6.2.1":
version: 6.2.1
resolution: "@jupyterlab/coreutils@npm:6.2.1"
dependencies:
"@lumino/coreutils": ^2.1.2
"@lumino/disposable": ^2.1.2
"@lumino/signaling": ^2.1.2
minimist: ~1.2.0
path-browserify: ^1.0.0
url-parse: ~1.5.4
checksum: c8167bd8d4472471297e5669d6b3ee7c9d5c1246e8413680713b15f8a81926d2c97bc6a3c0b26c16603b197b412e01b443cc74b02a3676adea5690aac41964be
languageName: node
linkType: hard

"@jupyterlab/coreutils@npm:~6.1.5":
version: 6.1.8
resolution: "@jupyterlab/coreutils@npm:6.1.8"
Expand Down Expand Up @@ -2518,6 +2541,15 @@ __metadata:
languageName: node
linkType: hard

"@jupyterlab/nbformat@npm:^4.2.1":
version: 4.2.1
resolution: "@jupyterlab/nbformat@npm:4.2.1"
dependencies:
"@lumino/coreutils": ^2.1.2
checksum: 192167e2a9019bf91e1e7088c9eaaae7b1037f5e7b5db15b97687b052323e6e75913b301ca7a9783d0e59aa36f18ddff90fc71a90a8153e0c89e32fd92b2519c
languageName: node
linkType: hard

"@jupyterlab/nbformat@npm:~4.1.5":
version: 4.1.8
resolution: "@jupyterlab/nbformat@npm:4.1.8"
Expand Down Expand Up @@ -2662,6 +2694,25 @@ __metadata:
languageName: node
linkType: hard

"@jupyterlab/services@npm:^7.1.6":
version: 7.2.1
resolution: "@jupyterlab/services@npm:7.2.1"
dependencies:
"@jupyter/ydoc": ^2.0.1
"@jupyterlab/coreutils": ^6.2.1
"@jupyterlab/nbformat": ^4.2.1
"@jupyterlab/settingregistry": ^4.2.1
"@jupyterlab/statedb": ^4.2.1
"@lumino/coreutils": ^2.1.2
"@lumino/disposable": ^2.1.2
"@lumino/polling": ^2.1.2
"@lumino/properties": ^2.0.1
"@lumino/signaling": ^2.1.2
ws: ^8.11.0
checksum: f07be2f3a174466c17ab5c22f8ef622fc623e8c61f2220b8bfb465a263971313cb9129e84bba32606e6ab7d1e0be3a9754b97f98e173e9c95eaf0b1c6cd8110a
languageName: node
linkType: hard

"@jupyterlab/services@npm:^7.2.0":
version: 7.2.0
resolution: "@jupyterlab/services@npm:7.2.0"
Expand Down Expand Up @@ -2719,6 +2770,25 @@ __metadata:
languageName: node
linkType: hard

"@jupyterlab/settingregistry@npm:^4.2.1":
version: 4.2.1
resolution: "@jupyterlab/settingregistry@npm:4.2.1"
dependencies:
"@jupyterlab/nbformat": ^4.2.1
"@jupyterlab/statedb": ^4.2.1
"@lumino/commands": ^2.3.0
"@lumino/coreutils": ^2.1.2
"@lumino/disposable": ^2.1.2
"@lumino/signaling": ^2.1.2
"@rjsf/utils": ^5.13.4
ajv: ^8.12.0
json5: ^2.2.3
peerDependencies:
react: ">=16"
checksum: 794e5ecde19a40e1b95c0d636eed7b56bbdc46857c8f3b4ef446c1bc90e8ea660c2ccf8f36a238bc312002f106a5a8522bb057742d9c0d674b2974ef21a786d7
languageName: node
linkType: hard

"@jupyterlab/settingregistry@npm:~4.1.5":
version: 4.1.8
resolution: "@jupyterlab/settingregistry@npm:4.1.8"
Expand Down Expand Up @@ -2751,6 +2821,19 @@ __metadata:
languageName: node
linkType: hard

"@jupyterlab/statedb@npm:^4.2.1":
version: 4.2.1
resolution: "@jupyterlab/statedb@npm:4.2.1"
dependencies:
"@lumino/commands": ^2.3.0
"@lumino/coreutils": ^2.1.2
"@lumino/disposable": ^2.1.2
"@lumino/properties": ^2.0.1
"@lumino/signaling": ^2.1.2
checksum: 51e07db85269883bcd58fc5ba890db122e260e8d1ce4046f0b188453726694c2d909f27ca069ee3cd6944a93d70fcb8360074f87cdb13d611af2e24f6b14af30
languageName: node
linkType: hard

"@jupyterlab/statedb@npm:~4.1.5":
version: 4.1.8
resolution: "@jupyterlab/statedb@npm:4.1.8"
Expand Down Expand Up @@ -7612,6 +7695,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "jupyterlite-terminal@workspace:."
dependencies:
"@ianthomas23/cockle": ^0.0.2
"@jupyterlab/builder": ^4.0.0
"@jupyterlab/services": ^7.2.0
"@jupyterlab/terminal": ^4.2.0
Expand All @@ -7630,6 +7714,7 @@ __metadata:
eslint-config-prettier: ^8.8.0
eslint-plugin-prettier: ^5.0.0
jest: ^29.2.0
mock-socket: ^9.3.1
npm-run-all: ^4.1.5
prettier: ^3.0.0
rimraf: ^5.0.1
Expand Down Expand Up @@ -8223,7 +8308,7 @@ __metadata:
languageName: node
linkType: hard

"mock-socket@npm:^9.1.0":
"mock-socket@npm:^9.1.0, mock-socket@npm:^9.3.1":
version: 9.3.1
resolution: "mock-socket@npm:9.3.1"
checksum: cb2dde4fc5dde280dd5ccb78eaaa223382ee16437f46b86558017655584ad08c22e733bde2dd5cc86927def506b6caeb0147e3167b9a62d70d5cf19d44103853
Expand Down
Loading