Commit f2b98103 authored by Nicholas Bollweg's avatar Nicholas Bollweg
Browse files

contributing docs, binder, linting

parent daa903a6
# this is a minimal conda/mamba environment for working on JupyterLite and upstreams
name: jupyterlite-dev
channels:
- conda-forge
dependencies:
- jupyterlab >=3,<4
- jupyterlab-classic
- nodejs >=12,!=13.*,<15
- yarn <2
#!/usr/bin/env bash
yarn bootstrap
......@@ -2,7 +2,7 @@ name: Build
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: '*'
......@@ -10,21 +10,21 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install node
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Build
run: |
yarn
yarn run build
- name: Lint
run: |
yarn run eslint:check
yarn run prettier:check
- name: Test
run: |
yarn run build:test
yarn run test
- name: Checkout
uses: actions/checkout@v2
- name: Install node
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Build
run: |
yarn
yarn run build
- name: Lint
run: |
yarn run eslint:check
yarn run prettier:check
- name: Test
run: |
yarn run build:test
yarn run test
{
"singleQuote": true
"singleQuote": true,
"printWidth": 88,
"proseWrap": "always"
}
# Contributing to JupyterLite
Thanks for contributing to JupyterLite.
Thanks for contributing to JupyterLite!
Make sure to follow [Project Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md)
for a friendly and welcoming collaborative environment.
> We follow [Project Jupyter's Code of Conduct][coc] for a friendly and welcoming
> collaborative environment.
## Setting up a development environment
## Setup
### Get the code
```bash
git clone https://github.com/jtpio/jupyterlite
```
> if you don't have `git` yet, you might be able to use the instructions below to get it
### Prerequisites
You'll need:
- `git`
- `nodejs >=12`
- `yarn <2`
Various package managers on different operating systems provide these.
> A recommended approach for _any platform_ is to install [Mambaforge] and use the
> binder environment description checked into the repo.
>
> ```bash
> mamba env update --file .binder/environment.yml
> mamba activate jupyterlite-dev
> ```
## Development Tasks
### JavaScript development
#### Quick start
Most of the [development tasks](#development-tasks) can be run with one command:
```bash
yarn bootstrap
```
#### Install
```bash
yarn
```
#### Build
To build development assets:
```bash
yarn build
```
To build production assets:
```bash
yarn build:prod
```
#### Watch
```bash
yarn watch
```
#### Lint
```bash
yarn lint
```
#### Test
```bash
yarn build:test
yarn test
```
## Community Tasks
### Issues
JupyterLite features and bug fixes start as [issues] on GitHub.
- Look through the existing issues (and [pull requests]!) to see if a related issue
already exists or is being worked on
- If it is new:
- Start a [new issue]
- Pick an appropriate template
- Fill out the template
- Wait for the community to respond
### Pull Requests
JupyterLite become _real_ as [pull requests].
> Pull requests are a great place to discuss work-in-progress, but it is **highly
> recommended** to create an [issue](#issues) before starting work so the community can
> weigh in on choices.
- Fork the repo
- Make a new branch off `main`
- Make changes
- Run the `lint` and `test`
- Push to your fork
- Start the pull request
- your `git` cli should offer you a link, as will the GitHub web UI
- reference one or more [issue](#issues) so those that are interested can find your
work
- adding magic strings like `fixes #123` help tie the collaboration history together
- Wait for continuous integration
- If stuff breaks, fix it or ask for help!
### Releasing
> TBD
[issues]: https://github.com/jtpio/jupyterlite/issues
[new issue]: https://github.com/jtpio/jupyterlite/issues/new
[pull requests]: https://github.com/jtpio/jupyterlite/pulls
[repo]: https://github.com/jtpio/jupyterlite
[coc]: (https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md)
[mambaforge]: https://github.com/conda-forge/miniforge/
......@@ -2,11 +2,13 @@
![Github Actions Status](https://github.com/jtpio/jupyterlite/workflows/Build/badge.svg)
JupyterLite is a JupyterLab distribution that **runs entirely in the browser** built from the ground-up using JupyterLab components and plugins.
JupyterLite is a JupyterLab distribution that **runs entirely in the browser** built
from the ground-up using JupyterLab components and plugins.
## ✨ Try it in your browser ✨
JupyterLite works with both [JupyterLab](https://github.com/jupyterlab/jupyterlab) and [JupyterLab Classic](https://github.com/jtpio/jupyterlab-classic).
JupyterLite works with both [JupyterLab](https://github.com/jupyterlab/jupyterlab) and
[JupyterLab Classic](https://github.com/jtpio/jupyterlab-classic).
### [Try it with JupyterLab!](https://jupyterlite.vercel.app/)
......@@ -53,14 +55,22 @@ Then go to http://localhost:5000 in a web browser to start JupyterLite.
## Related
JupyterLite is a reboot of several attemps at making a full static Jupyter distribution that runs in the browser, without having to start the Python Jupyter Server on the host machine.
JupyterLite is a reboot of several attemps at making a full static Jupyter distribution
that runs in the browser, without having to start the Python Jupyter Server on the host
machine.
The goal is to provide a lightweight computing environment accessible in a matter of seconds with a single click, in a web browser and without having to install anything.
The goal is to provide a lightweight computing environment accessible in a matter of
seconds with a single click, in a web browser and without having to install anything.
This project is a collection of packages that can be remixed together in variety of ways to create new application and distributions. Most of the packages in this repo focus on providing server-like components that run in the browser (to manager kernels, files and settings), so existing JupyterLab extensions and plugins can be reused out of the box.
This project is a collection of packages that can be remixed together in variety of ways
to create new application and distributions. Most of the packages in this repo focus on
providing server-like components that run in the browser (to manager kernels, files and
settings), so existing JupyterLab extensions and plugins can be reused out of the box.
See also:
- [p5-notebook](https://github.com/jtpio/p5-notebook): A minimal Jupyter Notebook UI for p5.js kernels running in the browser
- [p5-notebook](https://github.com/jtpio/p5-notebook): A minimal Jupyter Notebook UI for
p5.js kernels running in the browser
- [jyve](https://github.com/deathbeds/jyve): Jupyter Kernels, right inside JupyterLab
- [Starboard Notebook](https://github.com/gzuidhof/starboard-notebook): In-browser literal notebooks
- [Starboard Notebook](https://github.com/gzuidhof/starboard-notebook): In-browser
literal notebooks
......@@ -24,21 +24,13 @@ const jupyterliteApputilsPlugin = path.resolve(
fs.mkdirpSync(jupyterliteApputilsPlugin);
fs.moveSync(
path.resolve(
buildDir,
'schemas/@jupyterlab/apputils-extension',
'themes.json'
),
path.resolve(buildDir, 'schemas/@jupyterlab/apputils-extension', 'themes.json'),
path.resolve(jupyterliteApputilsPlugin, 'themes.json'),
{ overwrite: true }
);
fs.copySync(
path.resolve(
buildDir,
'schemas/@jupyterlab/apputils-extension',
'package.json.orig'
),
path.resolve(buildDir, 'schemas/@jupyterlab/apputils-extension', 'package.json.orig'),
path.resolve(jupyterliteApputilsPlugin, 'package.json.orig')
);
......
......@@ -11,10 +11,7 @@ const escapeFileNames = filenames =>
module.exports = {
'**/*{.css,.json,.md}': filenames => {
const escapedFileNames = escapeFileNames(filenames);
return [
`prettier --write ${escapedFileNames}`,
`git add -f ${escapedFileNames}`
];
return [`prettier --write ${escapedFileNames}`, `git add -f ${escapedFileNames}`];
},
'**/*{.ts,.tsx,.js,.jsx}': filenames => {
const escapedFileNames = escapeFileNames(filenames);
......
......@@ -17,6 +17,7 @@
]
},
"scripts": {
"bootstrap": "yarn && yarn clean && yarn build && yarn lint && yarn test",
"build": "yarn build:lib && yarn build:app",
"build:lib": "lerna run build --scope @jupyterlite/metapackage",
"build:app": "lerna run build --stream --parallel --scope \"@jupyterlite/app-classic\" --scope \"@jupyterlite/app\"",
......@@ -28,8 +29,8 @@
"eslint": "eslint . --ext .ts,.tsx --fix",
"eslint:check": "eslint . --ext .ts,.tsx",
"install": "lerna bootstrap",
"prettier": "prettier --list-different --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier": "prettier --list-different --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml}\"",
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml}\"",
"publish": "yarn run clean && yarn run build && lerna publish",
"test": "lerna run test",
"update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna",
......
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
JupyterFrontEndPlugin,
JupyterFrontEnd
} from '@jupyterlab/application';
import { JupyterFrontEndPlugin, JupyterFrontEnd } from '@jupyterlab/application';
import { ITranslator, TranslationManager } from '@jupyterlab/translation';
......
import {
Contents as ServerContents,
ServerConnection
} from '@jupyterlab/services';
import { Contents as ServerContents, ServerConnection } from '@jupyterlab/services';
import { INotebookContent } from '@jupyterlab/nbformat';
......@@ -32,10 +29,7 @@ export class Contents implements IContents {
/**
* A signal emitted when the file has changed.
*/
get fileChanged(): ISignal<
ServerContents.IManager,
ServerContents.IChangedArgs
> {
get fileChanged(): ISignal<ServerContents.IManager, ServerContents.IChangedArgs> {
return this._fileChanged;
}
......@@ -353,13 +347,10 @@ export class Contents implements IContents {
* @returns A promise which resolves with the new checkpoint model when the
* checkpoint is created.
*/
async createCheckpoint(
path: string
): Promise<ServerContents.ICheckpointModel> {
async createCheckpoint(path: string): Promise<ServerContents.ICheckpointModel> {
const item = (await this._storage.getItem(path)) as ServerContents.IModel;
const copies =
((await this._checkpoints.getItem(path)) as ServerContents.IModel[]) ??
[];
((await this._checkpoints.getItem(path)) as ServerContents.IModel[]) ?? [];
copies.push(item);
// keep only a certain amount of checkpoints per file
if (copies.length > N_CHECKPOINTS) {
......@@ -381,12 +372,8 @@ export class Contents implements IContents {
* @returns A promise which resolves with a list of checkpoint models for
* the file.
*/
async listCheckpoints(
path: string
): Promise<ServerContents.ICheckpointModel[]> {
const copies = (await this._checkpoints.getItem(
path
)) as ServerContents.IModel[];
async listCheckpoints(path: string): Promise<ServerContents.ICheckpointModel[]> {
const copies = (await this._checkpoints.getItem(path)) as ServerContents.IModel[];
if (!copies) {
return [];
}
......@@ -407,9 +394,7 @@ export class Contents implements IContents {
* @returns A promise which resolves when the checkpoint is restored.
*/
async restoreCheckpoint(path: string, checkpointID: string): Promise<void> {
const copies = (await this._checkpoints.getItem(
path
)) as ServerContents.IModel[];
const copies = (await this._checkpoints.getItem(path)) as ServerContents.IModel[];
const id = parseInt(checkpointID);
const item = copies[id];
await this._storage.setItem(path, item);
......@@ -424,9 +409,7 @@ export class Contents implements IContents {
* @returns A promise which resolves when the checkpoint is deleted.
*/
async deleteCheckpoint(path: string, checkpointID: string): Promise<void> {
const copies = (await this._checkpoints.getItem(
path
)) as ServerContents.IModel[];
const copies = (await this._checkpoints.getItem(path)) as ServerContents.IModel[];
const id = parseInt(checkpointID);
copies.splice(id, 1);
await this._checkpoints.setItem(path, copies);
......@@ -521,9 +504,7 @@ export class Contents implements IContents {
*
* @param type The file type to increment the counter for.
*/
private async _incrementCounter(
type: ServerContents.ContentType
): Promise<number> {
private async _incrementCounter(type: ServerContents.ContentType): Promise<number> {
const current = ((await this._counters.getItem(type)) as number) ?? -1;
const counter = current + 1;
await this._counters.setItem(type, counter);
......
......@@ -5,9 +5,7 @@ import { Token } from '@lumino/coreutils';
/**
* The token for the settings service.
*/
export const IContents = new Token<IContents>(
'@jupyterlite/contents:IContents'
);
export const IContents = new Token<IContents>('@jupyterlite/contents:IContents');
/**
* The interface for the contents service.
......
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
JupyterLiteServer,
JupyterLiteServerPlugin
} from '@jupyterlite/server';
import { JupyterLiteServer, JupyterLiteServerPlugin } from '@jupyterlite/server';
import { IKernel, IKernelSpecs } from '@jupyterlite/kernel';
......
......@@ -239,10 +239,6 @@ export class JavaScriptKernel extends BaseKernel implements IKernel {
}
private _iframe: HTMLIFrameElement;
private _evalFunc = new Function(
'window',
'code',
'return window.eval(code);'
);
private _evalFunc = new Function('window', 'code', 'return window.eval(code);');
private _ready = new PromiseDelegate<void>();
}
......@@ -65,9 +65,7 @@ export abstract class BaseKernel implements IKernel {
/**
* Get the last parent header
*/
get parentHeader():
| KernelMessage.IHeader<KernelMessage.MessageType>
| undefined {
get parentHeader(): KernelMessage.IHeader<KernelMessage.MessageType> | undefined {
return this._parentHeader;
}
......@@ -244,9 +242,7 @@ export abstract class BaseKernel implements IKernel {
msgType: 'kernel_info_reply',
channel: 'shell',
session: parent.header.session,
parentHeader: parent.header as KernelMessage.IHeader<
'kernel_info_request'
>,
parentHeader: parent.header as KernelMessage.IHeader<'kernel_info_request'>,
content
});
......@@ -303,18 +299,16 @@ export abstract class BaseKernel implements IKernel {
*/
private async _historyRequest(msg: KernelMessage.IMessage): Promise<void> {
const historyMsg = msg as KernelMessage.IHistoryRequestMsg;
const message = KernelMessage.createMessage<KernelMessage.IHistoryReplyMsg>(
{
msgType: 'history_reply',
channel: 'shell',
parentHeader: historyMsg.header,
session: msg.header.session,
content: {
status: 'ok',
history: this._history as KernelMessage.IHistoryReply['history']
}
const message = KernelMessage.createMessage<KernelMessage.IHistoryReplyMsg>({
msgType: 'history_reply',
channel: 'shell',
parentHeader: historyMsg.header,
session: msg.header.session,
content: {
status: 'ok',
history: this._history as KernelMessage.IHistoryReply['history']
}
);
});
this._sendMessage(message);
}
......@@ -326,18 +320,16 @@ export abstract class BaseKernel implements IKernel {
private _executeInput(msg: KernelMessage.IMessage): void {
const parent = msg as KernelMessage.IExecuteInputMsg;
const code = parent.content.code;
const message = KernelMessage.createMessage<KernelMessage.IExecuteInputMsg>(
{
msgType: 'execute_input',
parentHeader: parent.header,
channel: 'iopub',
session: msg.header.session,
content: {
code,
execution_count: this._executionCount
}
const message = KernelMessage.createMessage<KernelMessage.IExecuteInputMsg>({
msgType: 'execute_input',
parentHeader: parent.header,
channel: 'iopub',
session: msg.header.session,
content: {
code,
execution_count: this._executionCount
}
);
});
this._sendMessage(message);
}
......@@ -349,14 +341,9 @@ export abstract class BaseKernel implements IKernel {
*/
private _executeResult(
msg: KernelMessage.IMessage,
content: Pick<
KernelMessage.IExecuteResultMsg['content'],
'data' | 'metadata'
>
content: Pick<KernelMessage.IExecuteResultMsg['content'], 'data' | 'metadata'>
): void {
const message = KernelMessage.createMessage<
KernelMessage.IExecuteResultMsg
>({
const message = KernelMessage.createMessage<KernelMessage.IExecuteResultMsg>({
msgType: 'execute_result',
parentHeader: msg.header,
channel: 'iopub',
......@@ -380,15 +367,13 @@ export abstract class BaseKernel implements IKernel {
content: KernelMessage.IExecuteReplyMsg['content']
): void {
const parent = msg as KernelMessage.IExecuteRequestMsg;
const message = KernelMessage.createMessage<KernelMessage.IExecuteReplyMsg>(
{
msgType: 'execute_reply',
channel: 'shell',
parentHeader: parent.header,
session: msg.header.session,
content
}
);
const message = KernelMessage.createMessage<KernelMessage.IExecuteReplyMsg>({
msgType: 'execute_reply',
channel: 'shell',
parentHeader: parent.header,
session: msg.header.session,
content
});
this._sendMessage(message);
}
......@@ -420,9 +405,7 @@ export abstract class BaseKernel implements IKernel {
private async _complete(msg: KernelMessage.IMessage): Promise<void> {
const completeMsg = msg as KernelMessage.ICompleteRequestMsg;
const content = await this.completeRequest(completeMsg.content);
const message = KernelMessage.createMessage<
KernelMessage.ICompleteReplyMsg
>({
const message = KernelMessage.createMessage<KernelMessage.ICompleteReplyMsg>({
msgType: 'complete_reply',
parentHeader: completeMsg.header,
channel: 'shell',
......
......@@ -2,10 +2,7 @@ import { ObservableMap } from '@jupyterlab/observables';
import { Kernel, KernelMessage } from '@jupyterlab/services';
import {
deserialize,
serialize
} from '@jupyterlab/services/lib/kernel/serialize';
import { deserialize, serialize } from '@jupyterlab/services/lib/kernel/serialize';
import { UUID } from '@lumino/coreutils';
......@@ -69,11 +66,7 @@ export class Kernels implements IKernels {
return kernel;
};
const hook = (
kernelId: string,
clientId: string,
socket: WebSocket
): void => {
const hook = (kernelId: string, clientId: string, socket: WebSocket): void => {
const kernel = this._kernels.get(kernelId);
if (!kernel) {
......
......@@ -128,9 +128,7 @@ export interface IKernel extends IObservableDisposable {
*
* @param content - The content of the request.
*/
inputRequest(
content: KernelMessage.IInputRequestMsg['content']
): Promise<void>;
inputRequest(content: KernelMessage.IInputRequestMsg['content']): Promise<void>;
}
/**
......
......@@ -3,10 +3,7 @@
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
import {
JupyterLiteServer,
JupyterLiteServerPlugin
} from '@jupyterlite/server';
import { JupyterLiteServer, JupyterLiteServerPlugin } from '@jupyterlite/server';
import { IKernel, IKernelSpecs } from '@jupyterlite/kernel';
......@@ -26,9 +23,7 @@ const kernel: JupyterLiteServerPlugin<void> = {
requires: [IKernelSpecs],
activate: (app: JupyterLiteServer, kernelspecs: IKernelSpecs) => {
const url