diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..20700c60
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,22 @@
+node_modules
+**/node_modules
+**/lib
+**/build
+**/static
+jupyterlab/schemas
+jupyterlab/themes
+jupyterlab/geckodriver
+jupyterlab/staging/yarn.js
+jupyterlab/chrome-test.js
+jupyterlab/staging/index.js
+dev_mode/index.js
+dev_mode/schemas
+dev_mode/static
+dev_mode/themes
+dev_mode/workspaces
+examples/app/build
+examples/app/themes
+examples/app/schemas
+tests/**/coverage
+docs/_build
+docs/api
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..e9b61604
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,23 @@
+{
+ "env": {
+ "browser": true,
+ "node": true,
+ "commonjs": true
+ },
+ "extends": ["eslint:recommended", "prettier"],
+ "plugins": ["prettier"],
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module",
+ "ecmaFeatures": {
+ "modules": true,
+ "jsx": true
+ }
+ },
+ "rules": {
+ "prettier/prettier": ["error", { "singleQuote": true }],
+ "indent": ["error", 2],
+ "linebreak-style": ["error", "unix"],
+ "no-console": ["error", { "allow": ["warn", "error"] }]
+ }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..0f0d132a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+jupyterlab/staging/yarn.js binary
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..c6f6aa83
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,58 @@
+---
+name: Bug report
+about: Create a report to help us improve
+---
+
+Hi! Thanks for using JupyterLab.
+
+If you are reporting an issue with JupyterLab, please use the [GitHub issue](https://github.com/jupyterlab/jupyterlab/issues) search feature to check if your issue has been asked already. If it has, please add your comments to the existing issue.
+
+The JupyterLab team and Project Jupyter value our community, and JupyterLab
+follows the Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+
+- OS: [e.g. Windows 10]
+- Browser [e.g. chrome 69.0, firefox 62.0]
+- JupyterLab [e.g. 0.34.2]
+
+**Additional context**
+Add any other context about the problem here.
+
+If available, please include the following details:
+
+Troubleshoot Output
+
+Paste the output from running `jupyter troubleshoot` from the command line here.
+You may want to sanitize the paths in the output.
+
+
+
+Command Line Output
+
+Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
+
+
+
+Browser Output
+
+Paste the output from your browser console here.
+
+
diff --git a/.github/ISSUE_TEMPLATE/installation-and-configuration-issues.md b/.github/ISSUE_TEMPLATE/installation-and-configuration-issues.md
new file mode 100644
index 00000000..32344a5a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/installation-and-configuration-issues.md
@@ -0,0 +1,6 @@
+---
+name: Installation and configuration issues
+about: Installation and configuration assistance
+---
+
+If you are having issues with installation or configuration, we encourage you to post in the [Jupyter Discourse forum](https://discourse.jupyter.org/c/jupyterlab) or file an issue here.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..d5c0e606
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,4 @@
+Fixes #
+
+**Screenshots**
+If applicable, add before and after screenshots to help show the issue being fixed, or the appearance of a new feature.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..58eb0f49
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,48 @@
+MANIFEST
+build
+dist
+lib
+jupyterlab/static
+jupyterlab/schemas
+jupyterlab/themes
+jupyterlab/geckodriver
+dev_mode/schemas
+dev_mode/static
+dev_mode/themes
+dev_mode/workspaces
+dev_mode/stats.json
+
+packages/theme-*/static
+node_modules
+.cache
+.vscode
+*.py[co]
+.pytest_cache
+__pycache__
+*.egg-info
+*~
+*.bak
+.ipynb_checkpoints
+.DS_Store
+\#*#
+.#*
+
+*.swp
+*.map
+.idea/
+
+coverage/
+tests/**/coverage
+tests/**/.cache-loader
+docs/_build
+docs/api
+docs/source/_build
+packages/services/examples/node/config.json
+examples/app/build
+examples/app/themes
+examples/app/schemas
+
+lerna-debug.log
+yarn-error.log
+
+junit.xml
diff --git a/.lintstagedrc b/.lintstagedrc
new file mode 100644
index 00000000..3dabd6ec
--- /dev/null
+++ b/.lintstagedrc
@@ -0,0 +1,8 @@
+{
+ "linters": {
+ "**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}": [
+ "prettier --write",
+ "git add"
+ ]
+ }
+}
diff --git a/.meeseeksdev.yml b/.meeseeksdev.yml
new file mode 100644
index 00000000..165a153d
--- /dev/null
+++ b/.meeseeksdev.yml
@@ -0,0 +1,6 @@
+special:
+ everyone:
+ can:
+ - say
+ - tag
+ - untag
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..ca3e7368
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,22 @@
+node_modules
+**/node_modules
+**/lib
+**/build
+**/static
+jupyterlab/schemas
+jupyterlab/themes
+jupyterlab/geckodriver
+jupyterlab/staging/yarn.js
+jupyterlab/staging/index.js
+dev_mode/index.js
+dev_mode/schemas
+dev_mode/static
+dev_mode/themes
+dev_mode/workspaces
+examples/app/build
+examples/app/themes
+examples/app/schemas
+tests/**/coverage
+docs/_build
+docs/api
+**/package.json
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..92cde390
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "singleQuote": true
+}
\ No newline at end of file
diff --git a/.yarnrc b/.yarnrc
new file mode 100644
index 00000000..9751ca9a
--- /dev/null
+++ b/.yarnrc
@@ -0,0 +1,2 @@
+yarn-path "./jupyterlab/staging/yarn.js"
+ignore-optional true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..69d40fc9
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,392 @@
+# Contributing to JupyterLab
+
+If you're reading this section, you're probably interested in contributing to
+JupyterLab. Welcome and thanks for your interest in contributing!
+
+Please take a look at the Contributor documentation, familiarize yourself with
+using JupyterLab, and introduce yourself on the mailing list and share
+what area of the project you are interested in working on. Please see also the
+Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
+
+We have labeled some issues as [good first issue](https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) or [help wanted](https://github.com/jupyterlab/jupyterlab/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
+that we believe are good examples of small, self-contained changes.
+We encourage those that are new to the code base to implement and/or ask
+questions about these issues.
+
+## Tag Issues with Labels
+
+Users without the commit rights to the jupyterlab repository can also tag the issues with labels. For example: To apply the label `foo` and `bar baz` to an issue, comment `@meeseeksdev tag foo "bar baz"` on the issue.
+
+## General Guidelines
+
+For general documentation about contributing to Jupyter projects, see the
+[Project Jupyter Contributor Documentation](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html) and [Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md).
+
+All source code is written in
+[TypeScript](http://www.typescriptlang.org/Handbook). See the [Style
+Guide](https://github.com/jupyterlab/jupyterlab/wiki/TypeScript-Style-Guide).
+
+All source code is formatted using [prettier](https://prettier.io).
+When code is modified and committed, all staged files will be automatically
+formatted using pre-commit git hooks (with help from the
+[lint-staged](https://github.com/okonet/lint-staged) and
+[husky](https://github.com/typicode/husky) libraries). The benefit of using a
+code formatter like prettier is that it removes the topic of code style from the conversation
+when reviewing pull requests, thereby speeding up the review process.
+
+You may also use the prettier npm script (e.g. `npm run prettier` or `yarn prettier` or `jlpm prettier`) to format the entire code base. We recommend
+installing a prettier
+extension for your code editor and configuring it to format your code with
+a keyboard shortcut or automatically on save.
+
+## Setting Up a Development Environment
+
+You can launch a binder with the latest JupyterLab master to test something (this may take a few minutes to load): [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/jupyterlab/master?urlpath=lab-dev/)
+
+### Installing Node.js and jlpm
+
+Building JupyterLab from its GitHub source code requires Node.js.
+
+If you use `conda`, you can get it with:
+
+```bash
+conda install -c conda-forge 'nodejs'
+```
+
+If you use [Homebrew](http://brew.sh/) on Mac OS X:
+
+```bash
+brew install node
+```
+
+You can also use the installer from the [Node.js](https://nodejs.org) website.
+
+## Installing JupyterLab
+
+JupyterLab requires Jupyter Notebook version 4.3 or later.
+
+If you use `conda`, you can install notebook using:
+
+```bash
+conda install -c conda-forge notebook
+```
+
+You may also want to install `nb_conda_kernels` to have a kernel option for different [conda environments](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html)
+
+```bash
+conda install -c conda-forge nb_conda_kernels
+```
+
+If you use `pip`, you can install notebook using:
+
+```bash
+pip install notebook
+```
+
+Fork the JupyterLab [repository](https://github.com/jupyterlab/jupyterlab).
+
+Once you have installed the dependencies mentioned above, use the following
+steps:
+
+```bash
+git clone https://github.com//jupyterlab.git
+cd jupyterlab
+pip install -e .
+jlpm install
+jlpm run build # Build the dev mode assets (optional)
+jlpm run build:core # Build the core mode assets (optional)
+jupyter lab build # Build the app dir assets (optional)
+```
+
+Notes:
+
+- A few of the scripts will run "python". If your target python is called something else (such as "python3") then parts of the build will fail. You may wish to build in a conda environment, or make an alias.
+
+- The `jlpm` command is a JupyterLab-provided, locked version of the [yarn](https://yarnpkg.com/en/) package manager. If you have `yarn` installed
+ already, you can use the `yarn` command when developing, and it will use the
+ local version of `yarn` in `jupyterlab/yarn.js` when run in the repository or
+ a built application directory.
+
+- At times, it may be necessary to clean your local repo with the command `npm run clean:slate`. This will clean the repository, and re-install and
+ rebuild.
+
+- If `pip` gives a `VersionConflict` error, it usually means that the installed
+ version of `jupyterlab_server` is out of date. Run `pip install --upgrade jupyterlab_server` to get the latest version.
+
+- To install JupyterLab in isolation for a single conda/virtual environment, you can add the `--sys-prefix` flag to the extension activation above; this will tie the installation to the `sys.prefix` location of your environment, without writing anything in your user-wide settings area (which are visible to all your envs):
+
+- You can run `jlpm run build:dev:prod` to build more accurate sourcemaps that show the original
+ Typescript code when debugging. However, it takes a bit longer to build the sources, so is used only to build for production
+ by default.
+
+If you are using a version of Jupyter Notebook earlier than 5.3, then
+you must also run the following command to enable the JupyterLab
+server extension:
+
+```bash
+jupyter serverextension enable --py --sys-prefix jupyterlab
+```
+
+For installation instructions to write documentation, please see [Writing Documentation](#writing-documentation)
+
+### Run JupyterLab
+
+Start JupyterLab in development mode:
+
+```bash
+jupyter lab --dev-mode
+```
+
+Development mode ensures that you are running the JavaScript assets that are
+built in the dev-installed Python package. When running in dev mode, a red
+stripe will appear at the top of the page; this is to indicate running
+an unreleased version.
+
+### Build and Run the Tests
+
+```bash
+jlpm run build:test
+jlpm test
+```
+
+You can run tests for an individual package by changing to the appropriate
+folder in tests:
+
+```bash
+cd tests/test-notebook
+jlpm test
+```
+
+Note: We are in the process of changing our test suite over to use `jest`. For folders
+that have a `jest.conf.js` file, please see the `jest` specific instructions below.
+
+You can also select specific test file(s) to run using a pattern:
+
+```bash
+cd tests/test-notebook
+jlpm test --pattern=src/*.spec.ts
+jlpm test --pattern=src/history.spec.ts
+```
+
+You can run `jlpm watch` from a test folder, and it will re-run the tests
+when the source file(s) change. Note that you have to launch the browser
+of your choice after it says `No captured browser`. You can put a `debugger`
+statement on a line and open the browser debugger to debug specific tests.
+`jlpm watch` also accepts the `--pattern` argument.
+
+Note that there are some helper functions in `testutils` (which is a public npm package called `@jupyterlab/testutils`) that are used by many of the tests.
+
+We use `karma` to run our tests in a browser, `mocha` as the test framework, and `chai` for test assertions. We use [async/await](https://mochajs.org/#using-async--await) for asynchronous tests. We have
+a helper function in `@jupyterlab/testutils` called `testEmission` to help with
+writing tests that use `Phosphor` signals, as well as a `framePromise` function
+to get a `Promise` for a `requestAnimationFrame`. We sometimes have to set
+a sentinel value inside a `Promise` and then check that the sentinel was set if
+we need a promise to run without blocking.
+
+To create a new test for a package in `packages/`, use the following
+command, where `` is the name of the folder in
+`packages/`:
+
+```bash
+jlpm create:test
+```
+
+#### Running Jest Tests
+
+For those test folders that use `jest`, they can be run as `jlpm test` to run the files
+directly. You can also use `jlpm test --namePattern=` to specify specific test
+suite names, and `jlpm test --pathPattern=` to specify specific test module names. In order to watch the code, add a `debugger` line in your code and run `jlpm watch`. This will start a node V8 debugger, which can be debugged
+in Chrome by browsing to `chrome://inspect/` and launching the remote session.
+
+### Build and run the stand-alone examples
+
+To install and build the examples in the `examples` directory:
+
+```bash
+jlpm run build:examples
+```
+
+To run a specific example, change to the examples directory (i.e.
+`examples/filebrowser`) and enter:
+
+```bash
+python main.py
+```
+
+## Debugging
+
+All methods of building JupyterLab produce source maps. The source maps
+should be available in the source files view of your browser's development
+tools under the `webpack://` header.
+
+When running JupyterLab normally, expand the `~` header to see the source maps for individual packages.
+
+When running in `--dev-mode`, the core packages are available under
+`packages/`, while the third party libraries are available under `~`.
+Note: it is recommended to use `jupyter lab --watch --dev-mode` while
+debugging.
+
+When running a test, the packages will be available at the top level
+(e.g. `application/src`), and the current set of test files available under
+`/src`. Note: it is recommended to use `jlpm run watch` in the test folder
+while debugging test options. See [above](#build-and-run-the-tests) for more info.
+
+---
+
+## High level Architecture
+
+The JupyterLab application is made up of two major parts:
+
+- an npm package
+- a Jupyter server extension (Python package)
+
+Each part is named `jupyterlab`. The [developer tutorial documentation](https://jupyterlab.readthedocs.io/en/latest/index.html)
+provides additional architecture information.
+
+## The NPM Packages
+
+The repository consists of many npm packages that are managed using the lerna
+build tool. The npm package source files are in the `packages/` subdirectory.
+
+### Build the NPM Packages from Source
+
+```bash
+git clone https://github.com/jupyterlab/jupyterlab.git
+cd jupyterlab
+pip install -e .
+jlpm
+jlpm run build:packages
+```
+
+**Rebuild**
+
+```bash
+jlpm run clean
+jlpm run build:packages
+```
+
+## [Writing Documentation](#writing-documenation)
+
+Documentation is written in Markdown and reStructuredText. In particular, the documentation on our Read the Docs page is written in reStructuredText. To ensure that the Read the Docs page builds, you'll need to install the documentation dependencies with `conda`. These dependencies are located in `docs/environment.yml`. You can install the dependencies for building the documentation by creating a new conda environment:
+
+```bash
+conda env create -f docs/environment.yml
+```
+
+Alternatively, you can install the documentation dependencies in an existing environment using the following command:
+
+```bash
+conda env update -n -f docs/environment.yml
+```
+
+The Developer Documentation includes a [guide](http://jupyterlab.readthedocs.io/en/latest/developer/documentation.html) to writing documentation including writing style, naming conventions, keyboard shortcuts, and screenshots.
+
+To test the docs run:
+
+```
+py.test --check-links -k .md . || py.test --check-links -k .md --lf .
+```
+
+The Read the Docs pages can be built using `make`:
+
+```bash
+cd docs
+make html
+```
+
+Or with `jlpm`:
+
+```
+jlpm run docs
+```
+
+## The Jupyter Server Extension
+
+The Jupyter server extension source files are in the `jupyterlab/`
+subdirectory. To use this extension, make sure the Jupyter Notebook server
+version 4.3 or later is installed.
+
+### Build the JupyterLab server extension
+
+When you make a change to JupyterLab npm package source files, run:
+
+```bash
+jlpm run build
+```
+
+to build the changes, and then refresh your browser to see the changes.
+
+To have the system build after each source file change, run:
+
+```bash
+jupyter lab --dev-mode --watch
+```
+
+## Build Utilities
+
+There is a range of build utilities for maintaining the repository.
+To get a suggested version for a library use `jlpm run get:dependency foo`.
+To update the version of a library across the repo use `jlpm run update:dependency foo ^latest`.
+To remove an unwanted dependency use `jlpm run remove:dependency foo`.
+
+The key utility is `jlpm run integrity`, which ensures the integrity of
+the packages in the repo. It will:
+
+- Ensure the core package version dependencies match everywhere.
+- Ensure imported packages match dependencies.
+- Ensure a consistent version of all packages.
+- Manage the meta package.
+
+The `packages/metapackage` package is used to build all of the TypeScript
+in the repository at once, instead of 50+ individual builds.
+
+The integrity script also allows you to automatically add a dependency for
+a package by importing from it in the TypeScript file, and then running:
+`jlpm run integrity` from the repo root.
+
+We also have scripts for creating and removing packages in `packages/`,
+`jlpm run create:package` and `jlpm run remove:package`.
+
+## Testing Changes to External Packages
+
+### Linking/Unlinking Packages to JupyterLab
+
+If you want to make changes to one of JupyterLab's external packages (for example, [Phosphor](https://github.com/phosphorjs/phosphor)) and test them out against your copy of JupyterLab, you can easily do so using the `link` command:
+
+1. Make your changes and then build the external package
+2. Register a link to the modified external package
+ - navigate to the external package dir and run `jlpm link`
+3. Link JupyterLab to modded package
+ - navigate to top level of your JupyterLab repo, then run `jlpm link ""`
+
+You can then (re)build JupyterLab (eg `jlpm run build`) and your changes should be picked up by the build.
+
+To restore JupyterLab to its original state, you use the `unlink` command:
+
+1. Unlink JupyterLab and modded package
+ - navigate to top level of your JupyterLab repo, then run `jlpm unlink ""`
+2. Reinstall original version of the external package in JupyterLab
+ - run `jlpm install --check-files`
+
+You can then (re)build JupyterLab and everything should be back to default.
+
+### Possible Linking Pitfalls
+
+If you're working on an external project with more than one package, you'll probably have to link in your copies of every package in the project, including those you made no changes to. Failing to do so may cause issues relating to duplication of shared state.
+
+Specifically, when working with Phosphor, you'll probably have to link your copy of the `"@phosphor/messaging"` package (in addition to whatever packages you actually made changes to). This is due to potential duplication of objects contained in the `MessageLoop` namespace provided by the `messaging` package.
+
+## Notes
+
+- By default, the application will load from the JupyterLab staging directory (default is `/share/jupyter/lab/build`. If you wish to run
+ the core application in `/jupyterlab/build`,
+ run `jupyter lab --core-mode`. This is the core application that will
+ be shipped.
+
+- If working with extensions, see the extension documentation on
+ https://jupyterlab.readthedocs.io/en/latest/index.html.
+
+- The npm modules are fully compatible with Node/Babel/ES6/ES5. Simply
+ omit the type declarations when using a language other than TypeScript.
+
+- For more information, read the [documentation](http://jupyterlab.readthedocs.io/en/latest/).
diff --git a/CORPORATE.md b/CORPORATE.md
new file mode 100644
index 00000000..046b9031
--- /dev/null
+++ b/CORPORATE.md
@@ -0,0 +1,112 @@
+# Corporate Engagement and Contribution Guide
+
+Along with welcoming contributions from individuals, we also welcome
+contributions to JupyterLab from corporations. Over a number of years of working
+with a wide range of corporations, we have discovered a set of principles and
+practices that enable these collaborations to be healthy, productive, and
+sustainable. These principles and practices are detailed here, along with a
+mental model that sets the stage.
+
+## Mental Model
+
+We would like to offer the following mental model of open-source projects, set
+as a parable. This idea was first introduced by a core Python contributor, Brett
+Cannon, on Twitter:
+
+> Pull requests can be like someone trying to give you a puppy you didn't ask
+> for; they mean well, but they can forget a puppy is a decade-or-more commitment…
+
+Let's expand on this idea a bit more.
+
+An open-source software project is like a big yard full of puppies. Puppies are
+amazing!!! Lots of people stop by to look at the puppies, play with them, take
+them on walks, and play fetch. These people are like the users of an open-source
+project. A few people, the maintainers, live in the yard with the puppies. The
+maintainers feed the puppies, take care of them, wash them, take them to the
+vet, get their shots, and keep the yard clean. Just to keep the existing puppies
+happy and healthy requires a ton of work and money.
+
+Working in a yard full of puppies is CRAZY! At times, there can be a line around
+the block of people wanting to play with the puppies and talk, or complain, to
+the maintainers. Sometimes, people show up with more puppies. These are like pull
+requests to an open-source project. While the maintainers love puppies and would
+love to be able to have more of them for everyone to enjoy, each new puppy has
+to be cleaned, fed, trained, taken to the vet, etc.
+
+## Practices and Principles
+
+The following principles and practices should be followed by corporations
+wanting to contribute to JupyterLab. These apply to both corporations as a
+whole, as well as individual contributors within those corporations.
+
+**Empower individual contributors.** _Encourage your employees to help at the
+puppy yard._ In the daily development and design work of the project, everyone
+contributes as individuals. Because of this, if your corporation wants to engage
+with and contribute to JupyterLab, the primary means will be through allocating,
+empowering, and encouraging your individual employees to contribute in an
+ongoing manner. Put more bluntly, pay your employees to contribute to Jupyter.
+
+**Make balanced contributions.** _You can help take care of the puppies in a
+variety of ways._ We expect all contributors to make balanced contributions that
+match their skill level and level of participation. Balanced contributions
+include work on new features, fixing bugs, reviewing pull requests, writing
+documentation, issue triage, answering users' questions, attending meetings,
+helping with releases, and other maintenance tasks. Balanced contributions build
+trust with the team and scale the human side of the project in a healthy and
+sustainable manner.
+
+**Help review pull requests.** One aspect of this balanced contribution approach
+deserves special attention: reviewing pull requests. Submitting pull requests
+with new features is an exciting and high-profile way to contribute. However,
+reviewing such pull requests is probably the biggest bottleneck in the
+development of open-source software. If you want your pull requests to move
+through review quickly, proactively review and test the pull requests of other
+contributors. Helping us to keep the pull request queue moving quickly
+accelerates the work of everyone.
+
+**Use public communications channels whenever possible.** JupyterLab is
+developed openly on GitHub. We have weekly video meetings on Wednesday at 9:00
+am PT on Zoom (https://calpoly.zoom.us/my/jupyter). Finally, we have a public
+chat room on gitter (https://gitter.im/jupyterlab/jupyterlab). As much as
+possible, all work and communications should take place on these public
+channels. We welcome you to join these channels and introduce yourself.
+We are willing to have private conversations once in a while, but we
+try to minimize their frequency and always summarize them in our
+public channels.
+
+**Hire our core maintainers and give them more time to contribute.** _Consider
+paying the regulars around here to keep looking after the puppies._ Our core
+maintainers love working on Jupyter, but have to balance those contributions
+with the reality of paid employment. If you are interested in hiring a core
+maintainer, we encourage you to do this in a way that gives them **more** time
+to contribute than they currently have. Put this into their employment contract.
+This is an incredibly effective way of turbocharging the work of the project and
+building trust in the community. Conversely, if you hire a maintainer who wants
+to work on the project and give them less time to contribute, you may damage the
+project and your organization may find itself alienated from the community.
+
+**Commit to maintaining your contributions.** _Don't abandon puppies here!_
+Contributions, especially those for new features, add to the ongoing maintenance
+load of the project. New features bring new users to the project, and along with
+them, bug reports, ideas, discussions, questions, refactoring, etc. If your
+company wants to contribute new features, we ask that you commit to maintaining
+those features for a period of at least 2-3 years. This isn't a legal contract,
+but we do want you to carefully consider the maintenance of your contributions.
+If a new feature is abandonded and we are unable to maintain it, it may be
+deprecated or removed.
+
+**Don't surprise us.** _Don't build us a new dog park, when what we really need
+is just a couple more kennels._ Before beginning major work, build trust through
+balanced contributions, and talk to us about your plans (see public
+communication channels above). See if there are existing issues on the topic, or
+open a new issue describing the work. There may be other people already working
+on it, or the work may be blocked by other factors.
+
+**Be patient.** _Puppies require training, and complex new tricks can take years
+and the right motivation to learn._ Open-source software projects offer a unique
+path to innovation by bringing together users and contributors with diverse
+needs and usage cases. Even if fully resourced, the resulting innovation can be
+slow, possibly much slower than your corporation's desired timeline in the short
+term. However, in the long-term, sustainable open-source can lead to rapid
+growth and development. Help us build a sustainable project through long-term
+thinking, strategy, and resource allocation.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..91aeae19
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,34 @@
+Copyright (c) 2015 Project Jupyter Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Semver File License
+===================
+
+The semver.py file is from https://github.com/podhmo/python-semver
+which is licensed under the "MIT" license. See the semver.py file for details.
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..6dccc0ef
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,9 @@
+include package.json
+include LICENSE
+include CONTRIBUTING.md
+include README.md
+include setupbase.py
+
+# Documentation
+graft docs
+exclude docs/\#*
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..fa372b5b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,203 @@
+**[Installation](#installation)** |
+**[Documentation](http://jupyterlab.readthedocs.io)** |
+**[Contributing](#contributing)** |
+**[License](#license)** |
+**[Team](#team)** |
+**[Getting help](#getting-help)** |
+
+# [JupyterLab](http://jupyterlab.github.io/jupyterlab/)
+
+[![PyPI version](https://badge.fury.io/py/jupyterlab.svg)](https://badge.fury.io/py/jupyterlab)
+[![Build Status](https://dev.azure.com/jupyterlab/jupyterlab/_apis/build/status/jupyterlab.jupyterlab?branchName=master)](https://dev.azure.com/jupyterlab/jupyterlab/_build/latest?definitionId=1&branchName=master)
+[![Documentation Status](https://readthedocs.org/projects/jupyterlab/badge/?version=stable)](http://jupyterlab.readthedocs.io/en/stable/)
+[![GitHub](https://img.shields.io/badge/issue_tracking-github-blue.svg)](https://github.com/jupyterlab/jupyterlab/issues)
+[![Discourse](https://img.shields.io/badge/help_forum-discourse-blue.svg)](https://discourse.jupyter.org/c/jupyterlab)
+[![Gitter](https://img.shields.io/badge/social_chat-gitter-blue.svg)](https://gitter.im/jupyterlab/jupyterlab)
+
+[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab/tree/demo)
+
+An extensible environment for interactive and reproducible computing, based on the
+Jupyter Notebook and Architecture. [Currently ready for users.](https://blog.jupyter.org/jupyterlab-is-ready-for-users-5a6f039b8906)
+
+[JupyterLab](http://jupyterlab.readthedocs.io/en/stable/) is the next-generation user interface for [Project Jupyter](https://jupyter.org) offering
+all the familiar building blocks of the classic Jupyter Notebook (notebook,
+terminal, text editor, file browser, rich outputs, etc.) in a flexible and
+powerful user interface.
+JupyterLab will eventually replace the classic Jupyter Notebook.
+
+JupyterLab can be extended using [npm](https://www.npmjs.com/) packages
+that use our public APIs. To find JupyterLab extensions, search for the npm keyword [jupyterlab-extension](https://www.npmjs.com/search?q=keywords:jupyterlab-extension) or the GitHub topic [jupyterlab-extension](https://github.com/topics/jupyterlab-extension). To learn more about extensions, see the [user documentation](https://jupyterlab.readthedocs.io/en/latest/user/extensions.html).
+
+The current JupyterLab releases are suitable for general
+usage, and the extension APIs will continue to
+evolve for JupyterLab extension developers.
+
+Read the latest version's documentation on [ReadTheDocs](http://jupyterlab.readthedocs.io/en/latest/).
+
+---
+
+## Getting started
+
+### Installation
+
+[install](http://jupyterlab.readthedocs.io/en/stable/getting_started/installation.html) JupyterLab using `conda`, `pip`, or `pipenv`. Conda is recommended if you have no installation preference.
+
+Project installation instructions from the git sources are available in the [contributor documentation](CONTRIBUTING.md).
+
+#### conda
+
+Conda is an open source package management system and environment management system that runs on Windows, macOS, and Linux. Conda packages and distributes software for any language, and by default uses the Anaconda repository managed by Anaconda Inc. To install conda, please [see the conda installation instructions](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html).
+
+Install the [JupyterLab `conda` package](https://anaconda.org/conda-forge/jupyterlab) with:
+
+```bash
+conda install -c conda-forge jupyterlab
+```
+
+#### pip
+
+pip is a package management system for installing and updating Python packages, and comes with any Python installation. On Ubuntu and Fedora Linux, use the system package manager to install the `python3-pip` package. [\_The Hitchhiker's Guide to Python_provides guidance on how to install Python](https://docs.python-guide.org/starting/installation/); Another option is to [install Python directly from python.org](https://www.python.org/getit/). We suggest you [upgrade pip](https://pip.pypa.io/en/stable/installing/) before using it to install other programs.
+
+JupyterLab requires Python 3.5 or higher.
+
+1. When using Windows with Python version 3.5 or higher, use the [Python Launcher for Windows](https://docs.python.org/3/using/windows.html?highlight=shebang#python-launcher-for-windows) to use `pip` with Python version 3:
+ ```bash
+ py -3 -m pip install jupyterlab
+ ```
+2. If the system has a `python3` command (standard on Unix-like systems), install with the comand:
+ ```bash
+ python3 -m pip install jupyterlab
+ ```
+3. Using the `python` command directly is another option, but this will use the _current_ version of Python (which may be Python version 2 or version 3 if both are installed):
+ ```bash
+ python -m pip install jupyterlab
+ ```
+
+Some systems have a `pip3` command that has the same effect as `python3 -m pip` and/or a `pip` command that behaves the same as `python -m pip`.
+
+Adding `--user` after `pip install` will install the files to a local user install directory (typically `~/.local/` or `%APPDATA%\Python` on Windows) instead of the system-wide directory. This can be helpful, especially if writing to the system-wide directory is not permitted. However, the user-level `bin` directory must be added to the `PATH` environment variable in order to launch `jupyter lab`.
+
+#### pipenv
+
+`Pipenv` provides users and developers of applications with an easy method to setup a working environment, however Python must be installed first. See the [pipenv installation documentation](https://docs.pipenv.org/install) to use Pipenv if it is not installed.
+
+`pipenv` can be installed as:
+
+```bash
+pipenv install jupyterlab
+pipenv shell
+```
+
+or from a git checkout:
+
+```bash
+pipenv install git+git://github.com/jupyterlab/jupyterlab.git#egg=jupyterlab
+pipenv shell
+```
+
+When using `pipenv`, in order to launch `jupyter lab`, activate the project's virtualenv. For example, in the directory where `pipenv`'s `Pipfile` and `Pipfile.lock` live (i.e., where the above commands were run):
+
+```bash
+pipenv shell
+jupyter lab
+```
+
+#### Installing with Previous Versions of Jupyter Notebook
+
+When using a version of Jupyter Notebook earlier than 5.3, the following command must be run
+after installation to enable the JupyterLab server extension:
+
+```bash
+jupyter serverextension enable --py jupyterlab --sys-prefix
+```
+
+### Running
+
+Start up JupyterLab using:
+
+```bash
+jupyter lab
+```
+
+JupyterLab will open automatically in the browser. See the [documentation](http://jupyterlab.readthedocs.io/en/stable/getting_started/starting.html) for additional details.
+
+### Prerequisites and Supported Browsers
+
+Jupyter notebook version 4.3 or later is required. To check the notebook version, run the command:
+
+```bash
+jupyter notebook --version
+```
+
+The latest versions of the following browsers are currently _known to work_:
+
+- Firefox
+- Chrome
+- Safari
+
+See our [documentation](http://jupyterlab.readthedocs.io/en/latest/getting_started/installation.html) for additional details.
+
+---
+
+## Development
+
+### Contributing
+
+To contribute to the project, please read the [contributor documentation](CONTRIBUTING.md).
+
+JupyterLab follows the Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
+
+### Extending JupyterLab
+
+To start developing an extension, see the [developer documentation](https://jupyterlab.readthedocs.io/en/latest/developer/extension_dev.html) and the [API docs](http://jupyterlab.github.io/jupyterlab/index.html).
+
+### License
+
+JupyterLab uses a shared copyright model that enables all contributors to maintain the
+copyright on their contributions. All code is licensed under the terms of the revised [BSD license](https://github.com/jupyterlab/jupyterlab/blob/master/LICENSE).
+
+### Team
+
+JupyterLab is part of [Project Jupyter](http://jupyter.org/) and is developed by an open community. The maintenance team is assisted by a much larger group of contributors to JupyterLab and Project Jupyter as a whole.
+
+JupyterLab's current maintainers are listed in alphabetical order, with affiliation, and main areas of contribution:
+
+- Chris Colbert, Project Jupyter (co-creator, application/low-level architecture,
+ technical leadership, vision, PhosphorJS)
+- Afshin Darian, Two Sigma (co-creator, application/high-level architecture,
+ prolific contributions throughout the code base).
+- Jessica Forde, Project Jupyter (demo, documentation)
+- Tim George, Cal Poly (UI/UX design, strategy, management, user needs analysis)
+- Brian Granger, Cal Poly (co-creator, strategy, vision, management, UI/UX design,
+ architecture).
+- Jason Grout, Bloomberg (co-creator, vision, general development).
+- Fernando Perez, UC Berkeley (co-creator, vision).
+- Ian Rose, UC Berkeley (Real-time collaboration, document architecture).
+- Saul Shanabrook, Quansight (general development, extensions)
+- Steven Silvester, JPMorgan Chase (co-creator, release management, packaging,
+ prolific contributions throughout the code base).
+
+Maintainer emeritus:
+
+- Cameron Oelsen, Cal Poly (UI/UX design).
+
+This list is provided to give the reader context on who we are and how our team functions.
+To be listed, please submit a pull request with your information.
+
+---
+
+## Getting help
+
+We encourage you to ask questions on the [Discourse forum](https://discourse.jupyter.org/c/jupyterlab). A question answered there can become a useful resource for others.
+
+Please use the [GitHub issues page](https://github.com/jupyterlab/jupyterlab/issues) to provide feedback or submit a bug report.
+
+### Weekly Dev Meeting
+
+We have videoconference meetings every week where we discuss what we have been working on and get feedback from one another.
+
+Anyone is welcome to attend, if they would like to discuss a topic or just to listen in.
+
+- When: Wednesdays 9AM PT
+- Where: [`calpoly/jupyter` Zoom](https://calpoly.zoom.us/my/jupyter)
+- What: [Meeting notes on Dropbox Paper](https://paper.dropbox.com/doc/JLab-Dev-Meeting-Minutes-2019--AZlv6L3jnv8ntl6kJK88y5M5Ag-Lj0P4kI2JrbA0eXHZSdY5)
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 00000000..b4c36bfc
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,231 @@
+# Making a JupyterLab release
+
+This document guides a contributor through creating a release of JupyterLab.
+
+## Check installed tools
+
+Review `CONTRIBUTING.md`. Make sure all the tools needed to generate the
+built JavaScript files are properly installed.
+
+## Creating a full release
+
+We publish the npm packages, a Python source package, and a Python universal binary wheel. We also publish a conda package on conda-forge (see below).
+See the Python docs on [package uploading](https://packaging.python.org/guides/tool-recommendations/)
+for twine setup instructions and for why twine is the recommended method.
+
+## Getting a clean environment
+
+For convenience, here are commands for getting a completely clean repo. This makes sure that we don't have any extra tags or commits in our repo (especially since we will push our tags later in the process), and that we are on the master branch.
+
+```bash
+cd release
+conda deactivate
+conda remove --all -y -n jlabrelease
+rm -rf jupyterlab
+
+conda create -c conda-forge -y -n jlabrelease notebook nodejs twine
+conda activate jlabrelease
+git clone git@github.com:jupyterlab/jupyterlab.git
+cd jupyterlab
+pip install -ve .
+```
+
+### Publish the npm packages
+
+The command below ensures the latest dependencies and built files,
+then prompts you to select package versions. When one package has an
+effective major release, the packages that depend on it should also get a
+major release, to prevent consumers that are using the `^` semver
+requirement from getting a conflict. Note that we publish the
+JavaScript packages using the `next` tag until we are ready for the
+final release.
+
+```bash
+jlpm run publish:next
+```
+
+### Publish the Python package
+
+- Update `jupyterlab/_version.py` with an `rc` version
+- Prep the static assets for release:
+
+```bash
+jlpm run build:update
+```
+
+- Commit and tag and push the tag
+- Create the Python release artifacts:
+
+```bash
+rm -rf dist build
+python setup.py sdist
+python setup.py bdist_wheel --universal
+twine upload dist/*
+```
+
+### Post prerelease checklist
+
+- [ ] Modify and run `python scripts/milestone_check.py` to check the issues assigned to this milestone
+- [ ] Write [release highlights](https://github.com/jupyterlab/jupyterlab/blob/master/docs/source/getting_started/changelog.rst), starting with:
+ ```bash
+ loghub jupyterlab/jupyterlab -m XXX -t $GITHUB_TOKEN --template scripts/release_template.txt
+ ```
+- [ ] Test the release candidate in a clean environment
+- [ ] Make sure the CI builds pass
+ - The build will fail if we publish a new package because by default it is
+ private. Use `npm access public @jupyterlab/` to make it public.
+ - The build will fail if we forget to include `style/` in the `files:`
+ of a package (it will fail on the `jupyter lab build` command because
+ webpack cannot find the referenced styles to import.
+- [ ] Update the other repos:
+ - [ ] https://github.com/jupyterlab/extension-cookiecutter-js
+ - [ ] https://github.com/jupyterlab/extension-cookiecutter-ts
+ - [ ] https://github.com/jupyterlab/mimerender-cookiecutter
+ - [ ] https://github.com/jupyterlab/mimerender-cookiecutter-ts
+ - [ ] https://github.com/jupyterlab/jupyter-renderers
+ - [ ] https://github.com/jupyterhub/jupyterlab-hub
+- [ ] Add a tag to [ts cookiecutter](https://github.com/jupyterlab/extension-cookiecutter-ts) with the new JupyterLab version
+- [ ] Update the extension examples:
+ - [ ] [Notebook toolbar button](https://github.com/jupyterlab/jupyterlab/blob/master/docs/source/developer/notebook.rst#adding-a-button-to-the-toolbar)
+- [ ] Update the [xkcd tutorial](https://github.com/jupyterlab/jupyterlab/blob/master/RELEASE.md#updating-the-xkcd-tutorial)
+- [ ] At this point, there may have been some more commits merged. Run `python scripts/milestone_check.py` to check the issues assigned to this milestone one more time. Update changelog if necessary.
+- [ ] Publish the final (not prerelease) JavaScript packages using `jlpm run publish:next` at some point.
+
+Now do the actual final release:
+
+- [ ] Update `jupyterlab/_version.py` with a final version
+- [ ] Make a final Python release
+- [ ] Create a branch for the release and push to GitHub
+- [ ] Merge the PRs on the other repos and set the default branch of the
+ xckd repo
+- [ ] Update the `latest` npm tags by running `jlpm run update:dist-tags` and running the commands it prints out
+- [ ] Publish to [conda-forge](https://github.com/jupyterlab/jupyterlab/blob/master/RELEASE.md#publishing-to-conda-forge).
+
+After a few days (to allow for possible patch releases), set up development for
+the next release:
+
+- [ ] Update `jupyterlab/_version.py` with a `dev` version
+- [ ] Run `jlpm integrity` to update the `dev_mode` version
+- [ ] Commit and push the version update to master
+- [ ] Release the other repos as appropriate
+- [ ] Update version for [binder](https://github.com/jupyterlab/jupyterlab/blob/master/RELEASE.md#update-version-for-binder)
+
+### Updating the xkcd tutorial
+
+- Clone the repo if you don't have it
+
+```bash
+git clone git@github.com:jupyterlab/jupyterlab_xkcd.git
+```
+
+#### Simple updates by rebasing
+
+If the updates are simple, it may be enough to check out a new branch based on
+the current base branch, then rebase from the root commit, editing the root
+commit and other commits that involve installing packages to update to the new
+versions:
+
+```bash
+git checkout -b 0.XX # whatever the new version is
+git rebase -i --root
+```
+
+"Edit" the commits that involve installing packages, so you can update the
+`package.json`. Amend the last commit to bump the version number in package.json
+in preparation for publishing to npm. Then skip down to the step below about
+publishing the xkcd tutorial. If the edits are more substantial than just
+updating package versions, then do the next steps instead.
+
+#### Creating the tutorial from scratch
+
+- Create a new empty branch in the xkcd repo.
+
+```bash
+git checkout --orphan name-of-branch
+git rm -rf .
+git clean -dfx
+cookiecutter path-to-local-extension-cookiecutter-ts
+# Fill in the values from the previous branch package.json initial commit
+cp -r jupyterlab_xkcd/ .
+rm -rf jupyterlab_xkcd
+```
+
+- Create a new PR in JupyterLab.
+- Run through the tutorial in the PR, making commits and updating
+ the tutorial as appropriate.
+- For the publish section of the readme, use the `README`
+ file from the previous branch, as well as the `package.json` fields up to
+ `license`. Bump the version number in preparation for publishing to npm.
+
+#### Publishing xkcd tutorial changes
+
+- Replace the tag references in the tutorial with the new branch number, e.g.
+ replace `0.28-` with `0.29-`. Prefix the new tags with the branch name, e.g.
+ `0.28-01-show-a-panel`
+ ```bash
+ git tag 0.XX-01-show-a-panel HEAD~5
+ git tag 0.XX-02-show-a-comic HEAD~4
+ git tag 0.XX-03-style-and-attribute HEAD~3
+ git tag 0.XX-04-refactor-and-refresh HEAD~2
+ git tag 0.XX-05-restore-panel-state HEAD~1
+ git tag 0.XX-06-prepare-to-publish HEAD
+ ```
+- Push the branch with the new tags
+ ```bash
+ git push origin 0.XX --tags
+ ```
+ Set the branch as the default branch (see `github.com/jupyterlab/jupyterlab_xkcd/settings/branches`).
+- If there were changes to the example in the documentation, submit a PR to JupyterLab
+- Publish the new `@jupyterlab/xkcd` npm package. Make sure to update the version
+ number in the last commit of the branch.
+ ```bash
+ npm publish
+ ```
+
+If you make a mistake and need to start over, clear the tags using the
+following pattern:
+
+```bash
+git tag | grep 0.XX | xargs git tag -d
+```
+
+### Publishing to conda-forge
+
+- If no requirements have changed, wait for the conda-forge autotick-bot.
+- Otherwise:
+- Get the sha256 hash for conda-forge release:
+
+```bash
+shasum -a 256 dist/*.tar.gz
+```
+
+- Fork https://github.com/conda-forge/jupyterlab-feedstock
+- Create a PR with the version bump
+- Update `recipe/meta.yaml` with the new version and md5 and reset the build number to 0.
+
+## Making a patch release JavaScript package(s)
+
+- Backport the change to the previous release branch
+- Make a new PR against the previous branch
+- Run the following script, where the package is in `/packages/package-folder-name` (note that multiple packages can be given):
+
+```bash
+jlpm run patch:release package-folder-name
+```
+
+- Push the resulting commit and tag.
+- Create a new Python release on the previous branch
+- Cherry pick the patch commit to the master branch
+- Update the dev version of the master branch in `_version.py`
+- Update the `package.json` file in `dev_mode` with the new JupyterLab version in the `jupyterlab` metadata section.
+
+## Update version for binder
+
+Each time we release JupyterLab, we should update the version of JupyterLab
+used in binder and repo2docker. Here is an example PR that updates the
+relevant files:
+
+https://github.com/jupyter/repo2docker/pull/169/files
+
+This needs to be done in both the conda and pip buildpacks in both the
+frozen and non-frozen version of the files.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 00000000..8b9bbdca
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,97 @@
+jobs:
+ - job: 'Linux'
+ pool:
+ vmImage: 'ubuntu-16.04'
+ variables:
+ python.version: '3.6'
+ gh.ref: github.com/jupyterlab/jupyterlab.git
+ strategy:
+ matrix:
+ JS:
+ group: 'js'
+ testResultsFiles: 'tests/**/junit.xml'
+ Integrity:
+ group: 'integrity'
+ Python:
+ group: 'python'
+ testResultsFiles: 'junit.xml'
+ CLI:
+ group: 'cli'
+ python.version: '3.5'
+ Docs:
+ group: 'docs'
+
+ steps:
+ - task: UsePythonVersion@0
+ inputs:
+ versionSpec: '$(python.version)'
+ architecture: 'x64'
+
+ - task: Bash@3
+ displayName: 'install'
+ inputs:
+ targetType: 'filePath'
+ filePath: ./scripts/travis_install.sh
+ - script: python -m pip install virtualenv
+ - task: Bash@3
+ displayName: 'script'
+ inputs:
+ targetType: 'filePath'
+ filePath: ./scripts/travis_script.sh
+ - task: PublishTestResults@2
+ displayName: 'publish test results'
+ condition: variables['testResultsFiles']
+ inputs:
+ testResultsFiles: '$(testResultsFiles)'
+ testRunTitle: 'Linux - $(group)'
+ mergeTestResults: true
+ - task: Bash@3
+ displayName: 'after_success'
+ inputs:
+ targetType: 'filePath'
+ filePath: ./scripts/travis_after_success.sh
+
+ - job: 'Windows'
+ pool:
+ vmImage: 'vs2017-win2016'
+ variables:
+ python.version: '3.6'
+ strategy:
+ matrix:
+ JS:
+ name: 'javascript'
+ testResultsFiles: 'tests/**/junit.xml'
+ python.version: '3.5'
+ Python:
+ name: 'python'
+ Integrity:
+ name: 'integrity'
+
+ steps:
+ - task: UsePythonVersion@0
+ displayName: 'install python'
+ inputs:
+ versionSpec: '$(python.version)'
+ architecture: 'x64'
+
+ - script: powershell Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem -Name LongPathsEnabled -Value 1
+ displayName: 'set long path'
+ - script: 'python -m pip install -U pip'
+ displayName: 'upgrade pip'
+ - script: 'pip install --upgrade -e ".[test]"'
+ displayName: 'install pip deps'
+ - script: 'jupyter kernelspec list'
+ displayName: 'list kernels'
+ - script: 'jlpm versions'
+ displayName: 'list jlpm versions'
+ - script: 'jlpm config current'
+ displayName: 'list jlpm config'
+ - script: cmd /E:ON /V:ON /C .\\scripts\\appveyor.cmd
+ displayName: 'run tests'
+ - task: PublishTestResults@2
+ displayName: 'publish results'
+ condition: variables['testResultsFiles']
+ inputs:
+ testResultsFiles: '$(testResultsFiles)'
+ testRunTitle: 'Windows - $(name)'
+ mergeTestResults: true
diff --git a/binder/jupyter_notebook_config.py b/binder/jupyter_notebook_config.py
new file mode 100644
index 00000000..0e351cf4
--- /dev/null
+++ b/binder/jupyter_notebook_config.py
@@ -0,0 +1,30 @@
+lab_command = ' '.join([
+ 'jupyter',
+ 'lab',
+ '--dev-mode',
+ '--debug',
+ '--no-browser',
+ '--port={port}',
+ '--NotebookApp.token=""',
+ '--NotebookApp.base_url={base_url}lab-dev',
+ # Disable dns rebinding protection here, since our 'Host' header
+ # is not going to be localhost when coming from hub.mybinder.org
+ '--NotebookApp.allow_remote_access=True'
+])
+
+c.ServerProxy.servers = {
+ 'lab-dev': {
+ 'command': [
+ '/bin/bash', '-c',
+ # Redirect all logs to a log file
+ f'{lab_command} >jupyterlab-dev.log 2>&1'
+ ],
+ 'timeout': 10,
+ 'absolute_url': True
+ }
+}
+
+c.NotebookApp.default_url = '/lab-dev'
+
+import logging
+c.NotebookApp.log_level = logging.DEBUG
diff --git a/binder/postBuild b/binder/postBuild
new file mode 100755
index 00000000..23b2c254
--- /dev/null
+++ b/binder/postBuild
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -euo pipefail
+
+pip install -e .
+
+jlpm
+
+jlpm build
+
+# This seems to be explicitly needed with `pip install -e .`
+jupyter serverextension enable jupyterlab --sys-prefix
diff --git a/binder/requirements.txt b/binder/requirements.txt
new file mode 100644
index 00000000..95ec1217
--- /dev/null
+++ b/binder/requirements.txt
@@ -0,0 +1 @@
+jupyter-server-proxy==1.0beta9
diff --git a/binder/start b/binder/start
new file mode 100755
index 00000000..a83bbb33
--- /dev/null
+++ b/binder/start
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+import sys
+import shutil
+import os
+
+argv = sys.argv[1:] + ['--config', 'binder/jupyter_notebook_config.py']
+print(argv)
+
+os.execv(shutil.which(argv[0]), argv)
\ No newline at end of file
diff --git a/buildutils/package-lock.json b/buildutils/package-lock.json
new file mode 100644
index 00000000..7fe71344
--- /dev/null
+++ b/buildutils/package-lock.json
@@ -0,0 +1,959 @@
+{
+ "name": "@jupyterlab/buildutils",
+ "version": "0.11.1-alpha.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@phosphor/coreutils": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@phosphor/coreutils/-/coreutils-1.3.0.tgz",
+ "integrity": "sha1-YyktOBwBLFqw0Blug87YKbfgSkI="
+ },
+ "@sindresorhus/is": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
+ "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
+ },
+ "@types/events": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
+ "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==",
+ "dev": true
+ },
+ "@types/fs-extra": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.8.tgz",
+ "integrity": "sha512-Z5nu9Pbxj9yNeXIK3UwGlRdJth4cZ5sCq05nI7FaI6B0oz28nxkOtp6Lsz0ZnmLHJGvOJfB/VHxSTbVq/i6ujA==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/glob": {
+ "version": "5.0.36",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz",
+ "integrity": "sha512-KEzSKuP2+3oOjYYjujue6Z3Yqis5HKA1BsIC+jZ1v3lrRNdsqyNNtX0rQf6LSuI4DJJ2z5UV//zBZCcvM0xikg==",
+ "dev": true,
+ "requires": {
+ "@types/events": "*",
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/inquirer": {
+ "version": "0.0.36",
+ "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.36.tgz",
+ "integrity": "sha512-fbqtdP4EOpbWaN+MtGArjo6CSVbPOzNtu8g6wporjg3pdk7cAq8opK4yKfP0gWLoVkbCIT1e/rSVbpYlV8FNrg==",
+ "dev": true,
+ "requires": {
+ "@types/rx": "*",
+ "@types/through": "*"
+ }
+ },
+ "@types/minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "8.0.58",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.58.tgz",
+ "integrity": "sha512-V746iUU7eHNdzQipoACuguDlVhC7IHK8CES1jSkuFt352wwA84BCWPXaGekBd7R5XdNK5ReHONDVKxlL9IreAw==",
+ "dev": true
+ },
+ "@types/rx": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz",
+ "integrity": "sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=",
+ "dev": true,
+ "requires": {
+ "@types/rx-core": "*",
+ "@types/rx-core-binding": "*",
+ "@types/rx-lite": "*",
+ "@types/rx-lite-aggregates": "*",
+ "@types/rx-lite-async": "*",
+ "@types/rx-lite-backpressure": "*",
+ "@types/rx-lite-coincidence": "*",
+ "@types/rx-lite-experimental": "*",
+ "@types/rx-lite-joinpatterns": "*",
+ "@types/rx-lite-testing": "*",
+ "@types/rx-lite-time": "*",
+ "@types/rx-lite-virtualtime": "*"
+ }
+ },
+ "@types/rx-core": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-core/-/rx-core-4.0.3.tgz",
+ "integrity": "sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=",
+ "dev": true
+ },
+ "@types/rx-core-binding": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz",
+ "integrity": "sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==",
+ "dev": true,
+ "requires": {
+ "@types/rx-core": "*"
+ }
+ },
+ "@types/rx-lite": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite/-/rx-lite-4.0.5.tgz",
+ "integrity": "sha512-KZk5XTR1dm/kNgBx8iVpjno6fRYtAUQWBOmj+O8j724+nk097sz4fOoHJNpCkOJUtHUurZlJC7QvSFCZHbkC+w==",
+ "dev": true,
+ "requires": {
+ "@types/rx-core": "*",
+ "@types/rx-core-binding": "*"
+ }
+ },
+ "@types/rx-lite-aggregates": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz",
+ "integrity": "sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-async": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz",
+ "integrity": "sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-backpressure": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz",
+ "integrity": "sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-coincidence": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz",
+ "integrity": "sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-experimental": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz",
+ "integrity": "sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-joinpatterns": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz",
+ "integrity": "sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-testing": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz",
+ "integrity": "sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite-virtualtime": "*"
+ }
+ },
+ "@types/rx-lite-time": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz",
+ "integrity": "sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/rx-lite-virtualtime": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz",
+ "integrity": "sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==",
+ "dev": true,
+ "requires": {
+ "@types/rx-lite": "*"
+ }
+ },
+ "@types/through": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz",
+ "integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "ansi-escapes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+ "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw=="
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "cacheable-request": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz",
+ "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=",
+ "requires": {
+ "clone-response": "1.0.2",
+ "get-stream": "3.0.0",
+ "http-cache-semantics": "3.8.1",
+ "keyv": "3.0.0",
+ "lowercase-keys": "1.0.0",
+ "normalize-url": "2.0.1",
+ "responselike": "1.0.2"
+ },
+ "dependencies": {
+ "lowercase-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+ "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
+ }
+ }
+ },
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chardet": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
+ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
+ },
+ "child_process": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
+ "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o="
+ },
+ "cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "requires": {
+ "restore-cursor": "^2.0.0"
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
+ },
+ "clone-response": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
+ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+ },
+ "decompress-response": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
+ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+ },
+ "duplexer3": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+ },
+ "external-editor": {
+ "version": "2.2.0",
+ "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+ "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
+ "requires": {
+ "chardet": "^0.4.0",
+ "iconv-lite": "^0.4.17",
+ "tmp": "^0.0.33"
+ }
+ },
+ "figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "got": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz",
+ "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==",
+ "requires": {
+ "@sindresorhus/is": "^0.7.0",
+ "cacheable-request": "^2.1.1",
+ "decompress-response": "^3.3.0",
+ "duplexer3": "^0.1.4",
+ "get-stream": "^3.0.0",
+ "into-stream": "^3.1.0",
+ "is-retry-allowed": "^1.1.0",
+ "isurl": "^1.0.0-alpha5",
+ "lowercase-keys": "^1.0.0",
+ "mimic-response": "^1.0.0",
+ "p-cancelable": "^0.4.0",
+ "p-timeout": "^2.0.1",
+ "pify": "^3.0.0",
+ "safe-buffer": "^5.1.1",
+ "timed-out": "^4.0.1",
+ "url-parse-lax": "^3.0.0",
+ "url-to-options": "^1.0.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+ },
+ "has-symbol-support-x": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz",
+ "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw=="
+ },
+ "has-to-string-tag-x": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
+ "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==",
+ "requires": {
+ "has-symbol-support-x": "^1.4.1"
+ }
+ },
+ "http-cache-semantics": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ },
+ "inquirer": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
+ "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
+ "requires": {
+ "ansi-escapes": "^3.0.0",
+ "chalk": "^2.0.0",
+ "cli-cursor": "^2.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^2.0.4",
+ "figures": "^2.0.0",
+ "lodash": "^4.3.0",
+ "mute-stream": "0.0.7",
+ "run-async": "^2.2.0",
+ "rx-lite": "^4.0.8",
+ "rx-lite-aggregates": "^4.0.8",
+ "string-width": "^2.1.0",
+ "strip-ansi": "^4.0.0",
+ "through": "^2.3.6"
+ }
+ },
+ "into-stream": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
+ "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
+ "requires": {
+ "from2": "^2.1.1",
+ "p-is-promise": "^1.1.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+ },
+ "is-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
+ "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA="
+ },
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
+ },
+ "is-retry-allowed": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
+ "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isurl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
+ "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==",
+ "requires": {
+ "has-to-string-tag-x": "^1.2.0",
+ "is-object": "^1.0.1"
+ }
+ },
+ "json-buffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
+ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "keyv": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
+ "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==",
+ "requires": {
+ "json-buffer": "3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
+ },
+ "lowercase-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
+ },
+ "mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mute-stream": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
+ },
+ "normalize-url": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
+ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
+ "requires": {
+ "prepend-http": "^2.0.0",
+ "query-string": "^5.0.1",
+ "sort-keys": "^2.0.0"
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+ },
+ "p-cancelable": {
+ "version": "0.4.1",
+ "resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
+ "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ=="
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+ },
+ "p-is-promise": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
+ "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4="
+ },
+ "p-timeout": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
+ "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
+ "requires": {
+ "p-finally": "^1.0.0"
+ }
+ },
+ "package-json": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/package-json/-/package-json-5.0.0.tgz",
+ "integrity": "sha512-EeHQFFTlEmLrkIQoxbE9w0FuAWHoc1XpthDqnZ/i9keOt701cteyXwAxQFLpVqVjj3feh2TodkihjLaRUtIgLg==",
+ "requires": {
+ "got": "^8.3.1",
+ "registry-auth-token": "^3.3.2",
+ "registry-url": "^3.1.0",
+ "semver": "^5.5.0"
+ }
+ },
+ "path": {
+ "version": "0.12.7",
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
+ "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
+ "requires": {
+ "process": "^0.11.1",
+ "util": "^0.10.3"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+ },
+ "prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+ },
+ "query-string": {
+ "version": "5.1.1",
+ "resolved": "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
+ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
+ "requires": {
+ "decode-uri-component": "^0.2.0",
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "requires": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+ "requires": {
+ "rc": "^1.0.1"
+ }
+ },
+ "responselike": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
+ "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+ "requires": {
+ "lowercase-keys": "^1.0.0"
+ }
+ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "requires": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "run-async": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "rx-lite": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
+ "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ="
+ },
+ "rx-lite-aggregates": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
+ "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
+ "requires": {
+ "rx-lite": "*"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "semver": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
+ "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
+ "sort-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ },
+ "sort-object-keys": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.2.tgz",
+ "integrity": "sha1-06bEjcKsl+a8lDZ2luA/bQnTeVI="
+ },
+ "sort-package-json": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-1.7.1.tgz",
+ "integrity": "sha1-8uX7/+hCDMG7BEhfRQnwXnO0wPI=",
+ "requires": {
+ "sort-object-keys": "^1.1.1"
+ }
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ },
+ "timed-out": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "typescript": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.1.tgz",
+ "integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ=="
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "url-parse-lax": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
+ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+ "requires": {
+ "prepend-http": "^2.0.0"
+ }
+ },
+ "url-to-options": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
+ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k="
+ },
+ "util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ }
+ }
+}
diff --git a/buildutils/package.json b/buildutils/package.json
new file mode 100644
index 00000000..67659fbf
--- /dev/null
+++ b/buildutils/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@jupyterlab/buildutils",
+ "version": "1.0.0-alpha.3",
+ "description": "JupyterLab - Build Utilities",
+ "homepage": "https://github.com/jupyterlab/jupyterlab",
+ "bugs": {
+ "url": "https://github.com/jupyterlab/jupyterlab/issues"
+ },
+ "license": "BSD-3-Clause",
+ "author": "Project Jupyter",
+ "files": [
+ "lib/*.d.ts",
+ "lib/*.js.map",
+ "lib/*.js",
+ "template/package.json",
+ "template/tsconfig.json",
+ "template/src/index.ts"
+ ],
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "bin": {
+ "get-dependency": "./lib/get-dependency.js",
+ "remove-dependency": "./lib/remove-dependency.js",
+ "update-dependency": "./lib/update-dependency.js",
+ "update-dist-tag": "./lib/update-dist-tag.js"
+ },
+ "directories": {
+ "lib": "lib/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jupyterlab/jupyterlab.git"
+ },
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf lib",
+ "prepublishOnly": "npm run build",
+ "watch": "tsc -w --listEmittedFiles"
+ },
+ "dependencies": {
+ "@phosphor/coreutils": "^1.3.0",
+ "@yarnpkg/lockfile": "^1.1.0",
+ "child_process": "~1.0.2",
+ "commander": "~2.18.0",
+ "fs-extra": "~4.0.2",
+ "glob": "~7.1.2",
+ "inquirer": "~3.3.0",
+ "mini-css-extract-plugin": "~0.4.4",
+ "package-json": "~5.0.0",
+ "path": "~0.12.7",
+ "semver": "^5.5.0",
+ "sort-package-json": "~1.7.1",
+ "typescript": "~3.3.1",
+ "webpack": "~4.12.0"
+ },
+ "devDependencies": {
+ "@types/fs-extra": "~4.0.3",
+ "@types/glob": "~5.0.33",
+ "@types/inquirer": "0.0.36",
+ "@types/mini-css-extract-plugin": "^0.2.0",
+ "@types/node": "~8.0.47",
+ "@types/webpack": "^4.4.17",
+ "rimraf": "~2.6.2"
+ },
+ "gitHead": "31f68f6d1717b58c344a5fb4f4baf3b123b7c75c"
+}
diff --git a/buildutils/src/add-sibling.ts b/buildutils/src/add-sibling.ts
new file mode 100755
index 00000000..4f9bef6c
--- /dev/null
+++ b/buildutils/src/add-sibling.ts
@@ -0,0 +1,85 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as utils from './utils';
+
+/**
+ * Add an extension to the source tree of JupyterLab.
+ * It takes as an argument either a path to a directory
+ * on the local filesystem or a URL to a git repository.
+ * In the former case, it copies the directory into the
+ * source tree, in the latter it adds the repository as
+ * a git submodule.
+ *
+ * It also adds the relevant metadata to the build files.
+ */
+
+// Make sure we have required command line arguments.
+if (process.argv.length < 3) {
+ let msg = '** Must supply a target extension';
+ process.stderr.write(msg);
+ process.exit(1);
+}
+
+// Extract the desired git repository and repository name.
+let target = process.argv[2];
+let basePath = path.resolve('.');
+let packageDirName = path.basename(target);
+
+let packagePath = path.resolve(target);
+if (fs.existsSync(packagePath)) {
+ // Copy the package directory contents to the sibling package.
+ let newPackagePath = path.join(basePath, 'packages', packageDirName);
+ fs.copySync(packagePath, newPackagePath);
+ packagePath = newPackagePath;
+} else {
+ // Otherwise treat it as a git reposotory and try to add it.
+ packageDirName = target
+ .split('/')
+ .pop()
+ .split('.')[0];
+ packagePath = path.join(basePath, 'packages', packageDirName);
+ utils.run('git clone ' + target + ' ' + packagePath);
+}
+
+// Remove any existing node_modules in the extension.
+if (fs.existsSync(path.join(packagePath, 'node_modules'))) {
+ fs.removeSync(path.join(packagePath, 'node_modules'));
+}
+
+// Make sure composite is set to true in the new package.
+let packageTsconfigPath = path.join(packagePath, 'tsconfig.json');
+if (fs.existsSync(packageTsconfigPath)) {
+ let packageTsconfig = utils.readJSONFile(packageTsconfigPath);
+ packageTsconfig.compilerOptions.composite = true;
+ utils.writeJSONFile(packageTsconfigPath, packageTsconfig);
+}
+
+// Get the package.json of the extension.
+let pkgJSONPath = path.join(packagePath, 'package.json');
+let data = utils.readJSONFile(pkgJSONPath);
+if (data.private !== true) {
+ data.publishConfig = {};
+ data.publishConfig.access = 'public';
+ utils.writeJSONFile(pkgJSONPath, data);
+}
+
+// Add the extension path to packages/metapackage/tsconfig.json
+let tsconfigPath = path.join(
+ basePath,
+ 'packages',
+ 'metapackage',
+ 'tsconfig.json'
+);
+let tsconfig = utils.readJSONFile(tsconfigPath);
+tsconfig.references.push({
+ path: path.join('..', '..', packageDirName)
+});
+utils.writeJSONFile(tsconfigPath, tsconfig);
+
+// Update the core jupyterlab build dependencies.
+utils.run('jlpm run integrity');
diff --git a/buildutils/src/build.ts b/buildutils/src/build.ts
new file mode 100644
index 00000000..a20d8895
--- /dev/null
+++ b/buildutils/src/build.ts
@@ -0,0 +1,220 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+import * as webpack from 'webpack';
+import * as fs from 'fs-extra';
+import * as glob from 'glob';
+import * as path from 'path';
+import * as utils from './utils';
+
+/**
+ * A namespace for JupyterLab build utilities.
+ */
+export namespace Build {
+ /**
+ * The options used to ensure a root package has the appropriate
+ * assets for its JupyterLab extension packages.
+ */
+ export interface IEnsureOptions {
+ /**
+ * The output directory where the build assets should reside.
+ */
+ output: string;
+
+ /**
+ * The names of the packages to ensure.
+ */
+ packageNames: ReadonlyArray;
+ }
+
+ /**
+ * The JupyterLab extension attributes in a module.
+ */
+ export interface ILabExtension {
+ /**
+ * Indicates whether the extension is a standalone extension.
+ *
+ * #### Notes
+ * If `true`, the `main` export of the package is used. If set to a string
+ * path, the export from that path is loaded as a JupyterLab extension. It
+ * is possible for one package to have both an `extension` and a
+ * `mimeExtension` but they cannot be identical (i.e., the same export
+ * cannot be declared both an `extension` and a `mimeExtension`).
+ */
+ readonly extension?: boolean | string;
+
+ /**
+ * Indicates whether the extension is a MIME renderer extension.
+ *
+ * #### Notes
+ * If `true`, the `main` export of the package is used. If set to a string
+ * path, the export from that path is loaded as a JupyterLab extension. It
+ * is possible for one package to have both an `extension` and a
+ * `mimeExtension` but they cannot be identical (i.e., the same export
+ * cannot be declared both an `extension` and a `mimeExtension`).
+ */
+ readonly mimeExtension?: boolean | string;
+
+ /**
+ * The local schema file path in the extension package.
+ */
+ readonly schemaDir?: string;
+
+ /**
+ * The local theme file path in the extension package.
+ */
+ readonly themePath?: string;
+ }
+
+ /**
+ * A minimal definition of a module's package definition (i.e., package.json).
+ */
+ export interface IModule {
+ /**
+ * The JupyterLab metadata/
+ */
+ jupyterlab?: ILabExtension;
+
+ /**
+ * The main entry point in a module.
+ */
+ main?: string;
+
+ /**
+ * The name of a module.
+ */
+ name: string;
+ }
+
+ /**
+ * Ensures that the assets of plugin packages are populated for a build.
+ *
+ * @ Returns An array of lab extension config data.
+ */
+ export function ensureAssets(
+ options: IEnsureOptions
+ ): webpack.Configuration[] {
+ let { output, packageNames } = options;
+
+ const themeConfig: webpack.Configuration[] = [];
+
+ packageNames.forEach(name => {
+ const packageDataPath = require.resolve(path.join(name, 'package.json'));
+ const packageDir = path.dirname(packageDataPath);
+ const packageData = utils.readJSONFile(packageDataPath);
+ const extension = normalizeExtension(packageData);
+
+ const { schemaDir, themePath } = extension;
+
+ // Handle schemas.
+ if (schemaDir) {
+ const schemas = glob.sync(
+ path.join(path.join(packageDir, schemaDir), '*')
+ );
+ const destination = path.join(output, 'schemas', name);
+
+ // Remove the existing directory if necessary.
+ if (fs.existsSync(destination)) {
+ try {
+ const oldPackagePath = path.join(destination, 'package.json.orig');
+ const oldPackageData = utils.readJSONFile(oldPackagePath);
+ if (oldPackageData.version === packageData.version) {
+ fs.removeSync(destination);
+ }
+ } catch (e) {
+ fs.removeSync(destination);
+ }
+ }
+
+ // Make sure the schema directory exists.
+ fs.mkdirpSync(destination);
+
+ // Copy schemas.
+ schemas.forEach(schema => {
+ const file = path.basename(schema);
+ fs.copySync(schema, path.join(destination, file));
+ });
+
+ // Write the package.json file for future comparison.
+ fs.copySync(
+ path.join(packageDir, 'package.json'),
+ path.join(destination, 'package.json.orig')
+ );
+ }
+
+ if (!themePath) {
+ return;
+ }
+ themeConfig.push({
+ mode: 'production',
+ entry: {
+ index: path.join(name, themePath)
+ },
+ output: {
+ path: path.resolve(path.join(output, 'themes', name)),
+ // we won't use these JS files, only the extracted CSS
+ filename: '[name].js'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader']
+ },
+ {
+ test: /\.svg/,
+ use: [
+ { loader: 'svg-url-loader', options: {} },
+ { loader: 'svgo-loader', options: { plugins: [] } }
+ ]
+ },
+ {
+ test: /\.(png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
+ use: [{ loader: 'url-loader', options: { limit: 10000 } }]
+ }
+ ]
+ },
+ plugins: [
+ new MiniCssExtractPlugin({
+ // Options similar to the same options in webpackOptions.output
+ // both options are optional
+ filename: '[name].css',
+ chunkFilename: '[id].css'
+ })
+ ]
+ });
+ });
+
+ return themeConfig;
+ }
+
+ /**
+ * Returns JupyterLab extension metadata from a module.
+ */
+ export function normalizeExtension(module: IModule): ILabExtension {
+ let { jupyterlab, main, name } = module;
+
+ main = main || 'index.js';
+
+ if (!jupyterlab) {
+ throw new Error(`Module ${name} does not contain JupyterLab metadata.`);
+ }
+
+ let { extension, mimeExtension, schemaDir, themePath } = jupyterlab;
+
+ extension = extension === true ? main : extension;
+ mimeExtension = mimeExtension === true ? main : mimeExtension;
+
+ if (extension && mimeExtension && extension === mimeExtension) {
+ const message = 'extension and mimeExtension cannot be the same export.';
+
+ throw new Error(message);
+ }
+
+ return { extension, mimeExtension, schemaDir, themePath };
+ }
+}
diff --git a/buildutils/src/clean-packages.ts b/buildutils/src/clean-packages.ts
new file mode 100644
index 00000000..4b7e2dec
--- /dev/null
+++ b/buildutils/src/clean-packages.ts
@@ -0,0 +1,60 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as glob from 'glob';
+import { readJSONFile } from './utils';
+
+// Get all of the packages.
+let basePath = path.resolve('.');
+let baseConfig = readJSONFile(path.join(basePath, 'package.json'));
+let packageConfig = baseConfig.workspaces;
+let skipSource = process.argv.indexOf('packages') === -1;
+let skipExamples = process.argv.indexOf('examples') === -1;
+
+// Handle the packages
+for (let i = 0; i < packageConfig.length; i++) {
+ if (skipSource && packageConfig[i] === 'packages/*') {
+ continue;
+ }
+ if (skipExamples && packageConfig[i] === 'examples/*') {
+ continue;
+ }
+ let files = glob.sync(path.join(basePath, packageConfig[i]));
+ for (let j = 0; j < files.length; j++) {
+ try {
+ handlePackage(files[j]);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+}
+
+/**
+ * Handle an individual package on the path - update the dependency.
+ */
+function handlePackage(packagePath: string): void {
+ // Read in the package.json.
+ let packageJSONPath = path.join(packagePath, 'package.json');
+ let data: any;
+ try {
+ data = require(packageJSONPath);
+ } catch (e) {
+ console.log('skipping', packagePath);
+ return;
+ }
+ if (!data.scripts || !data.scripts.clean) {
+ return;
+ }
+ let targets = data.scripts.clean.split('&&');
+ for (let i = 0; i < targets.length; i++) {
+ let target = targets[i].replace('rimraf', '').trim();
+ target = path.join(packagePath, target);
+ if (fs.existsSync(target)) {
+ fs.removeSync(target);
+ }
+ }
+}
diff --git a/buildutils/src/create-package.ts b/buildutils/src/create-package.ts
new file mode 100644
index 00000000..e6418582
--- /dev/null
+++ b/buildutils/src/create-package.ts
@@ -0,0 +1,42 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as inquirer from 'inquirer';
+import * as path from 'path';
+import * as utils from './utils';
+
+let questions = [
+ {
+ type: 'input',
+ name: 'name',
+ message: 'name: '
+ },
+ {
+ type: 'input',
+ name: 'description',
+ message: 'description: '
+ }
+];
+
+inquirer.prompt(questions).then(answers => {
+ let { name, description } = answers;
+ let dest = path.resolve(path.join('.', 'packages', name));
+ if (fs.existsSync(dest)) {
+ console.error('Package already exists: ', name);
+ process.exit(1);
+ }
+ fs.copySync(path.resolve(path.join(__dirname, '..', 'template')), dest);
+ let jsonPath = path.join(dest, 'package.json');
+ let data = utils.readJSONFile(jsonPath);
+ if (name.indexOf('@jupyterlab/') === -1) {
+ name = '@jupyterlab/' + name;
+ }
+ data.name = name;
+ data.description = description;
+ utils.writePackageData(jsonPath, data);
+ // Use npm here so this file can be used outside of JupyterLab.
+ utils.run('npm run integrity');
+});
diff --git a/buildutils/src/create-test-package.ts b/buildutils/src/create-test-package.ts
new file mode 100644
index 00000000..c88465bb
--- /dev/null
+++ b/buildutils/src/create-test-package.ts
@@ -0,0 +1,37 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as utils from './utils';
+
+if (require.main === module) {
+ // Make sure we have required command line arguments.
+ if (process.argv.length !== 3) {
+ let msg = '** Must supply a source package name\n';
+ process.stderr.write(msg);
+ process.exit(1);
+ }
+ let name = process.argv[2];
+ let pkgPath = path.resolve(path.join('.', 'packages', name));
+ if (!fs.existsSync(pkgPath)) {
+ console.error('Package does not exist: ', name);
+ process.exit(1);
+ }
+ let dest = path.resolve(`./tests/test-${name}`);
+ if (fs.existsSync(dest)) {
+ console.error('Test package already exists:', dest);
+ process.exit(1);
+ }
+ fs.copySync(path.resolve(path.join(__dirname, '..', 'test-template')), dest);
+ let jsonPath = path.join(dest, 'package.json');
+ let data = utils.readJSONFile(jsonPath);
+ if (name.indexOf('@jupyterlab/') === -1) {
+ name = '@jupyterlab/test-' + name;
+ }
+ data.name = name;
+ utils.writePackageData(jsonPath, data);
+ fs.ensureDir(path.join(dest, 'src'));
+}
diff --git a/buildutils/src/create-theme.ts b/buildutils/src/create-theme.ts
new file mode 100644
index 00000000..ed6c155c
--- /dev/null
+++ b/buildutils/src/create-theme.ts
@@ -0,0 +1,97 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as inquirer from 'inquirer';
+import * as path from 'path';
+import * as utils from './utils';
+
+let questions = [
+ {
+ type: 'input',
+ name: 'name',
+ message: 'name: '
+ },
+ {
+ type: 'input',
+ name: 'title',
+ message: 'title: '
+ },
+ {
+ type: 'input',
+ name: 'description',
+ message: 'description: '
+ }
+];
+
+const template = `
+import {
+ JupyterFrontEnd, JupyterFrontEndPlugin
+} from '@jupyterlab/application';
+
+import {
+ IThemeManager
+} from '@jupyterlab/apputils';
+
+
+/**
+ * A plugin for the {{title}}
+ */
+const plugin: JupyterFrontEndPlugin = {
+ id: '{{name}}:plugin',
+ requires: [IThemeManager],
+ activate: (app: JupyterFrontEnd, manager: IThemeManager) => {
+ manager.register({
+ name: '{{title}}',
+ isLight: true,
+ load: () => manager.loadCSS('{{name}}/index.css'),
+ unload: () => Promise.resolve(undefined)
+ });
+ },
+ autoStart: true
+};
+
+
+export default plugin;
+`;
+
+inquirer.prompt(questions).then(answers => {
+ let { name, title, description } = answers;
+ let dest = path.resolve(path.join('.', name));
+ if (fs.existsSync(dest)) {
+ console.error('Package already exists: ', name);
+ process.exit(1);
+ }
+ fs.copySync(path.resolve('.', 'packages', 'theme-light-extension'), dest);
+ let jsonPath = path.join(dest, 'package.json');
+ let data = utils.readJSONFile(jsonPath);
+ data.name = name;
+ data.description = description;
+ utils.writePackageData(jsonPath, data);
+
+ // update the urls in urls.css
+ let filePath = path.resolve('.', name, 'style', 'urls.css');
+ let text = fs.readFileSync(filePath, 'utf8');
+ text = text.split('@jupyterlab/theme-light-extension').join(name);
+ fs.writeFileSync(filePath, text, 'utf8');
+
+ // remove lib, node_modules and static.
+ ['lib', 'node_modules', 'static'].forEach(folder => {
+ let folderPath = path.join('.', name, folder);
+ if (fs.existsSync(folderPath)) {
+ fs.remove(folderPath);
+ }
+ });
+
+ let readme = `${name}\n${description}\n`;
+ fs.writeFileSync(path.join('.', name, 'README.md'), readme, 'utf8');
+
+ let src = template.split('{{name}}').join(name);
+ src = src.split('{{title}}').join(title);
+ fs.writeFileSync(path.join('.', name, 'src', 'index.ts'), src, 'utf8');
+
+ // Signify successful complation.
+ console.log(`Created new theme ${name}`);
+});
diff --git a/buildutils/src/dependency-graph.ts b/buildutils/src/dependency-graph.ts
new file mode 100644
index 00000000..5981ac0a
--- /dev/null
+++ b/buildutils/src/dependency-graph.ts
@@ -0,0 +1,294 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as lockfile from '@yarnpkg/lockfile';
+import * as path from 'path';
+import * as utils from './utils';
+import commander from 'commander';
+
+/**
+ * Flatten a nested array one level.
+ */
+function flat(arr: any[]) {
+ return arr.reduce((acc, val) => acc.concat(val), []);
+}
+
+/**
+ * Parse the yarn file at the given path.
+ */
+function readYarn(basePath: string = '.') {
+ let file = fs.readFileSync(path.join(basePath, 'yarn.lock'), 'utf8');
+ let json = lockfile.parse(file);
+
+ if (json.type !== 'success') {
+ throw new Error('Error reading file');
+ }
+
+ return json.object;
+}
+
+/**
+ * Get a node name corresponding to package@versionspec.
+ *
+ * The nodes names are of the form "@".
+ *
+ * Returns undefined if the package is not fund
+ */
+function getNode(yarnData: any, pkgName: string) {
+ if (!(pkgName in yarnData)) {
+ console.error(
+ `Could not find ${pkgName} in yarn.lock file. Ignore if this is a top-level package.`
+ );
+ return undefined;
+ }
+ let name = pkgName[0] + pkgName.slice(1).split('@')[0];
+ let version = yarnData[pkgName].version;
+ let pkgNode = `${name}@${version}`;
+ return pkgNode;
+}
+
+/**
+ * The type for graphs.
+ *
+ * Keys are nodes, values are the list of neighbors for the node.
+ */
+type Graph = { [key: string]: string[] };
+
+/**
+ * Build a dependency graph based on the yarn data.
+ */
+function buildYarnGraph(yarnData: any): Graph | undefined {
+ // 'a': ['b', 'c'] means 'a' depends on 'b' and 'c'
+ const dependsOn: Graph = Object.create(null);
+
+ Object.keys(yarnData).forEach(pkgName => {
+ let pkg = yarnData[pkgName];
+ let pkgNode = getNode(yarnData, pkgName);
+
+ // If multiple version specs resolve to the same actual package version, we
+ // only want to record the dependency once.
+ if (dependsOn[pkgNode] !== undefined) {
+ return;
+ }
+
+ dependsOn[pkgNode] = [];
+ let deps = pkg.dependencies;
+ if (deps) {
+ Object.keys(deps).forEach(depName => {
+ let depNode = getNode(yarnData, `${depName}@${deps[depName]}`);
+ dependsOn[pkgNode].push(depNode);
+ });
+ }
+ });
+ return dependsOn;
+}
+
+/**
+ * Construct a subgraph of all nodes reachable from the given nodes.
+ */
+function subgraph(graph: Graph, nodes: string[]): Graph {
+ let sub = Object.create(null);
+ // Seed the graph
+ let newNodes = nodes;
+ while (newNodes.length > 0) {
+ let old = newNodes;
+ newNodes = [];
+ old.forEach(i => {
+ if (!(i in sub)) {
+ sub[i] = graph[i];
+ newNodes.push(...sub[i]);
+ }
+ });
+ }
+ return sub;
+}
+
+/**
+ * Return the package.json data at the given path
+ */
+function pkgData(packagePath: string) {
+ packagePath = path.join(packagePath, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(packagePath);
+ } catch (e) {
+ console.error('Skipping package ' + packagePath);
+ return {};
+ }
+ return data;
+}
+
+function convertDot(
+ g: { [key: string]: string[] },
+ graphOptions: string,
+ distinguishRoots = false,
+ distinguishLeaves = false
+) {
+ let edges: string[][] = flat(
+ Object.keys(g).map(a => g[a].map(b => [a, b]))
+ ).sort();
+ let nodes = Object.keys(g).sort();
+ // let leaves = Object.keys(g).filter(i => g[i].length === 0);
+ // let roots = Object.keys(g).filter(i => g[i].length === 0);
+ let dot = `
+digraph DEPS {
+ ${graphOptions || ''}
+ ${nodes.map(node => `"${node}";`).join(' ')}
+ ${edges.map(([a, b]) => `"${a}" -> "${b}"`).join('\n ')}
+}
+`;
+ return dot;
+}
+
+interface IMainOptions {
+ dependencies: boolean;
+ devDependencies: boolean;
+ jupyterlab: boolean;
+ lerna: boolean;
+ lernaExclude: string;
+ lernaInclude: string;
+ path: string;
+ phosphor: boolean;
+ topLevel: boolean;
+}
+
+function main({
+ dependencies,
+ devDependencies,
+ jupyterlab,
+ lerna,
+ lernaExclude,
+ lernaInclude,
+ path,
+ phosphor,
+ topLevel
+}: IMainOptions) {
+ let yarnData = readYarn(path);
+ let graph = buildYarnGraph(yarnData);
+
+ let paths: string[] = [path];
+ if (lerna !== false) {
+ paths.push(...utils.getLernaPaths(path).sort());
+ }
+
+ // Get all package data
+ let data: any[] = paths.map(p => pkgData(p));
+
+ // Get top-level package names (these won't be listed in yarn)
+ const topLevelNames: Set = new Set(data.map(d => d.name));
+
+ // Filter lerna packages if a regex was supplied
+ if (lernaInclude) {
+ let re = new RegExp(lernaInclude);
+ data = data.filter(d => d.name && d.name.match(re));
+ }
+ if (lernaExclude) {
+ let re = new RegExp(lernaExclude);
+ data = data.filter(d => d.name && !d.name.match(re));
+ }
+
+ const depKinds: string[] = [];
+ if (devDependencies) {
+ depKinds.push('devDependencies');
+ }
+ if (dependencies) {
+ depKinds.push('dependencies');
+ }
+ /**
+ * All dependency roots *except* other packages in this repo.
+ */
+ const dependencyRoots: string[][] = data.map(d => {
+ let roots: string[] = [];
+ for (let depKind of depKinds) {
+ let deps = d[depKind];
+ if (deps === undefined) {
+ continue;
+ }
+ let nodes = Object.keys(deps)
+ .map(i => {
+ // Do not get a package if it is a top-level package (and this is
+ // not in yarn).
+ if (!topLevelNames.has(i)) {
+ return getNode(yarnData, `${i}@${deps[i]}`);
+ }
+ })
+ .filter(i => i !== undefined);
+ roots.push(...nodes);
+ }
+ return roots;
+ });
+
+ // Find the subgraph
+ let sub = subgraph(graph, flat(dependencyRoots));
+
+ // Add in top-level lerna packages if desired
+ if (topLevel) {
+ data.forEach((d, i) => {
+ sub[`${d.name}@${d.version}`] = dependencyRoots[i];
+ });
+ }
+
+ // Filter out *all* phosphor nodes
+ if (!phosphor) {
+ Object.keys(sub).forEach(v => {
+ sub[v] = sub[v].filter(w => !w.startsWith('@phosphor/'));
+ });
+ Object.keys(sub).forEach(v => {
+ if (v.startsWith('@phosphor/')) {
+ delete sub[v];
+ }
+ });
+ }
+
+ // Filter for any edges going into a jlab package, and then for any
+ // disconnected jlab packages. This preserves jlab packages in the graph that
+ // point to other packages, so we can see where third-party packages come
+ // from.
+ if (!jupyterlab) {
+ Object.keys(sub).forEach(v => {
+ sub[v] = sub[v].filter(w => !w.startsWith('@jupyterlab/'));
+ });
+ Object.keys(sub).forEach(v => {
+ if (v.startsWith('@jupyterlab/') && sub[v].length === 0) {
+ delete sub[v];
+ }
+ });
+ }
+
+ return sub;
+}
+
+commander
+ .description(`Print out the dependency graph in dot graph format.`)
+ .option('--lerna', 'Include dependencies in all lerna packages')
+ .option(
+ '--lerna-include ',
+ 'A regex for package names to include in dependency roots'
+ )
+ .option(
+ '--lerna-exclude ',
+ 'A regex for lerna package names to exclude from dependency roots (can override the include regex)'
+ )
+ .option('--path [path]', 'Path to package or monorepo to investigate', '.')
+ .option(
+ '--no-jupyterlab',
+ 'Do not include dependency connections TO @jupyterlab org packages nor isolated @jupyterlab org packages'
+ )
+ .option('--no-phosphor', 'Do not include @phosphor org packages')
+ .option('--no-devDependencies', 'Do not include dev dependencies')
+ .option('--no-dependencies', 'Do not include normal dependencies')
+ .option('--no-top-level', 'Do not include the top-level packages')
+ .option(
+ '--graph-options ',
+ 'dot graph options (such as "ratio=0.25; concentrate=true;")'
+ )
+ .action(args => {
+ let graph = main(args);
+ console.log(convertDot(graph, args.graphOptions));
+ console.error(`Nodes: ${Object.keys(graph).length}`);
+ });
+
+commander.parse(process.argv);
diff --git a/buildutils/src/ensure-package.ts b/buildutils/src/ensure-package.ts
new file mode 100644
index 00000000..060b48a1
--- /dev/null
+++ b/buildutils/src/ensure-package.ts
@@ -0,0 +1,286 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as glob from 'glob';
+import * as path from 'path';
+import * as ts from 'typescript';
+import { getDependency } from './get-dependency';
+import * as utils from './utils';
+
+/**
+ * Ensure the integrity of a package.
+ *
+ * @param options - The options used to ensure the package.
+ *
+ * @returns A list of changes that were made to ensure the package.
+ */
+export async function ensurePackage(
+ options: IEnsurePackageOptions
+): Promise {
+ let { data, pkgPath } = options;
+ let deps: { [key: string]: string } = data.dependencies || {};
+ let devDeps: { [key: string]: string } = data.devDependencies || {};
+ let seenDeps = options.depCache || {};
+ let missing = options.missing || [];
+ let unused = options.unused || [];
+ let messages: string[] = [];
+ let locals = options.locals || {};
+
+ // Verify dependencies are consistent.
+ let promises = Object.keys(deps).map(async name => {
+ if (!(name in seenDeps)) {
+ seenDeps[name] = await getDependency(name);
+ }
+ if (deps[name] !== seenDeps[name]) {
+ messages.push(`Updated dependency: ${name}@${seenDeps[name]}`);
+ }
+ deps[name] = seenDeps[name];
+ });
+
+ await Promise.all(promises);
+
+ // Verify devDependencies are consistent.
+ promises = Object.keys(devDeps).map(async name => {
+ if (!(name in seenDeps)) {
+ seenDeps[name] = await getDependency(name);
+ }
+ if (devDeps[name] !== seenDeps[name]) {
+ messages.push(`Updated devDependency: ${name}@${seenDeps[name]}`);
+ }
+ devDeps[name] = seenDeps[name];
+ });
+
+ await Promise.all(promises);
+
+ // For TypeScript files, verify imports match dependencies.
+ let filenames: string[] = [];
+ filenames = glob.sync(path.join(pkgPath, 'src/*.ts*'));
+ filenames = filenames.concat(glob.sync(path.join(pkgPath, 'src/**/*.ts*')));
+
+ if (!fs.existsSync(path.join(pkgPath, 'tsconfig.json'))) {
+ if (utils.writePackageData(path.join(pkgPath, 'package.json'), data)) {
+ messages.push('Updated package.json');
+ }
+ return messages;
+ }
+
+ let imports: string[] = [];
+
+ // Extract all of the imports from the TypeScript files.
+ filenames.forEach(fileName => {
+ let sourceFile = ts.createSourceFile(
+ fileName,
+ fs.readFileSync(fileName).toString(),
+ (ts.ScriptTarget as any).ES6,
+ /*setParentNodes */ true
+ );
+ imports = imports.concat(getImports(sourceFile));
+ });
+ let names: string[] = Array.from(new Set(imports)).sort();
+ names = names.map(function(name) {
+ let parts = name.split('/');
+ if (name.indexOf('@') === 0) {
+ return parts[0] + '/' + parts[1];
+ }
+ return parts[0];
+ });
+
+ // Look for imports with no dependencies.
+ promises = names.map(async name => {
+ if (missing.indexOf(name) !== -1) {
+ return;
+ }
+ if (name === '.' || name === '..') {
+ return;
+ }
+ if (!deps[name]) {
+ if (!(name in seenDeps)) {
+ seenDeps[name] = await getDependency(name);
+ }
+ deps[name] = seenDeps[name];
+ messages.push(`Added dependency: ${name}@${seenDeps[name]}`);
+ }
+ });
+
+ await Promise.all(promises);
+
+ // Look for unused packages
+ Object.keys(deps).forEach(name => {
+ if (options.noUnused === false) {
+ return;
+ }
+ if (unused.indexOf(name) !== -1) {
+ return;
+ }
+ const isTest = data.name.indexOf('test') !== -1;
+ if (isTest) {
+ const testLibs = ['jest', 'ts-jest', '@jupyterlab/testutils'];
+ if (testLibs.indexOf(name) !== -1) {
+ return;
+ }
+ }
+ if (names.indexOf(name) === -1) {
+ let version = data.dependencies[name];
+ messages.push(
+ `Unused dependency: ${name}@${version}: remove or add to list of known unused dependencies for this package`
+ );
+ }
+ });
+
+ // Handle typedoc config output.
+ const tdOptionsPath = path.join(pkgPath, 'tdoptions.json');
+ if (fs.existsSync(tdOptionsPath)) {
+ const tdConfigData = utils.readJSONFile(tdOptionsPath);
+ const pkgDirName = pkgPath.split('/').pop();
+ tdConfigData['out'] = `../../docs/api/${pkgDirName}`;
+ utils.writeJSONFile(tdOptionsPath, tdConfigData);
+ }
+
+ // Handle references.
+ let references: { [key: string]: string } = Object.create(null);
+ Object.keys(deps).forEach(name => {
+ if (!(name in locals)) {
+ return;
+ }
+ const target = locals[name];
+ if (!fs.existsSync(path.join(target, 'tsconfig.json'))) {
+ return;
+ }
+ let ref = path.relative(pkgPath, locals[name]);
+ references[name] = ref.split(path.sep).join('/');
+ });
+ if (
+ data.name.indexOf('example-') === -1 &&
+ Object.keys(references).length > 0
+ ) {
+ const tsConfigPath = path.join(pkgPath, 'tsconfig.json');
+ const tsConfigData = utils.readJSONFile(tsConfigPath);
+ tsConfigData.references = [];
+ Object.keys(references).forEach(name => {
+ tsConfigData.references.push({ path: references[name] });
+ });
+ utils.writeJSONFile(tsConfigPath, tsConfigData);
+ }
+
+ // Get a list of all the published files.
+ // This will not catch .js or .d.ts files if they have not been built,
+ // but we primarily use this to check for files that are published as-is,
+ // like styles, assets, and schemas.
+ const published = new Set(
+ data.files
+ ? data.files.reduce((acc: string[], curr: string) => {
+ return acc.concat(glob.sync(path.join(pkgPath, curr)));
+ }, [])
+ : []
+ );
+
+ // Ensure that the `schema` directories match what is in the `package.json`
+ const schemaDir = data.jupyterlab && data.jupyterlab.schemaDir;
+ const schemas = glob.sync(
+ path.join(pkgPath, schemaDir || 'schema', '*.json')
+ );
+ if (schemaDir && !schemas.length) {
+ messages.push(`No schemas found in ${path.join(pkgPath, schemaDir)}.`);
+ } else if (!schemaDir && schemas.length) {
+ messages.push(`Schemas found, but no schema indicated in ${pkgPath}`);
+ }
+ for (let schema of schemas) {
+ if (!published.has(schema)) {
+ messages.push(`Schema ${schema} not published in ${pkgPath}`);
+ }
+ }
+
+ // Ensure that the `style` directories match what is in the `package.json`
+ const styles = glob.sync(path.join(pkgPath, 'style', '**/*.*'));
+ for (let style of styles) {
+ if (!published.has(style)) {
+ messages.push(`Style file ${style} not published in ${pkgPath}`);
+ }
+ }
+
+ // Ensure dependencies and dev dependencies.
+ data.dependencies = deps;
+ data.devDependencies = devDeps;
+
+ if (Object.keys(data.dependencies).length === 0) {
+ delete data.dependencies;
+ }
+ if (Object.keys(data.devDependencies).length === 0) {
+ delete data.devDependencies;
+ }
+
+ if (utils.writePackageData(path.join(pkgPath, 'package.json'), data)) {
+ messages.push('Updated package.json');
+ }
+ return messages;
+}
+
+/**
+ * The options used to ensure a package.
+ */
+export interface IEnsurePackageOptions {
+ /**
+ * The path to the package.
+ */
+ pkgPath: string;
+
+ /**
+ * The package data.
+ */
+ data: any;
+
+ /**
+ * The cache of dependency versions by package.
+ */
+ depCache?: { [key: string]: string };
+
+ /**
+ * A list of dependencies that can be unused.
+ */
+ unused?: string[];
+
+ /**
+ * A list of dependencies that can be missing.
+ */
+ missing?: string[];
+
+ /**
+ * A map of local package names and their relative path.
+ */
+ locals?: { [key: string]: string };
+
+ /**
+ * Whether to enforce that dependencies get used. Default is true.
+ */
+ noUnused?: boolean;
+}
+
+/**
+ * Extract the module imports from a TypeScript source file.
+ *
+ * @param sourceFile - The path to the source file.
+ *
+ * @returns An array of package names.
+ */
+function getImports(sourceFile: ts.SourceFile): string[] {
+ let imports: string[] = [];
+ handleNode(sourceFile);
+
+ function handleNode(node: any): void {
+ switch (node.kind) {
+ case ts.SyntaxKind.ImportDeclaration:
+ imports.push(node.moduleSpecifier.text);
+ break;
+ case ts.SyntaxKind.ImportEqualsDeclaration:
+ imports.push(node.moduleReference.expression.text);
+ break;
+ default:
+ // no-op
+ }
+ ts.forEachChild(node, handleNode);
+ }
+ return imports;
+}
diff --git a/buildutils/src/ensure-repo.ts b/buildutils/src/ensure-repo.ts
new file mode 100644
index 00000000..87889d33
--- /dev/null
+++ b/buildutils/src/ensure-repo.ts
@@ -0,0 +1,272 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/**
+ * Ensure the integrity of the packages in the repo.
+ *
+ * Ensure the core package version dependencies match everywhere.
+ * Ensure imported packages match dependencies.
+ * Ensure a consistent version of all packages.
+ * Manage the metapackage meta package.
+ */
+import * as path from 'path';
+import * as utils from './utils';
+import { ensurePackage, IEnsurePackageOptions } from './ensure-package';
+
+// Data to ignore.
+let MISSING: { [key: string]: string[] } = {
+ '@jupyterlab/buildutils': ['path']
+};
+
+let UNUSED: { [key: string]: string[] } = {
+ '@jupyterlab/apputils': ['@types/react'],
+ '@jupyterlab/application': ['font-awesome'],
+ '@jupyterlab/apputils-extension': ['es6-promise'],
+ '@jupyterlab/services': ['node-fetch', 'ws'],
+ '@jupyterlab/testutils': ['node-fetch', 'identity-obj-proxy'],
+ '@jupyterlab/test-csvviewer': ['csv-spectrum'],
+ '@jupyterlab/vega4-extension': ['vega', 'vega-lite']
+};
+
+let pkgData: { [key: string]: any } = {};
+let pkgPaths: { [key: string]: string } = {};
+let pkgNames: { [key: string]: string } = {};
+let depCache: { [key: string]: string } = {};
+let locals: { [key: string]: string } = {};
+
+/**
+ * Ensure the metapackage package.
+ *
+ * @returns An array of messages for changes.
+ */
+function ensureMetaPackage(): string[] {
+ let basePath = path.resolve('.');
+ let mpPath = path.join(basePath, 'packages', 'metapackage');
+ let mpJson = path.join(mpPath, 'package.json');
+ let mpData = utils.readJSONFile(mpJson);
+ let messages: string[] = [];
+ let seen: { [key: string]: boolean } = {};
+
+ utils.getCorePaths().forEach(pkgPath => {
+ if (path.resolve(pkgPath) === path.resolve(mpPath)) {
+ return;
+ }
+ let name = pkgNames[pkgPath];
+ if (!name) {
+ return;
+ }
+ seen[name] = true;
+ let data = pkgData[name];
+ let valid = true;
+
+ // Ensure it is a dependency.
+ if (!mpData.dependencies[name]) {
+ valid = false;
+ mpData.dependencies[name] = '^' + data.version;
+ }
+
+ if (!valid) {
+ messages.push(`Updated: ${name}`);
+ }
+ });
+
+ // Make sure there are no extra deps.
+ Object.keys(mpData.dependencies).forEach(name => {
+ if (!(name in seen)) {
+ messages.push(`Removing dependency: ${name}`);
+ delete mpData.dependencies[name];
+ }
+ });
+
+ // Write the files.
+ if (messages.length > 0) {
+ utils.writePackageData(mpJson, mpData);
+ }
+
+ // Update the global data.
+ pkgData[mpData.name] = mpData;
+
+ return messages;
+}
+
+/**
+ * Ensure the jupyterlab application package.
+ */
+function ensureJupyterlab(): string[] {
+ // Get the current version of JupyterLab
+ let cmd = 'python setup.py --version';
+ let version = utils.run(cmd, { stdio: 'pipe' }, true);
+
+ let basePath = path.resolve('.');
+ let corePath = path.join(basePath, 'dev_mode', 'package.json');
+ let corePackage = utils.readJSONFile(corePath);
+
+ corePackage.jupyterlab.extensions = {};
+ corePackage.jupyterlab.mimeExtensions = {};
+ corePackage.jupyterlab.version = version;
+ corePackage.jupyterlab.linkedPackages = {};
+ corePackage.dependencies = {};
+
+ let singletonPackages = corePackage.jupyterlab.singletonPackages;
+ let vendorPackages = corePackage.jupyterlab.vendor;
+
+ utils.getCorePaths().forEach(pkgPath => {
+ let dataPath = path.join(pkgPath, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(dataPath);
+ } catch (e) {
+ return;
+ }
+ if (data.private === true || data.name === '@jupyterlab/metapackage') {
+ return;
+ }
+
+ // Make sure it is included as a dependency.
+ corePackage.dependencies[data.name] = '^' + String(data.version);
+ let relativePath = `../packages/${path.basename(pkgPath)}`;
+ corePackage.jupyterlab.linkedPackages[data.name] = relativePath;
+ // Add its dependencies to the core dependencies if they are in the
+ // singleton packages or vendor packages.
+ let deps = data.dependencies || {};
+ for (let dep in deps) {
+ if (singletonPackages.indexOf(dep) !== -1) {
+ corePackage.dependencies[dep] = deps[dep];
+ }
+ if (vendorPackages.indexOf(dep) !== -1) {
+ corePackage.dependencies[dep] = deps[dep];
+ }
+ }
+
+ let jlab = data.jupyterlab;
+ if (!jlab) {
+ return;
+ }
+
+ // Handle extensions.
+ ['extension', 'mimeExtension'].forEach(item => {
+ let ext = jlab[item];
+ if (ext === true) {
+ ext = '';
+ }
+ if (typeof ext !== 'string') {
+ return;
+ }
+ corePackage.jupyterlab[item + 's'][data.name] = ext;
+ });
+ });
+
+ // Write the package.json back to disk.
+ if (utils.writePackageData(corePath, corePackage)) {
+ return ['Updated dev mode'];
+ }
+ return [];
+}
+
+/**
+ * Ensure the repo integrity.
+ */
+export async function ensureIntegrity(): Promise {
+ let messages: { [key: string]: string[] } = {};
+
+ // Pick up all the package versions.
+ let paths = utils.getLernaPaths();
+
+ // These two are not part of the workspaces but should be kept
+ // in sync.
+ paths.push('./jupyterlab/tests/mock_packages/extension');
+ paths.push('./jupyterlab/tests/mock_packages/mimeextension');
+
+ paths.forEach(pkgPath => {
+ // Read in the package.json.
+ let data: any;
+ try {
+ data = utils.readJSONFile(path.join(pkgPath, 'package.json'));
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+
+ pkgData[data.name] = data;
+ pkgPaths[data.name] = pkgPath;
+ pkgNames[pkgPath] = data.name;
+ locals[data.name] = pkgPath;
+ });
+
+ // Update the metapackage.
+ let pkgMessages = ensureMetaPackage();
+ if (pkgMessages.length > 0) {
+ let pkgName = '@jupyterlab/metapackage';
+ if (!messages[pkgName]) {
+ messages[pkgName] = [];
+ }
+ messages[pkgName] = messages[pkgName].concat(pkgMessages);
+ }
+
+ // Validate each package.
+ for (let name in pkgData) {
+ let unused = UNUSED[name] || [];
+ // Allow jest-junit to be unused in the test suite.
+ if (name.indexOf('@jupyterlab/test-') === 0) {
+ unused.push('jest-junit');
+ }
+ let options: IEnsurePackageOptions = {
+ pkgPath: pkgPaths[name],
+ data: pkgData[name],
+ depCache,
+ missing: MISSING[name],
+ unused,
+ locals
+ };
+
+ if (name === '@jupyterlab/metapackage') {
+ options.noUnused = false;
+ }
+ let pkgMessages = await ensurePackage(options);
+ if (pkgMessages.length > 0) {
+ messages[name] = pkgMessages;
+ }
+ }
+
+ // Handle the top level package.
+ let corePath = path.resolve('.', 'package.json');
+ let coreData: any = utils.readJSONFile(corePath);
+ if (utils.writePackageData(corePath, coreData)) {
+ messages['top'] = ['Update package.json'];
+ }
+
+ // Handle the JupyterLab application top package.
+ pkgMessages = ensureJupyterlab();
+ if (pkgMessages.length > 0) {
+ let pkgName = '@jupyterlab/application-top';
+ if (!messages[pkgName]) {
+ messages[pkgName] = [];
+ }
+ messages[pkgName] = messages[pkgName].concat(pkgMessages);
+ }
+
+ // Handle any messages.
+ if (Object.keys(messages).length > 0) {
+ console.log(JSON.stringify(messages, null, 2));
+ if ('--force' in process.argv) {
+ console.log(
+ '\n\nPlease run `jlpm run integrity` locally and commit the changes'
+ );
+ process.exit(1);
+ }
+ utils.run('jlpm install');
+ console.log('\n\nMade integrity changes!');
+ console.log('Please commit the changes by running:');
+ console.log('git commit -a -m "Package integrity updates"');
+ return false;
+ }
+
+ console.log('Repo integrity verified!');
+ return true;
+}
+
+if (require.main === module) {
+ ensureIntegrity();
+}
diff --git a/buildutils/src/get-dependency.ts b/buildutils/src/get-dependency.ts
new file mode 100644
index 00000000..2148db2b
--- /dev/null
+++ b/buildutils/src/get-dependency.ts
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as utils from './utils';
+import packageJson = require('package-json');
+
+let allDeps: string[] = [];
+let allDevDeps: string[] = [];
+
+/**
+ * Get the appropriate dependency for a given package name.
+ *
+ * @param name - The name of the package.
+ *
+ * @returns The dependency version specifier.
+ */
+export async function getDependency(name: string): Promise {
+ let version = '';
+ let versions: { [key: string]: number } = {};
+ allDeps = [];
+ allDevDeps = [];
+
+ utils.getLernaPaths().forEach(pkgRoot => {
+ // Read in the package.json.
+ let packagePath = path.join(pkgRoot, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(packagePath);
+ } catch (e) {
+ return;
+ }
+
+ if (data.name === name) {
+ version = '^' + data.version;
+ return;
+ }
+
+ let deps = data.dependencies || {};
+ let devDeps = data.devDependencies || {};
+ if (deps[name]) {
+ allDeps.push(data.name);
+ if (deps[name] in versions) {
+ versions[deps[name]]++;
+ } else {
+ versions[deps[name]] = 1;
+ }
+ }
+ if (devDeps[name]) {
+ allDevDeps.push(data.name);
+ if (devDeps[name] in versions) {
+ versions[devDeps[name]]++;
+ } else {
+ versions[devDeps[name]] = 1;
+ }
+ }
+ });
+
+ if (version) {
+ return version;
+ }
+
+ if (Object.keys(versions).length > 0) {
+ // Get the most common version.
+ version = Object.keys(versions).reduce((a, b) => {
+ return versions[a] > versions[b] ? a : b;
+ });
+ } else {
+ const releaseData = await packageJson(name);
+ version = '~' + releaseData.version;
+ }
+
+ return Promise.resolve(version);
+}
+
+if (require.main === module) {
+ // Make sure we have required command line arguments.
+ if (process.argv.length < 3) {
+ let msg = '** Must supply a target library name\n';
+ process.stderr.write(msg);
+ process.exit(1);
+ }
+ let name = process.argv[2];
+ getDependency(name).then(version => {
+ console.log('dependency of: ', allDeps);
+ console.log('devDependency of:', allDevDeps);
+ console.log(`\n "${name}": "${version}"`);
+ });
+}
diff --git a/buildutils/src/index.ts b/buildutils/src/index.ts
new file mode 100644
index 00000000..585b99d9
--- /dev/null
+++ b/buildutils/src/index.ts
@@ -0,0 +1,9 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+export * from './build';
+export * from './ensure-package';
+export * from './get-dependency';
+export * from './utils';
diff --git a/buildutils/src/package-json.d.ts b/buildutils/src/package-json.d.ts
new file mode 100644
index 00000000..3651a6b5
--- /dev/null
+++ b/buildutils/src/package-json.d.ts
@@ -0,0 +1,8 @@
+// Type definitions for package-json v5.0.0
+// https://github.com/sindresorhus/package-json
+// Definitions by: Steven Silvester
+
+declare module 'package-json' {
+ function inner(name: string, options?: any): Promise;
+ export = inner;
+}
diff --git a/buildutils/src/patch-release.ts b/buildutils/src/patch-release.ts
new file mode 100755
index 00000000..742432c8
--- /dev/null
+++ b/buildutils/src/patch-release.ts
@@ -0,0 +1,52 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as utils from './utils';
+
+// Make sure we have required command line arguments.
+if (process.argv.length < 3) {
+ let msg = '** Must supply a target package';
+ process.stderr.write(msg);
+ process.exit(1);
+}
+
+// Use npm here so this file can be used outside of JupyterLab.
+utils.run('npm run build:packages');
+
+// Extract the desired package target(s).
+process.argv.slice(2).forEach(target => {
+ let packagePath = path.resolve(path.join('packages', target));
+
+ if (!fs.existsSync(packagePath)) {
+ console.log('Invalid package path', packagePath);
+ process.exit(1);
+ }
+
+ // Perform the patch operations.
+ console.log('Patching', target, '...');
+
+ utils.run('npm version patch', { cwd: packagePath });
+ utils.run('npm publish', { cwd: packagePath });
+
+ // Extract the new package info.
+ let data = utils.readJSONFile(path.join(packagePath, 'package.json'));
+ let name = data.name;
+ let version = data.version;
+
+ // Make the release commit
+ utils.run('git commit -a -m "Release ' + name + '@' + version + '"');
+ utils.run('git tag ' + name + '@' + version);
+});
+
+// Update the static folder.
+utils.run('npm run build:update');
+
+// Integrity update
+utils.run('npm run integrity');
+utils.run('git commit -a -m "Integrity update"');
+
+console.log('\n\nFinished, make sure to push the commit(s) and tag(s).');
diff --git a/buildutils/src/prepublish.ts b/buildutils/src/prepublish.ts
new file mode 100644
index 00000000..24fae6ba
--- /dev/null
+++ b/buildutils/src/prepublish.ts
@@ -0,0 +1,9 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+import * as utils from './utils';
+
+utils.run('npm run clean:slate');
+utils.run('jlpm run build:packages');
+utils.run('jlpm integrity');
diff --git a/buildutils/src/prompt.d.ts b/buildutils/src/prompt.d.ts
new file mode 100644
index 00000000..388a2d99
--- /dev/null
+++ b/buildutils/src/prompt.d.ts
@@ -0,0 +1,12 @@
+// Type definitions for sort-package-json v1.7.1
+// https://github.com/keithamus/sort-package-json
+// Definitions by: Steven Silvester
+
+declare module 'prompt' {
+ export function start(): void;
+
+ export function get(
+ items: string[],
+ callback: (err: Error, result: any) => void
+ ): void;
+}
diff --git a/buildutils/src/read-package-json.d.ts b/buildutils/src/read-package-json.d.ts
new file mode 100644
index 00000000..6f8d125f
--- /dev/null
+++ b/buildutils/src/read-package-json.d.ts
@@ -0,0 +1,8 @@
+// Type definitions for sort-package-json v1.7.1
+// https://github.com/keithamus/sort-package-json
+// Definitions by: Steven Silvester
+
+declare module 'sort-package-json' {
+ function sort(value: any): any;
+ export = sort;
+}
diff --git a/buildutils/src/remove-dependency.ts b/buildutils/src/remove-dependency.ts
new file mode 100755
index 00000000..04f54185
--- /dev/null
+++ b/buildutils/src/remove-dependency.ts
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as utils from './utils';
+
+// Make sure we have required command line arguments.
+if (process.argv.length !== 3) {
+ let msg = '** Must supply a library name\n';
+ process.stderr.write(msg);
+ process.exit(1);
+}
+
+let name = process.argv[2];
+
+// Handle the packages
+utils.getLernaPaths().forEach(pkgPath => {
+ handlePackage(pkgPath);
+});
+handlePackage(path.resolve('.'));
+
+/**
+ * Handle an individual package on the path - update the dependency.
+ */
+function handlePackage(packagePath: string): void {
+ // Read in the package.json.
+ packagePath = path.join(packagePath, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(packagePath);
+ } catch (e) {
+ console.log('Skipping package ' + packagePath);
+ return;
+ }
+
+ // Update dependencies as appropriate.
+ for (let dtype of ['dependencies', 'devDependencies']) {
+ let deps = data[dtype] || {};
+ delete deps[name];
+ }
+
+ // Write the file back to disk.
+ utils.writePackageData(packagePath, data);
+}
+
+// Update the core jupyterlab build dependencies.
+utils.run('jlpm run integrity');
diff --git a/buildutils/src/remove-package.ts b/buildutils/src/remove-package.ts
new file mode 100755
index 00000000..42010d34
--- /dev/null
+++ b/buildutils/src/remove-package.ts
@@ -0,0 +1,39 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/**
+ * Remove an extension from the relevant metadata
+ * files of the JupyterLab source tree so that it
+ * is not included in the build. Intended for testing
+ * adding/removing extensions against development
+ * branches of JupyterLab.
+ */
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as utils from './utils';
+
+// Make sure we have required command line arguments.
+if (process.argv.length < 3) {
+ let msg = '** Must supply a target extension name';
+ process.stderr.write(msg);
+ process.exit(1);
+}
+
+// Get the package name or path.
+let target = process.argv[2];
+let basePath = path.resolve('.');
+
+// Get the package.json of the extension.
+let packagePath = path.join(basePath, 'packages', target, 'package.json');
+if (!fs.existsSync(packagePath)) {
+ packagePath = require.resolve(path.join(target, 'package.json'));
+}
+
+// Remove the package from the local tree.
+fs.removeSync(path.dirname(packagePath));
+
+// Update the core jupyterlab build dependencies.
+utils.run('npm run integrity');
diff --git a/buildutils/src/update-core-mode.ts b/buildutils/src/update-core-mode.ts
new file mode 100644
index 00000000..5e684c0f
--- /dev/null
+++ b/buildutils/src/update-core-mode.ts
@@ -0,0 +1,40 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as utils from './utils';
+
+// Get the dev mode package.json file.
+let data = utils.readJSONFile('./dev_mode/package.json');
+
+// Update the values that need to change and write to staging.
+data['jupyterlab']['buildDir'] = './build';
+data['jupyterlab']['outputDir'] = '..';
+data['jupyterlab']['staticDir'] = '../static';
+data['jupyterlab']['linkedPackages'] = {};
+
+let staging = './jupyterlab/staging';
+utils.writePackageData(path.join(staging, 'package.json'), data);
+
+// Update our staging files.
+[
+ 'index.js',
+ 'webpack.config.js',
+ 'webpack.prod.config.js',
+ 'templates'
+].forEach(name => {
+ fs.copySync(
+ path.join('.', 'dev_mode', name),
+ path.join('.', 'jupyterlab', 'staging', name)
+ );
+});
+
+// Create a new yarn.lock file to ensure it is correct.
+fs.removeSync(path.join(staging, 'yarn.lock'));
+utils.run('jlpm', { cwd: staging });
+
+// Build the core assets.
+utils.run('jlpm run build:prod', { cwd: staging });
diff --git a/buildutils/src/update-dependency.ts b/buildutils/src/update-dependency.ts
new file mode 100755
index 00000000..4d3edabe
--- /dev/null
+++ b/buildutils/src/update-dependency.ts
@@ -0,0 +1,217 @@
+#!/usr/bin/env node
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as utils from './utils';
+import packageJson from 'package-json';
+
+import commander from 'commander';
+import semver from 'semver';
+
+let versionCache = new Map();
+const tags = /^([~^]?)([\w.]*)$/;
+
+async function getVersion(pkg: string, specifier: string) {
+ let key = JSON.stringify([pkg, specifier]);
+ if (versionCache.has(key)) {
+ return versionCache.get(key);
+ }
+ if (semver.validRange(specifier) === null) {
+ // We have a tag, with possibly a range specifier, such as ^latest
+ let match = specifier.match(tags);
+ if (match === null) {
+ throw Error(`Invalid version specifier: ${specifier}`);
+ }
+
+ // Look up the actual version corresponding to the tag
+ let { version } = await packageJson(pkg, { version: match[2] });
+ specifier = match[1] + version;
+ }
+ versionCache.set(key, specifier);
+ return specifier;
+}
+
+/**
+ * A very simple subset comparator
+ *
+ * @returns true if we can determine if range1 is a subset of range2, otherwise false
+ *
+ * #### Notes
+ * This will not be able to determine if range1 is a subset of range2 in many cases.
+ */
+function subset(range1: string, range2: string): boolean {
+ try {
+ const [, r1, version1] = range1.match(tags);
+ const [, r2] = range2.match(tags);
+ return (
+ ['', '~', '^'].indexOf(r1) >= 0 &&
+ r1 === r2 &&
+ semver.valid(version1) &&
+ semver.satisfies(version1, range2)
+ );
+ } catch (e) {
+ return false;
+ }
+}
+
+async function handleDependency(
+ dependencies: { [key: string]: string },
+ dep: string,
+ specifier: string,
+ minimal: boolean
+): Promise<{ updated: boolean; log: string[] }> {
+ let log = [];
+ let updated = false;
+ let newRange = await getVersion(dep, specifier);
+ let oldRange = dependencies[dep];
+ if (minimal && subset(newRange, oldRange)) {
+ log.push(`SKIPPING ${dep} ${oldRange} -> ${newRange}`);
+ } else {
+ log.push(`${dep} ${oldRange} -> ${newRange}`);
+ dependencies[dep] = newRange;
+ updated = true;
+ }
+ return { updated, log };
+}
+
+/**
+ * Handle an individual package on the path - update the dependency.
+ */
+async function handlePackage(
+ name: string | RegExp,
+ specifier: string,
+ packagePath: string,
+ dryRun = false,
+ minimal = false
+) {
+ let fileUpdated = false;
+ let fileLog: string[] = [];
+
+ // Read in the package.json.
+ packagePath = path.join(packagePath, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(packagePath);
+ } catch (e) {
+ console.log('Skipping package ' + packagePath);
+ return;
+ }
+
+ // Update dependencies as appropriate.
+ for (let dtype of ['dependencies', 'devDependencies']) {
+ let deps = data[dtype] || {};
+ if (typeof name === 'string') {
+ let dep = name;
+ if (dep in deps) {
+ let { updated, log } = await handleDependency(
+ deps,
+ dep,
+ specifier,
+ minimal
+ );
+ if (updated) {
+ fileUpdated = true;
+ }
+ fileLog.push(...log);
+ }
+ } else {
+ let keys = Object.keys(deps);
+ keys.sort();
+ for (let dep of keys) {
+ if (dep.match(name)) {
+ let { updated, log } = await handleDependency(
+ deps,
+ dep,
+ specifier,
+ minimal
+ );
+ if (updated) {
+ fileUpdated = true;
+ }
+ fileLog.push(...log);
+ }
+ }
+ }
+ }
+
+ if (fileLog.length > 0) {
+ console.log(packagePath);
+ console.log(fileLog.join('\n'));
+ console.log();
+ }
+
+ // Write the file back to disk.
+ if (!dryRun && fileUpdated) {
+ utils.writePackageData(packagePath, data);
+ }
+}
+
+commander
+ .description('Update dependency versions')
+ .usage('[options] [versionspec], versionspec defaults to ^latest')
+ .option('--dry-run', 'Do not perform actions, just print output')
+ .option('--regex', 'Package is a regular expression')
+ .option('--lerna', 'Update dependencies in all lerna packages')
+ .option('--path ', 'Path to package or monorepo to update')
+ .option('--minimal', 'only update if the change is substantial')
+ .arguments(' [versionspec]')
+ .action(
+ async (name: string | RegExp, version: string = '^latest', args: any) => {
+ let basePath = path.resolve(args.path || '.');
+ let pkg = args.regex ? new RegExp(name) : name;
+
+ if (args.lerna) {
+ let paths = utils.getLernaPaths(basePath).sort();
+
+ // We use a loop instead of Promise.all so that the output is in
+ // alphabetical order.
+ for (let pkgPath of paths) {
+ await handlePackage(pkg, version, pkgPath, args.dryRun, args.minimal);
+ }
+ }
+ await handlePackage(pkg, version, basePath, args.dryRun, args.minimal);
+ }
+ );
+
+commander.on('--help', function() {
+ console.log(`
+Examples
+--------
+
+ Update the package 'webpack' to a specific version range:
+
+ update-dependency webpack ^4.0.0
+
+ Update all packages to the latest version, with a caret.
+ Only update if the update is substantial:
+
+ update-dependency --minimal --regex '.*' ^latest
+
+ Print the log of the above without actually making any changes.
+
+ update-dependency --dry-run --minimal --regex '.*' ^latest
+
+ Update all packages starting with '@jupyterlab/' to the version
+ the 'latest' tag currently points to, with a caret range:
+
+ update-dependency --regex '^@jupyterlab/' ^latest
+
+ Update all packages starting with '@jupyterlab/' in all lerna
+ workspaces and the root package.json to whatever version the 'next'
+ tag for each package currently points to (with a caret tag).
+ Update the version range only if the change is substantial.
+
+ update-dependency --lerna --regex --minimal '^@jupyterlab/' ^next
+`);
+});
+
+commander.parse(process.argv);
+
+// If no arguments supplied
+if (!process.argv.slice(2).length) {
+ commander.outputHelp();
+ process.exit(1);
+}
diff --git a/buildutils/src/update-dist-tag.ts b/buildutils/src/update-dist-tag.ts
new file mode 100755
index 00000000..d2ae5fcf
--- /dev/null
+++ b/buildutils/src/update-dist-tag.ts
@@ -0,0 +1,85 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as utils from './utils';
+import packageJson from 'package-json';
+import commander from 'commander';
+import semver from 'semver';
+
+/**
+ * Handle an individual package on the path - update the dependency.
+ */
+async function handlePackage(packagePath: string): Promise {
+ const cmds: string[] = [];
+
+ // Read in the package.json.
+ packagePath = path.join(packagePath, 'package.json');
+ let data: any;
+ try {
+ data = utils.readJSONFile(packagePath);
+ } catch (e) {
+ console.log('Skipping package ' + packagePath);
+ return cmds;
+ }
+
+ if (data.private) {
+ return cmds;
+ }
+
+ const pkg = data.name;
+
+ let npmData = await packageJson(pkg, { allVersions: true });
+ let versions = Object.keys(npmData.versions).sort(semver.rcompare);
+ let tags = npmData['dist-tags'];
+
+ // Go through the versions. The latest prerelease is 'next', the latest
+ // non-prerelease should be 'stable'.
+ let next = semver.prerelease(versions[0]) ? versions[0] : undefined;
+ let latest = versions.find(i => !semver.prerelease(i));
+
+ if (latest && latest !== tags.latest) {
+ cmds.push(`npm dist-tag add ${pkg}@${latest} latest`);
+ }
+
+ // If next is defined, but not supposed to be, remove it. If next is supposed
+ // to be defined, but is not the same as the current next, change it.
+ if (!next && tags.next) {
+ cmds.push(`npm dist-tag rm ${pkg} next`);
+ } else if (next && next !== tags.next) {
+ cmds.push(`npm dist-tag add ${pkg}@${next} next`);
+ }
+
+ return cmds;
+}
+
+function flatten(a: any[]) {
+ return a.reduce((acc, val) => acc.concat(val), []);
+}
+
+commander
+ .description(
+ `Print out commands to update npm 'latest' and 'next' dist-tags
+so that 'latest' points to the latest stable release and 'next'
+points to the latest prerelease after it.`
+ )
+ .option('--lerna', 'Update dist-tags in all lerna packages')
+ .option('--path [path]', 'Path to package or monorepo to update')
+ .action(async (args: any) => {
+ let basePath = path.resolve(args.path || '.');
+ let cmds: string[][] = [];
+ let paths: string[] = [];
+ if (args.lerna) {
+ paths = utils.getLernaPaths(basePath).sort();
+ cmds = await Promise.all(paths.map(handlePackage));
+ }
+ cmds.push(await handlePackage(basePath));
+ let out = flatten(cmds).join('\n');
+ if (out) {
+ console.log(out);
+ }
+ });
+
+commander.parse(process.argv);
diff --git a/buildutils/src/utils.ts b/buildutils/src/utils.ts
new file mode 100644
index 00000000..a7805fd9
--- /dev/null
+++ b/buildutils/src/utils.ts
@@ -0,0 +1,115 @@
+import path = require('path');
+import glob = require('glob');
+import fs = require('fs-extra');
+import childProcess = require('child_process');
+import sortPackageJson = require('sort-package-json');
+import coreutils = require('@phosphor/coreutils');
+
+/**
+ * Get all of the lerna package paths.
+ */
+export function getLernaPaths(basePath = '.'): string[] {
+ basePath = path.resolve(basePath);
+ let baseConfig = require(path.join(basePath, 'package.json'));
+ let paths: string[] = [];
+ for (let config of baseConfig.workspaces) {
+ paths = paths.concat(glob.sync(path.join(basePath, config)));
+ }
+ return paths.filter(pkgPath => {
+ return fs.existsSync(path.join(pkgPath, 'package.json'));
+ });
+}
+
+/**
+ * Get all of the core package paths.
+ */
+export function getCorePaths(): string[] {
+ let spec = path.resolve(path.join('.', 'packages', '*'));
+ return glob.sync(spec);
+}
+
+/**
+ * Write a package.json if necessary.
+ *
+ * @param data - The package data.
+ *
+ * @oaram pkgJsonPath - The path to the package.json file.
+ *
+ * @returns Whether the file has changed.
+ */
+export function writePackageData(pkgJsonPath: string, data: any): boolean {
+ let text = JSON.stringify(sortPackageJson(data), null, 2) + '\n';
+ let orig = fs
+ .readFileSync(pkgJsonPath, 'utf8')
+ .split('\r\n')
+ .join('\n');
+ if (text !== orig) {
+ fs.writeFileSync(pkgJsonPath, text, 'utf8');
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Read a json file.
+ */
+export function readJSONFile(filePath: string): any {
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
+}
+
+/**
+ * Write a json file.
+ */
+export function writeJSONFile(filePath: string, data: any): boolean {
+ function sortObjByKey(value: any): any {
+ // https://stackoverflow.com/a/35810961
+ return typeof value === 'object'
+ ? Array.isArray(value)
+ ? value.map(sortObjByKey)
+ : Object.keys(value)
+ .sort()
+ .reduce((o: any, key) => {
+ const v = value[key];
+ o[key] = sortObjByKey(v);
+ return o;
+ }, {})
+ : value;
+ }
+ let text = JSON.stringify(data, sortObjByKey(data), 2) + '\n';
+ let orig = {};
+ try {
+ orig = readJSONFile(filePath);
+ } catch (e) {
+ // no-op
+ }
+ if (!coreutils.JSONExt.deepEqual(data, orig)) {
+ fs.writeFileSync(filePath, text, 'utf8');
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Run a command with terminal output.
+ *
+ * @param cmd - The command to run.
+ */
+export function run(
+ cmd: string,
+ options: childProcess.ExecSyncOptions = {},
+ quiet?: boolean
+): string {
+ options = options || {};
+ options['stdio'] = options.stdio || 'inherit';
+ if (!quiet) {
+ console.log('>', cmd);
+ }
+ const value = childProcess.execSync(cmd, options);
+ if (value === null) {
+ return '';
+ }
+ return value
+ .toString()
+ .replace(/(\r\n|\n)$/, '')
+ .trim();
+}
diff --git a/buildutils/src/yarnlock.d.ts b/buildutils/src/yarnlock.d.ts
new file mode 100644
index 00000000..82a1db6e
--- /dev/null
+++ b/buildutils/src/yarnlock.d.ts
@@ -0,0 +1 @@
+declare module '@yarnpkg/lockfile';
diff --git a/buildutils/template/package.json b/buildutils/template/package.json
new file mode 100644
index 00000000..708518c2
--- /dev/null
+++ b/buildutils/template/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@jupyterlab/template",
+ "version": "1.0.0-alpha.3",
+ "description": "JupyterLab - Package Template",
+ "homepage": "https://github.com/jupyterlab/jupyterlab",
+ "bugs": {
+ "url": "https://github.com/jupyterlab/jupyterlab/issues"
+ },
+ "license": "BSD-3-Clause",
+ "author": "Project Jupyter",
+ "files": [
+ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
+ "schema/*.json",
+ "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
+ ],
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "directories": {
+ "lib": "lib/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jupyterlab/jupyterlab.git"
+ },
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf lib",
+ "prepublishOnly": "npm run build",
+ "watch": "tsc -w --listEmittedFiles"
+ },
+ "devDependencies": {
+ "rimraf": "~2.6.2",
+ "typescript": "~3.3.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "gitHead": "31f68f6d1717b58c344a5fb4f4baf3b123b7c75c"
+}
diff --git a/buildutils/template/src/index.ts b/buildutils/template/src/index.ts
new file mode 100644
index 00000000..215ca5d2
--- /dev/null
+++ b/buildutils/template/src/index.ts
@@ -0,0 +1,4 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
diff --git a/buildutils/template/tsconfig.json b/buildutils/template/tsconfig.json
new file mode 100644
index 00000000..a1e4ba13
--- /dev/null
+++ b/buildutils/template/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfigbase",
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "src"
+ },
+ "include": ["src/*"]
+}
diff --git a/buildutils/test-template/karma-cov.conf.js b/buildutils/test-template/karma-cov.conf.js
new file mode 100644
index 00000000..ed944045
--- /dev/null
+++ b/buildutils/test-template/karma-cov.conf.js
@@ -0,0 +1 @@
+module.exports = require('../karma-cov.conf');
diff --git a/buildutils/test-template/karma.conf.js b/buildutils/test-template/karma.conf.js
new file mode 100644
index 00000000..d1a08bcc
--- /dev/null
+++ b/buildutils/test-template/karma.conf.js
@@ -0,0 +1 @@
+module.exports = require('../karma.conf');
diff --git a/buildutils/test-template/package.json b/buildutils/test-template/package.json
new file mode 100644
index 00000000..cec3e532
--- /dev/null
+++ b/buildutils/test-template/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@jupyterlab/template-for-tests",
+ "version": "1.0.0-alpha.3",
+ "private": true,
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf build && rimraf coverage",
+ "coverage": "python run-test.py --browsers ChromeHeadless karma-cov.conf.js",
+ "test": "jlpm run test:firefox",
+ "test:chrome": "python run-test.py --browsers=Chrome karma.conf.js",
+ "test:debug": "python run-test.py --browsers=Chrome --singleRun=false --debug=true --browserNoActivityTimeout=10000000 karma.conf.js",
+ "test:firefox": "python run-test.py --browsers=Firefox karma.conf.js",
+ "test:ie": "python run-test.py --browsers=IE karma.conf.js",
+ "watch": "python run-test.py --singleRun=false",
+ "watch:src": "tsc -w --listEmittedFiles"
+ },
+ "devDependencies": {
+ "karma": "~2.0.4",
+ "rimraf": "~2.6.2",
+ "typescript": "~3.3.1"
+ }
+}
diff --git a/buildutils/test-template/run-test.py b/buildutils/test-template/run-test.py
new file mode 100644
index 00000000..173af144
--- /dev/null
+++ b/buildutils/test-template/run-test.py
@@ -0,0 +1,10 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import os
+from jupyterlab.tests.test_app import run_karma
+
+HERE = os.path.realpath(os.path.dirname(__file__))
+
+if __name__ == '__main__':
+ run_karma(HERE)
diff --git a/buildutils/test-template/src/mypackage.spec.ts b/buildutils/test-template/src/mypackage.spec.ts
new file mode 100644
index 00000000..fbc2d96c
--- /dev/null
+++ b/buildutils/test-template/src/mypackage.spec.ts
@@ -0,0 +1,2 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
diff --git a/buildutils/test-template/tsconfig.json b/buildutils/test-template/tsconfig.json
new file mode 100644
index 00000000..f5c9b139
--- /dev/null
+++ b/buildutils/test-template/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfigbase",
+ "compilerOptions": {
+ "outDir": "build",
+ "rootDir": "src"
+ },
+ "include": ["src/*"]
+}
diff --git a/buildutils/tsconfig.json b/buildutils/tsconfig.json
new file mode 100644
index 00000000..c68707ed
--- /dev/null
+++ b/buildutils/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../tsconfigbase",
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "src"
+ },
+ "include": ["src/*"]
+}
diff --git a/clean.py b/clean.py
new file mode 100644
index 00000000..cf5b4eeb
--- /dev/null
+++ b/clean.py
@@ -0,0 +1,23 @@
+import os
+import subprocess
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+# Workaround for https://github.com/git-for-windows/git/issues/607
+if os.name == 'nt':
+ for (root, dnames, files) in os.walk(here):
+ if 'node_modules' in dnames:
+ subprocess.check_call(['rmdir', '/s', '/q', 'node_modules'],
+ cwd=root, shell=True)
+ dnames.remove('node_modules')
+
+
+git_clean_exclude = [
+ '-e',
+ '/.vscode',
+]
+git_clean_command = ['git', 'clean', '-dfx'] + git_clean_exclude
+subprocess.check_call(git_clean_command, cwd=here)
+
+subprocess.call('python -m pip uninstall -y jupyterlab'.split(), cwd=here)
diff --git a/design/about.md b/design/about.md
new file mode 100644
index 00000000..407938e2
--- /dev/null
+++ b/design/about.md
@@ -0,0 +1,101 @@
+# JupyterLab About Plugin
+
+This document describes the design of the "About JupyterLab" plugin. This document will illustrate research done using Personas from users of the Jupyter Notebook, as well as describe all of the tasks and operations that the user should be able to do with this design, as well as how our design will look visually.
+
+## Personas
+
+### Ricardo Godfrey
+
+- Male 25, Full Stack Engineer
+
+- Uses single notebook to explain a very specific topic or teach people something
+
+ - Done with charts, graphs, tables (Matplotlib and Pandas), live code, images integrated in text
+
+### Krista
+
+- Female 40, Engineering Professor
+
+- Uses the Jupyter notebooks to teach her entire college courses
+
+ - Done with Matplotlib graphs, & embeds in notebook, live code
+
+## Other Solutions
+
+- Making the whole experience more visual, using GIFs to show firsthand what you can do with Jupyter Lab
+
+ - Show that you can drag and drop tabs to rearrange, etc.
+
+- Outline the differences between Notebook and Lab to help users who are familiar with the former better understand the latter
+
+ - Side-by-side GIFs showing some differences
+
+## User Tasks
+
+Users should be able to:
+
+- Learn how to get started using JupyterLab ("Scroll through tutorial")
+
+ - _Mouse: Scrolling_
+
+ - _UI: Clicking side pagination_
+
+ - _UI: Clicking "continue" arrow_
+
+ - _Keyboard: Arrow keys_
+
+ - _Keyboard: Numbers_
+
+- Easily close the page if they don’t need it
+
+ - _Mouse: Click to close_
+
+- Show users (with visual animation) how to perform JupyterLab actions
+
+ - i.e. Click and drag window tabs to move and split windows
+
+- Not be intimidated by too many words ("About" should not feel like a commitment)
+
+- Have an **overview** of tutorial contents on "Page 1" before scrolling
+
+- Read/Take action at the end (similar to launcher)
+
+ - _UI: Clicking Action_
+
+- Have a short description of JupyterLab capabilities
+
+## Visual Design
+
+### Layout
+
+The new About plugin will feature a cleaner layout, with different "slides" that guide the user through the different features and actions of JupyterLab. These slides can be scrolled through like any website, but each section is automatically formatted to fit the viewport (we will be creating a framework similar to—if not using parts of—[fullpage.js](http://alvarotrigo.com/fullPage/)). We will be implementing the use of images and animations per slide to help users understand each new “feature” of JupyterLab better, and to understand how to use it.
+
+The title slide will contain the JupyterLab logo near the top, and welcome text underneath to make the user feel at home, and to explain the current release of their JupyterLab version. After this, we’re hoping to include an overview of the "features" (i.e. Main Area, Command Palette, File Browser, etc.), by giving each feature an icon and description. This can be clicked and will ultimately be linked to that feature’s slide.
+
+After the title slide, there will be a few more slides for each feature. Each slide will have a mini icon and title for the feature, an animated image (_.gif or _.gifv file) showing the user process of the feature, and a more in-depth description of the "feature".
+
+The final slide will give the user the ability to start working with a Notebook, Console, and/or Terminal—very similar to the Launcher plugin—acting as a Call-to-Action for the user to start using JupyterLab.
+
+Each slide will have a downwards arrow (" ╲╱ ") at the bottom of the viewport to encourage the user to continue learning about the features of JupyterLab, as well as a fixed slide pagination on the right side of the window, to help the user understand which slide they’re on and what slides are available to them.
+
+### Typography
+
+The About plugin will use similar Typography (Helvetica Neue, sans-serif typeface) to keep the design consistent with the rest of JupyterLab (and Jupyter as a whole). The only change to this would be the official Jupyter font in the logo (Myriad Pro typeface), but this is in an image, and will not require outside font assets.
+
+We will continue brainstorming to determine if this choice is the most engaging for the About plugin.
+
+### Colors
+
+We are still discussing which choices of color would be the most effective at engaging users to learn more about the project of JupyterLab and the features of it. The About plugin will most likely feature a simple, light-themed color palette, to communicate ideas easily and pair with the theme of JupyterLab.
+
+We will continue brainstorming and developing design renditions to determine if this choice is the most engaging for the About plugin.
+
+### Motion
+
+The About Plugin will feature an interactive guide for the user to scroll through. Motion will be reflected with the scrolling, slide transitions, which helps the user understand where the About content is going, and where to get more About content.
+
+Motion will also be implemented in our animations for each feature, using a moving image format (_.gif or _.gifv file) of a UX demonstration of a real screen, to show users not only where and what each feature looks like, but also how to actually use each feature in real time.
+
+---
+
+Please let our team know if you have any questions, suggestions, or comments about the design of the About plugin. Thank you!
diff --git a/design/help.md b/design/help.md
new file mode 100644
index 00000000..23433d01
--- /dev/null
+++ b/design/help.md
@@ -0,0 +1,42 @@
+# Design of the help plugin
+
+This document describes the design of the Help plugin. This document illustrates how our research on personas and other solutions translates into the design decisions made to improve the Help plugin.
+
+# Personas
+
+### Jason Shu
+
+Jason Shu is a student majoring in computer programming. He has very basic knowledge of computer science and no knowledge of what Jupyter is.
+
+**Goal:** Learn how to create new notebooks, open a new terminal, and change kernels
+
+# Research of other solutions:
+
+- Improve discoverability of “Help” tab by including it in the main menu
+- Include a “Quickstart Guide” for how to use JupyterLab
+- Include a “FAQ” page
+
+# User tasks
+
+Users should be able to:
+
+- See a top-level Help menu immediately
+- Open the following in the R side panel (menu, command)
+ - Things listed in the current notebook help menu
+- Open the main About/Tour (Help menu)
+- Open a JupyterLab specific FAQ in the R side panel (menu, command)
+- See FAQ questions collapsed by default and expand to view answers (mouse)
+- Open the About page (menu, command)
+- Easily get to the issue page of jupyter/jupyterlab in a new browser tab (menu)
+- View the keyboard shortcuts for all commands related to a plugin (command palette)
+- Collapse the R side panel to hide all of the help (mouse)
+
+# Visual design
+
+- Help menu should follow design guidelines for all menus.
+- Help menu should have different sections for different types of content.
+- When a help item is opened, it should appear in the R side panel in an IFRAME.
+
+# Design questions
+
+- Should each help topic open in a single global R side panel help tab (current behavior), or should each open a new help tab that can be closed completely?
diff --git a/design/imagewidget.md b/design/imagewidget.md
new file mode 100644
index 00000000..10b7587c
--- /dev/null
+++ b/design/imagewidget.md
@@ -0,0 +1,25 @@
+# Design of the imageviewer plugin
+
+This document describes the design of the imageviewer plugin. This document illustrates how our research on personas and other solutions translates into the design decisions made to improve the imageviewer plugin.
+
+# Personas
+
+### Jackie Lair
+
+Jackie Lair is an experienced data scientist who is familiar with JupyterLab and uses it regularly. She uses the imageviewer to compare MRI scans alongside her code and write programs that outputs the information found in the MRI scans.
+
+**Goal:** To be able to manipulate images in JupyterLab to view alongside other tabs and support data analysis.
+
+# Users tasks
+
+Users should be able to:
+
+- Open an image from the file browser (file browser)
+- Drag and drop an image file into JupyterLab (mouse)
+- Resize an image (command palette, toolbar, context menu)
+- Image fits to width in tab
+- Reset the size of the image to original (command palette, toolbar, context menu)
+
+# Visual design
+
+- Imagewidget menu should follow design guidelines for all menus.
diff --git a/design/launcher.md b/design/launcher.md
new file mode 100644
index 00000000..47bc59c5
--- /dev/null
+++ b/design/launcher.md
@@ -0,0 +1,35 @@
+# Design of the launcher plugin
+
+This document describes the design of the launcher plugin. This document illustrates how our research on personas and other solutions translates into the design decisions made to improve the launcher plugin.
+
+# Personas
+
+### William Shar
+
+William Shar is a high school student who has learned to code in his own time through taking codeschool classes. He was told about JupyterLab from his older brother and decided to try it out.
+
+**Goal:** Utilize launcher as the main way to open new documents
+
+# User tasks
+
+Users should be able to:
+
+- See the Launcher as the only open tab when JupyterLab starts
+- Immediately open a document/activity of different types (click on icon)
+ - Notebook
+ - Terminal
+ - Text editor
+ - Console
+- Open the JupyterLab Tour (button or link)
+- Close the Launcher (x button)
+- Reopen the Launcher if closed and have it be the active tab/panel (Jupyter menu, command)
+- See the Launcher when they have multiple tabs open (it should be named “Launcher”)
+
+# Visual design
+
+- Work with Matt on icon design for 4 activity types.
+- “Take a tour” text underneath icons in launcher
+
+# Design questions
+
+- Should the launcher show only when there are not tabs/panels open?
diff --git a/design/notebook.md b/design/notebook.md
new file mode 100644
index 00000000..6c6edcba
--- /dev/null
+++ b/design/notebook.md
@@ -0,0 +1,83 @@
+# Design of the notebook plugin
+
+This document describes the design of the notebook plugin. This document illustrates how our research on personas and other solutions translates into the design decisions made to improve the notebook plugin.
+
+# Personas
+
+### Jane Gomez
+
+Jane Gomez has never used the Jupyter Notebook before. She has some basic understanding of python, but only knows how to write simple functions. She’s using the notebook for the first time in JupyterLab and wants to quickly learn how to use it.
+
+**Goal:** Jane can quickly learn how to use the notebook through an intuitive menu bar and/or a tour.
+
+### Shane Alborou
+
+Shane Alborou has used Jupyter Notebook in a classroom setting in the past. He wants to use the multiscreen JupyterLab to write his code and submit it to his professor.
+
+**Goal:** The student can use all of the same functions that the Jupyter notebook has to turn in his code to his professor.
+
+# User tasks
+
+Users should be able to:
+
+- See the name of the notebook on the top menu bar
+- Change the name of the notebook (file browser menu, top menu)
+- Get a user interface tour (top menu)
+- Use notebook cell operations
+ - Clear output(s) (command palette)
+ - Convert to code (command palette, shortcut, toolbar)
+ - Convert to markdown (command palette, shortcut, toolbar)
+ - Convert to raw (command palette, shortcut, toolbar)
+ - Copy Cells(s) (command palette, shortcut, toolbar)
+ - Cut Cells(s) (command palette, shortcut, toolbar)
+ - Delete Cell(s) (command palette, shortcut, toolbar)
+ - Extend selection above (command palette, shortcut)
+ - Extend selection below (command palette, shortcut)
+ - Insert Cell Above (command palette, shortcut)
+ - Insert Cell Below (command palette, shortcut)
+ - Merge Selected Cell(s) (command palette, shortcut)
+ - Paste Cell(s) (command palette, shortcut, toolbar)
+ - Redo Cell Operation (command palette, shortcut)
+ - Run Cell(s) (command palette, shortcut)
+ - Run Cell(s) and Advance (command palette, toolbar, shortcut)
+ - Run Cell(s) and Insert (command palette, shortcut)
+ - Select Cell Above (command palette, shortcut)
+ - Select Cell Below (command palette, shortcut)
+ - Split Cell (command palette, shortcut)
+ - Toggle Line Numbers (command palette, shortcut)
+ - Undo Cell Operation (command palette, shortcut)
+ - Redo Cell Operation (command palette)
+ - Select multiple cells at once (mouse, shortcut)
+ - Move cell(s) up and down (mouse)
+- Use notebook operations
+ - Clear All Outputs (command palette)
+ - Interrupt Kernel (command palette, shortcut, toolbar)
+ - Restart Kernel & Clear Outputs (command palette)
+ - Restart Kernel & Run All (command palette)
+ - Run All Cells (command palette)
+ - Switch Kernel (command palette, toolbar)
+ - To Command Mode (command palette, shortcut, mouse)
+ - To Edit Mode (command palette, shortcut, mouse)
+ - Toggle All Line Numbers (command palette)
+- See when the code is running in top right corner circle (toolbar)
+- See when the file has most recently been saved (toolbar)
+- See a reaction when an icon has been click (toolbar)
+- Include a top menu above the toolbar
+- Make a Copy (top menu)
+- Print Preview (top menu)
+- Download file as (top menu)
+- Find and Replace (top menu)
+- Be able to open a keyboard shortcut specific for the notebook (command palette)
+
+# Visual design
+
+- Add sections to the icon tools
+- Replace icons with google’s material icons
+- Make the top right kernel label a dropdown
+- Include an indicator that the file is being saved on the toolbar
+- Additional commands will follow the style guidelines of the current command palette
+- When the mouse hovers versus clicks on an icon, there are separate indicators (colors) for each
+
+# Design questions
+
+- Should there be a keyboard shortcut specific for the notebook, continue using the command palette as the lists of shortcuts, or redesign the command palette functionality?
diff --git a/design/real_time_collab.md b/design/real_time_collab.md
new file mode 100644
index 00000000..91b36ecc
--- /dev/null
+++ b/design/real_time_collab.md
@@ -0,0 +1,78 @@
+# Read Time Collaboration Design
+
+This purpose of this document is to describe the design of the real-time collaboration (RTC)
+features we are building for JuptyerLab. The focus here is primarily on the user experience
+and usage cases, rather than on the implementation details.
+
+## Personas
+
+### Jon the Academic Data Scientist
+
+Jon is an academic researcher and data scientist. He has been using the Jupyter Notebook daily
+for many years and is an advanced user, teacher and book author. He uses the Notebook
+exclusively on his local system and stores his notebooks on GitHub, in including blog posts
+and full length books.
+
+Jon regularly works with students, postdocs and other faculty at his own and other universities
+on a wide range of projects. These projects involve the collaborative creation of notebooks,
+markdown files, documentation (Sphinx) and source code files (Python, C, C++). His collaborators
+also run the notebook their local systems.
+
+Jon and his collaborators need the ability to do ad-hoc collaboration on particular documents. They
+are already version controlling their code, documentation and notebooks, but at times, Git/GitHub
+don't provide fast enough collaborative interactions. They want to continue to work on their local
+systems, with their own local notebook servers, but need to be able to initiate the RTC of
+one or more files or a diretory of files with a group of individuals.
+
+During the RTC sessions, the participants are focused on the following aspects of their
+work:
+
+- Collaborative editing of content.
+- Side channel live discussions (Bluejeans, appear.in, phonecalls).
+- Discussion and exploration of results.
+
+All individuals in the collaboration already have access to the files (Git/GitHub).
+Because of this, during the RTC they need to be able to collaborative edit the same exact
+version, which will later be commmited to the repository. Because of this, only one participant
+would have a live filesystem representation of the edited file. At the end of the ad-hoc RTC session,
+the inviduals would go back to syncing their changes through GitHub.
+
+## The MTTU Scientific Collaboration
+
+The MTTU Scientific Collaboration is a planet-wide scientific collaboration in the field
+of Physics. They are building a large experiment that will go live in a few years and have a
+ten year observing cycle. Once operational, they will collect petabytes of data that will be
+accessed by thousands of scientific users.
+
+The collaboration has a software stack based on Python and C++ and is embracing modern,
+software engieering practices (version control with Git/GitHub, Travis, Slack, etc.). They version
+control everything and run an extensive test suite on each commit. The collaboration is exploring the possibility of using JupyterHub to provide a unified user-experience for their users to access data
+and their analysis software.
+
+The collaboration would run JupyterLab with JuptyerHub, and build custom JupyterLab extensions that
+have custom front-end UIs that talk to their various backend services. They want to provide RTC
+capabilities for all of their services to enable the users and scientists to work with notebooks,
+text files and their custom backend services in a collaborative manner. Most of their RTC session will
+take place on shared single-user notebook servers managed by JupyterHub. This style of collaboration
+will also extend to Jupyter Notebook, Sphinx based documentation, Python/C++ source code files.
+In their JupyterHub deployment these files are managed on a massive scale shared file system that is
+deployed on their compute infrastructure. Again, all files are version controlled at the end of the day.
+
+## Arya the Data Science student
+
+Emma is a Junior Computer Science Major taking an introductory course in Data Science with Python.
+Her professor uses the Jupyter Notebook for all course materials and homework. The notebook is
+deployed on a single server using JupyterHub and nbgrader is used for all course material.
+This is the first time Emma has used the Jupyter Notebook and she is enjoying the experience.
+Git/GitHub is not used in the course; only a few of her classmates have ever used it, and mostly
+in the context of internships.
+
+Towards the end of the quarter, her professor assigns group projects. The students quickly realize
+that they will need the ability to easily share content. They appoint a single group member to
+hold the master copies of all their files for the project and then create a persistent share RTC
+session that enables all project members to work with the files at the same time. This was all
+possible without the instructors involvement (other than installing the RTC JupyterLab extension).
+
+As the students work collaboratively, the master copies of the files are updated and at the end of the
+project, those master copies are turned in. During the project, students both run code in their
+own private kernels and a shared kernel.
diff --git a/design/terminal.md b/design/terminal.md
new file mode 100644
index 00000000..5c97c839
--- /dev/null
+++ b/design/terminal.md
@@ -0,0 +1,42 @@
+# Design of the terminal plugin
+
+This document describes the design of the terminal plugin. Any significant change to the terminal plugin should also involve changes here.
+
+# Personas
+
+### Janet Tobin
+
+An experienced data scientist with a graduate degree in a technical field and has been coding for more than 5 years in multiple languages. She is 35 years old, has a solid income, and lives in an urban setting. In the past she has used the Jupyter Notebook alongside a text editor (Sublime Text) and the Terminal app on a Mac. She loves to code and feels at home in a terminal.
+
+**Goal:** Replace the usage of Mac's Terminal app, in particular when running Jupyter on a remote system.
+
+Some things they would do in the terminal include
+
+- Run command line git.
+- Small amounts of general software engineering to support their data science, such as running test suites, moving files around at the command line.
+- Run vim.
+- Run command line IPython.
+
+# User tasks
+
+Users should be able to:
+
+- Open a new terminal (command palette, top menu)
+- Close a terminal (UI)
+- Change the font size (bigger/smaller) (command palette, top menu)
+- Close all terminals (command palette, top menu)
+- See the name of the terminal in the dock area tab (UI)
+- Copy text from the terminal (UI+keyboard)
+- Paste text into the terminal (UI+keyboard)
+- Reconnect all terminals after a network outage (command palette)
+- Toggle between black/white and white/black (command palette, menu)
+- Copy and paste commands into the terminal (shortcut, mouse)
+- Go to different directories (commands inside terminal)
+- Access or use IPython from the terminal (commands inside terminal)
+
+# Visual Design
+
+- Terminal menu should follow design guidelines for all menus.
+- Terminal theme can change from black/white to white/black
+
+Team IO: @faricacarroll @spoorthyv @charnpreetsingh185 @katiewhite360
diff --git a/dev_mode/index.js b/dev_mode/index.js
new file mode 100644
index 00000000..cf70ca9e
--- /dev/null
+++ b/dev_mode/index.js
@@ -0,0 +1,199 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+require('es6-promise/auto'); // polyfill Promise on IE
+
+import {
+ PageConfig, URLExt
+} from '@jupyterlab/coreutils';
+
+__webpack_public_path__ = PageConfig.getOption('bundleUrl');
+
+// This needs to come after __webpack_public_path__ is set.
+require('font-awesome/css/font-awesome.min.css');
+
+/**
+ * The main entry point for the application.
+ */
+function main() {
+ var JupyterLab = require('@jupyterlab/application').JupyterLab;
+
+ // Get the disabled extensions.
+ var disabled = { patterns: [], matches: [] };
+ var disabledExtensions = [];
+ try {
+ var tempDisabled = PageConfig.getOption('disabledExtensions');
+ if (tempDisabled) {
+ disabledExtensions = JSON.parse(tempDisabled).map(function(pattern) {
+ disabled.patterns.push(pattern);
+ return { raw: pattern, rule: new RegExp(pattern) };
+ });
+ }
+ } catch (error) {
+ console.warn('Unable to parse disabled extensions.', error);
+ }
+
+ // Get the deferred extensions.
+ var deferred = { patterns: [], matches: [] };
+ var deferredExtensions = [];
+ var ignorePlugins = [];
+ try {
+ var tempDeferred = PageConfig.getOption('deferredExtensions');
+ if (tempDeferred) {
+ deferredExtensions = JSON.parse(tempDeferred).map(function(pattern) {
+ deferred.patterns.push(pattern);
+ return { raw: pattern, rule: new RegExp(pattern) };
+ });
+ }
+ } catch (error) {
+ console.warn('Unable to parse deferred extensions.', error);
+ }
+
+ function isDeferred(value) {
+ return deferredExtensions.some(function(pattern) {
+ return pattern.raw === value || pattern.rule.test(value);
+ });
+ }
+
+ function isDisabled(value) {
+ return disabledExtensions.some(function(pattern) {
+ return pattern.raw === value || pattern.rule.test(value);
+ });
+ }
+
+ var register = [];
+
+ // Handle the registered mime extensions.
+ var mimeExtensions = [];
+ {{#each jupyterlab_mime_extensions}}
+ try {
+ if (isDeferred('{{key}}')) {
+ deferred.matches.push('{{key}}');
+ ignorePlugins.push('{{key}}');
+ }
+ if (isDisabled('{{@key}}')) {
+ disabled.matches.push('{{@key}}');
+ } else {
+ var module = require('{{@key}}/{{this}}');
+ var extension = module.default;
+
+ // Handle CommonJS exports.
+ if (!module.hasOwnProperty('__esModule')) {
+ extension = module;
+ }
+
+ if (Array.isArray(extension)) {
+ extension.forEach(function(plugin) {
+ if (isDeferred(plugin.id)) {
+ deferred.matches.push(plugin.id);
+ ignorePlugins.push(plugin.id);
+ }
+ if (isDisabled(plugin.id)) {
+ disabled.matches.push(plugin.id);
+ return;
+ }
+ mimeExtensions.push(plugin);
+ });
+ } else {
+ mimeExtensions.push(extension);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ {{/each}}
+
+ // Handled the registered standard extensions.
+ {{#each jupyterlab_extensions}}
+ try {
+ if (isDeferred('{{key}}')) {
+ deferred.matches.push('{{key}}');
+ ignorePlugins.push('{{key}}');
+ }
+ if (isDisabled('{{@key}}')) {
+ disabled.matches.push('{{@key}}');
+ } else {
+ module = require('{{@key}}/{{this}}');
+ extension = module.default;
+
+ // Handle CommonJS exports.
+ if (!module.hasOwnProperty('__esModule')) {
+ extension = module;
+ }
+
+ if (Array.isArray(extension)) {
+ extension.forEach(function(plugin) {
+ if (isDeferred(plugin.id)) {
+ deferred.matches.push(plugin.id);
+ ignorePlugins.push(plugin.id);
+ }
+ if (isDisabled(plugin.id)) {
+ disabled.matches.push(plugin.id);
+ return;
+ }
+ register.push(plugin);
+ });
+ } else {
+ register.push(extension);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ {{/each}}
+
+ var lab = new JupyterLab({
+ mimeExtensions: mimeExtensions,
+ disabled: disabled,
+ deferred: deferred
+ });
+ register.forEach(function(item) { lab.registerPluginModule(item); });
+ lab.start({ ignorePlugins: ignorePlugins });
+
+ // Expose global lab instance when in dev mode.
+ if ((PageConfig.getOption('devMode') || '').toLowerCase() === 'true') {
+ window.lab = lab;
+ }
+
+ // Handle a browser test.
+ var browserTest = PageConfig.getOption('browserTest');
+ if (browserTest.toLowerCase() === 'true') {
+ var el = document.createElement('div');
+ el.id = 'browserTest';
+ document.body.appendChild(el);
+ el.textContent = '[]';
+ el.style.display = 'none';
+ var errors = [];
+ var reported = false;
+ var timeout = 25000;
+
+ var report = function(errors) {
+ if (reported) {
+ return;
+ }
+ reported = true;
+ el.className = 'completed';
+ }
+
+ window.onerror = function(msg, url, line, col, error) {
+ errors.push(String(error));
+ el.textContent = JSON.stringify(errors)
+ };
+ console.error = function(message) {
+ errors.push(String(message));
+ el.textContent = JSON.stringify(errors)
+ };
+
+ lab.restored
+ .then(function() { report(errors); })
+ .catch(function(reason) { report([`RestoreError: ${reason.message}`]); });
+
+ // Handle failures to restore after the timeout has elapsed.
+ window.setTimeout(function() { report(errors); }, timeout);
+ }
+
+}
+
+window.addEventListener('load', main);
diff --git a/dev_mode/package.json b/dev_mode/package.json
new file mode 100644
index 00000000..83bcad4e
--- /dev/null
+++ b/dev_mode/package.json
@@ -0,0 +1,311 @@
+{
+ "name": "@jupyterlab/application-top",
+ "version": "1.0.0-alpha.3",
+ "private": true,
+ "scripts": {
+ "build": "webpack",
+ "build:prod": "webpack --config webpack.prod.config.js",
+ "build:prod:stats": "webpack --profile --config webpack.prod.config.js --json > stats.json",
+ "build:stats": "webpack --profile --json > stats.json",
+ "clean": "rimraf build",
+ "prepublishOnly": "npm run build",
+ "watch": "webpack --watch"
+ },
+ "dependencies": {
+ "@jupyterlab/application": "^1.0.0-alpha.3",
+ "@jupyterlab/application-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/apputils": "^1.0.0-alpha.3",
+ "@jupyterlab/apputils-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/attachments": "^1.0.0-alpha.3",
+ "@jupyterlab/cells": "^1.0.0-alpha.4",
+ "@jupyterlab/codeeditor": "^1.0.0-alpha.3",
+ "@jupyterlab/codemirror": "^1.0.0-alpha.3",
+ "@jupyterlab/codemirror-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/completer": "^1.0.0-alpha.3",
+ "@jupyterlab/completer-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/console": "^1.0.0-alpha.3",
+ "@jupyterlab/console-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/coreutils": "^3.0.0-alpha.3",
+ "@jupyterlab/csvviewer": "^1.0.0-alpha.3",
+ "@jupyterlab/csvviewer-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/docmanager": "^1.0.0-alpha.3",
+ "@jupyterlab/docmanager-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/docregistry": "^1.0.0-alpha.3",
+ "@jupyterlab/documentsearch": "^1.0.0-alpha.3",
+ "@jupyterlab/documentsearch-extension": "^1.0.0-alpha.4",
+ "@jupyterlab/extensionmanager": "^1.0.0-alpha.3",
+ "@jupyterlab/extensionmanager-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/faq-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/filebrowser": "^1.0.0-alpha.3",
+ "@jupyterlab/filebrowser-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/fileeditor": "^1.0.0-alpha.3",
+ "@jupyterlab/fileeditor-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/help-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/htmlviewer": "^1.0.0-alpha.3",
+ "@jupyterlab/htmlviewer-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/imageviewer": "^1.0.0-alpha.3",
+ "@jupyterlab/imageviewer-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/inspector": "^1.0.0-alpha.3",
+ "@jupyterlab/inspector-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/javascript-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/json-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/launcher": "^1.0.0-alpha.3",
+ "@jupyterlab/launcher-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/mainmenu": "^1.0.0-alpha.3",
+ "@jupyterlab/mainmenu-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/markdownviewer": "^1.0.0-alpha.3",
+ "@jupyterlab/markdownviewer-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/mathjax2": "^1.0.0-alpha.3",
+ "@jupyterlab/mathjax2-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/notebook": "^1.0.0-alpha.4",
+ "@jupyterlab/notebook-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/observables": "^2.2.0-alpha.3",
+ "@jupyterlab/outputarea": "^1.0.0-alpha.3",
+ "@jupyterlab/pdf-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/rendermime": "^1.0.0-alpha.3",
+ "@jupyterlab/rendermime-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/rendermime-interfaces": "^1.3.0-alpha.3",
+ "@jupyterlab/running": "^1.0.0-alpha.3",
+ "@jupyterlab/running-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/services": "^4.0.0-alpha.3",
+ "@jupyterlab/settingeditor": "^1.0.0-alpha.3",
+ "@jupyterlab/settingeditor-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/shortcuts-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/statusbar": "^1.0.0-alpha.3",
+ "@jupyterlab/statusbar-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/tabmanager-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/terminal": "^1.0.0-alpha.3",
+ "@jupyterlab/terminal-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/theme-dark-extension": "^1.0.0-alpha.4",
+ "@jupyterlab/theme-light-extension": "^1.0.0-alpha.4",
+ "@jupyterlab/tooltip": "^1.0.0-alpha.3",
+ "@jupyterlab/tooltip-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/ui-components": "^1.0.0-alpha.3",
+ "@jupyterlab/vdom-extension": "^1.0.0-alpha.3",
+ "@jupyterlab/vega4-extension": "^1.0.0-alpha.3",
+ "@phosphor/algorithm": "^1.1.2",
+ "@phosphor/application": "^1.6.0",
+ "@phosphor/commands": "^1.6.1",
+ "@phosphor/coreutils": "^1.3.0",
+ "@phosphor/datagrid": "^0.1.6",
+ "@phosphor/disposable": "^1.1.2",
+ "@phosphor/domutils": "^1.1.2",
+ "@phosphor/dragdrop": "^1.3.0",
+ "@phosphor/messaging": "^1.2.2",
+ "@phosphor/properties": "^1.1.2",
+ "@phosphor/signaling": "^1.2.2",
+ "@phosphor/virtualdom": "^1.1.2",
+ "@phosphor/widgets": "^1.6.0",
+ "ajv": "^6.5.5",
+ "codemirror": "~5.42.0",
+ "comment-json": "^1.1.3",
+ "es6-promise": "~4.1.1",
+ "marked": "0.5.1",
+ "moment": "~2.21.0",
+ "path-posix": "~1.0.0",
+ "react": "~16.4.2",
+ "react-dom": "~16.4.2",
+ "react-paginate": "^5.2.3",
+ "sanitize-html": "~1.18.2",
+ "semver": "^5.5.0",
+ "url-parse": "~1.4.3",
+ "xterm": "~3.10.1"
+ },
+ "devDependencies": {
+ "@jupyterlab/buildutils": "^1.0.0-alpha.3",
+ "css-loader": "~0.28.7",
+ "duplicate-package-checker-webpack-plugin": "^3.0.0",
+ "file-loader": "~1.1.11",
+ "fs-extra": "~4.0.2",
+ "glob": "~7.1.2",
+ "handlebars": "~4.0.11",
+ "html-loader": "^0.5.1",
+ "html-webpack-plugin": "~3.2.0",
+ "mini-css-extract-plugin": "~0.4.4",
+ "raw-loader": "~0.5.1",
+ "rimraf": "~2.6.2",
+ "sort-package-json": "~1.7.1",
+ "source-map-loader": "~0.2.1",
+ "style-loader": "~0.21.0",
+ "svg-url-loader": "~2.3.1",
+ "svgo": "~1.0.4",
+ "svgo-loader": "~2.1.0",
+ "uglifyjs-webpack-plugin": "~1.2.5",
+ "url-loader": "~1.0.1",
+ "webpack": "~4.12.0",
+ "webpack-cli": "^3.0.3",
+ "webpack-merge": "^4.1.1",
+ "webpack-visualizer-plugin": "^0.1.11"
+ },
+ "engines": {
+ "node": ">=6.11.5"
+ },
+ "jupyterlab": {
+ "extensions": {
+ "@jupyterlab/application-extension": "",
+ "@jupyterlab/apputils-extension": "",
+ "@jupyterlab/codemirror-extension": "",
+ "@jupyterlab/completer-extension": "",
+ "@jupyterlab/console-extension": "",
+ "@jupyterlab/csvviewer-extension": "",
+ "@jupyterlab/docmanager-extension": "",
+ "@jupyterlab/documentsearch-extension": "",
+ "@jupyterlab/extensionmanager-extension": "",
+ "@jupyterlab/faq-extension": "",
+ "@jupyterlab/filebrowser-extension": "",
+ "@jupyterlab/fileeditor-extension": "",
+ "@jupyterlab/help-extension": "",
+ "@jupyterlab/htmlviewer-extension": "",
+ "@jupyterlab/imageviewer-extension": "",
+ "@jupyterlab/inspector-extension": "",
+ "@jupyterlab/launcher-extension": "",
+ "@jupyterlab/mainmenu-extension": "",
+ "@jupyterlab/markdownviewer-extension": "",
+ "@jupyterlab/mathjax2-extension": "",
+ "@jupyterlab/notebook-extension": "",
+ "@jupyterlab/rendermime-extension": "",
+ "@jupyterlab/running-extension": "",
+ "@jupyterlab/settingeditor-extension": "",
+ "@jupyterlab/shortcuts-extension": "",
+ "@jupyterlab/statusbar-extension": "",
+ "@jupyterlab/tabmanager-extension": "",
+ "@jupyterlab/terminal-extension": "",
+ "@jupyterlab/theme-dark-extension": "",
+ "@jupyterlab/theme-light-extension": "",
+ "@jupyterlab/tooltip-extension": ""
+ },
+ "mimeExtensions": {
+ "@jupyterlab/javascript-extension": "",
+ "@jupyterlab/json-extension": "",
+ "@jupyterlab/pdf-extension": "",
+ "@jupyterlab/vdom-extension": "",
+ "@jupyterlab/vega4-extension": ""
+ },
+ "name": "JupyterLab",
+ "buildDir": "./static",
+ "outputDir": ".",
+ "singletonPackages": [
+ "@jupyterlab/application",
+ "@jupyterlab/apputils",
+ "@jupyterlab/console",
+ "@jupyterlab/coreutils",
+ "@jupyterlab/docmanager",
+ "@jupyterlab/extensionmanager",
+ "@jupyterlab/filebrowser",
+ "@jupyterlab/fileeditor",
+ "@jupyterlab/imageviewer",
+ "@jupyterlab/launcher",
+ "@jupyterlab/notebook",
+ "@jupyterlab/rendermime",
+ "@jupyterlab/rendermime-interfaces",
+ "@jupyterlab/services",
+ "@jupyterlab/terminal",
+ "@jupyterlab/tooltip",
+ "@phosphor/coreutils",
+ "@phosphor/widgets"
+ ],
+ "vendor": [
+ "@phosphor/algorithm",
+ "@phosphor/application",
+ "@phosphor/commands",
+ "@phosphor/coreutils",
+ "@phosphor/datagrid",
+ "@phosphor/disposable",
+ "@phosphor/domutils",
+ "@phosphor/dragdrop",
+ "@phosphor/messaging",
+ "@phosphor/properties",
+ "@phosphor/signaling",
+ "@phosphor/virtualdom",
+ "@phosphor/widgets",
+ "ajv",
+ "codemirror",
+ "comment-json",
+ "es6-promise",
+ "marked",
+ "moment",
+ "path-posix",
+ "react",
+ "react-dom",
+ "react-paginate",
+ "sanitize-html",
+ "semver",
+ "url-parse",
+ "xterm"
+ ],
+ "version": "1.0.0a1",
+ "linkedPackages": {
+ "@jupyterlab/application": "../packages/application",
+ "@jupyterlab/application-extension": "../packages/application-extension",
+ "@jupyterlab/apputils": "../packages/apputils",
+ "@jupyterlab/apputils-extension": "../packages/apputils-extension",
+ "@jupyterlab/attachments": "../packages/attachments",
+ "@jupyterlab/cells": "../packages/cells",
+ "@jupyterlab/codeeditor": "../packages/codeeditor",
+ "@jupyterlab/codemirror": "../packages/codemirror",
+ "@jupyterlab/codemirror-extension": "../packages/codemirror-extension",
+ "@jupyterlab/completer": "../packages/completer",
+ "@jupyterlab/completer-extension": "../packages/completer-extension",
+ "@jupyterlab/console": "../packages/console",
+ "@jupyterlab/console-extension": "../packages/console-extension",
+ "@jupyterlab/coreutils": "../packages/coreutils",
+ "@jupyterlab/csvviewer": "../packages/csvviewer",
+ "@jupyterlab/csvviewer-extension": "../packages/csvviewer-extension",
+ "@jupyterlab/docmanager": "../packages/docmanager",
+ "@jupyterlab/docmanager-extension": "../packages/docmanager-extension",
+ "@jupyterlab/docregistry": "../packages/docregistry",
+ "@jupyterlab/documentsearch": "../packages/documentsearch",
+ "@jupyterlab/documentsearch-extension": "../packages/documentsearch-extension",
+ "@jupyterlab/extensionmanager": "../packages/extensionmanager",
+ "@jupyterlab/extensionmanager-extension": "../packages/extensionmanager-extension",
+ "@jupyterlab/faq-extension": "../packages/faq-extension",
+ "@jupyterlab/filebrowser": "../packages/filebrowser",
+ "@jupyterlab/filebrowser-extension": "../packages/filebrowser-extension",
+ "@jupyterlab/fileeditor": "../packages/fileeditor",
+ "@jupyterlab/fileeditor-extension": "../packages/fileeditor-extension",
+ "@jupyterlab/help-extension": "../packages/help-extension",
+ "@jupyterlab/htmlviewer": "../packages/htmlviewer",
+ "@jupyterlab/htmlviewer-extension": "../packages/htmlviewer-extension",
+ "@jupyterlab/imageviewer": "../packages/imageviewer",
+ "@jupyterlab/imageviewer-extension": "../packages/imageviewer-extension",
+ "@jupyterlab/inspector": "../packages/inspector",
+ "@jupyterlab/inspector-extension": "../packages/inspector-extension",
+ "@jupyterlab/javascript-extension": "../packages/javascript-extension",
+ "@jupyterlab/json-extension": "../packages/json-extension",
+ "@jupyterlab/launcher": "../packages/launcher",
+ "@jupyterlab/launcher-extension": "../packages/launcher-extension",
+ "@jupyterlab/mainmenu": "../packages/mainmenu",
+ "@jupyterlab/mainmenu-extension": "../packages/mainmenu-extension",
+ "@jupyterlab/markdownviewer": "../packages/markdownviewer",
+ "@jupyterlab/markdownviewer-extension": "../packages/markdownviewer-extension",
+ "@jupyterlab/mathjax2": "../packages/mathjax2",
+ "@jupyterlab/mathjax2-extension": "../packages/mathjax2-extension",
+ "@jupyterlab/notebook": "../packages/notebook",
+ "@jupyterlab/notebook-extension": "../packages/notebook-extension",
+ "@jupyterlab/observables": "../packages/observables",
+ "@jupyterlab/outputarea": "../packages/outputarea",
+ "@jupyterlab/pdf-extension": "../packages/pdf-extension",
+ "@jupyterlab/rendermime": "../packages/rendermime",
+ "@jupyterlab/rendermime-extension": "../packages/rendermime-extension",
+ "@jupyterlab/rendermime-interfaces": "../packages/rendermime-interfaces",
+ "@jupyterlab/running": "../packages/running",
+ "@jupyterlab/running-extension": "../packages/running-extension",
+ "@jupyterlab/services": "../packages/services",
+ "@jupyterlab/settingeditor": "../packages/settingeditor",
+ "@jupyterlab/settingeditor-extension": "../packages/settingeditor-extension",
+ "@jupyterlab/shortcuts-extension": "../packages/shortcuts-extension",
+ "@jupyterlab/statusbar": "../packages/statusbar",
+ "@jupyterlab/statusbar-extension": "../packages/statusbar-extension",
+ "@jupyterlab/tabmanager-extension": "../packages/tabmanager-extension",
+ "@jupyterlab/terminal": "../packages/terminal",
+ "@jupyterlab/terminal-extension": "../packages/terminal-extension",
+ "@jupyterlab/theme-dark-extension": "../packages/theme-dark-extension",
+ "@jupyterlab/theme-light-extension": "../packages/theme-light-extension",
+ "@jupyterlab/tooltip": "../packages/tooltip",
+ "@jupyterlab/tooltip-extension": "../packages/tooltip-extension",
+ "@jupyterlab/ui-components": "../packages/ui-components",
+ "@jupyterlab/vdom-extension": "../packages/vdom-extension",
+ "@jupyterlab/vega4-extension": "../packages/vega4-extension"
+ }
+ }
+}
diff --git a/dev_mode/templates/error.html b/dev_mode/templates/error.html
new file mode 100644
index 00000000..5c389852
--- /dev/null
+++ b/dev_mode/templates/error.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ {% block title %}{{page_title}}{% endblock %}
+
+ {% block favicon %}{% endblock %}
+
+
+
+
+
+{% block stylesheet %}
+
+{% endblock %}
+{% block site %}
+
+
+ {% block h1_error %}
+
JupyterLab assets not detected, please rebuild
+
+ {% endblock h1_error %}
+
+
+{% endblock %}
+
+
+
+
diff --git a/dev_mode/templates/partial.html b/dev_mode/templates/partial.html
new file mode 100644
index 00000000..673b9c52
--- /dev/null
+++ b/dev_mode/templates/partial.html
@@ -0,0 +1,12 @@
+
+
+ {% block favicon %}
+
+
+ {% endblock %}
diff --git a/dev_mode/templates/template.html b/dev_mode/templates/template.html
new file mode 100644
index 00000000..3661c53e
--- /dev/null
+++ b/dev_mode/templates/template.html
@@ -0,0 +1,36 @@
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+ <%= require('html-loader!./partial.html') %>
+
+
+
+
+
+
+
diff --git a/dev_mode/webpack.config.js b/dev_mode/webpack.config.js
new file mode 100644
index 00000000..aa76727b
--- /dev/null
+++ b/dev_mode/webpack.config.js
@@ -0,0 +1,237 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+var path = require('path');
+var fs = require('fs-extra');
+var Handlebars = require('handlebars');
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var webpack = require('webpack');
+var DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
+var Visualizer = require('webpack-visualizer-plugin');
+
+var Build = require('@jupyterlab/buildutils').Build;
+var package_data = require('./package.json');
+
+// Handle the extensions.
+var jlab = package_data.jupyterlab;
+var extensions = jlab.extensions;
+var mimeExtensions = jlab.mimeExtensions;
+
+var extraConfig = Build.ensureAssets({
+ packageNames: Object.keys(mimeExtensions).concat(Object.keys(extensions)),
+ output: jlab.outputDir
+});
+
+// Create the entry point file.
+var source = fs.readFileSync('index.js').toString();
+var template = Handlebars.compile(source);
+var data = {
+ jupyterlab_extensions: extensions,
+ jupyterlab_mime_extensions: mimeExtensions
+};
+var result = template(data);
+
+// Ensure a clear build directory.
+var buildDir = path.resolve(jlab.buildDir);
+if (fs.existsSync(buildDir)) {
+ fs.removeSync(buildDir);
+}
+fs.ensureDirSync(buildDir);
+
+fs.writeFileSync(path.join(buildDir, 'index.out.js'), result);
+fs.copySync('./package.json', path.join(buildDir, 'package.json'));
+
+// Set up variables for watch mode.
+var localLinked = {};
+var ignoreCache = Object.create(null);
+Object.keys(jlab.linkedPackages).forEach(function(name) {
+ var localPath = require.resolve(path.join(name, 'package.json'));
+ localLinked[name] = path.dirname(localPath);
+});
+var ignorePatterns = [/^\.\#/]; // eslint-disable-line
+
+/**
+ * Sync a local path to a linked package path if they are files and differ.
+ */
+function maybeSync(localPath, name, rest) {
+ var stats = fs.statSync(localPath);
+ if (!stats.isFile(localPath)) {
+ return;
+ }
+ var source = fs.realpathSync(path.join(jlab.linkedPackages[name], rest));
+ if (source === fs.realpathSync(localPath)) {
+ return;
+ }
+ fs.watchFile(source, { interval: 500 }, function(curr) {
+ if (!curr || curr.nlink === 0) {
+ return;
+ }
+ try {
+ fs.copySync(source, localPath);
+ } catch (err) {
+ console.error(err);
+ }
+ });
+}
+
+/**
+ * A WebPack Plugin that copies the assets to the static directory and
+ * fixes the output of the HTMLWebpackPlugin
+ */
+function JupyterFrontEndPlugin() {}
+
+JupyterFrontEndPlugin.prototype.apply = function(compiler) {
+ compiler.hooks.afterEmit.tap(
+ 'JupyterFrontEndPlugin',
+ function() {
+ // Fix the template output.
+ var indexPath = path.join(buildDir, 'index.html');
+ var indexData = fs.readFileSync(indexPath, 'utf8');
+ indexData = indexData
+ .split('{{page_config.bundleUrl}}/')
+ .join('{{page_config.bundleUrl}}');
+ fs.writeFileSync(indexPath, indexData, 'utf8');
+
+ // Copy the static assets.
+ var staticDir = jlab.staticDir;
+ if (!staticDir) {
+ return;
+ }
+ // Ensure a clean static directory on the first emit.
+ if (this._first && fs.existsSync(staticDir)) {
+ fs.removeSync(staticDir);
+ }
+ this._first = false;
+ fs.copySync(buildDir, staticDir);
+ }.bind(this)
+ );
+};
+
+JupyterFrontEndPlugin.prototype._first = true;
+
+const plugins = [
+ new DuplicatePackageCheckerPlugin({
+ verbose: true,
+ exclude(instance) {
+ // ignore known duplicates
+ return ['domelementtype', 'hash-base', 'inherits'].includes(
+ instance.name
+ );
+ }
+ }),
+ new HtmlWebpackPlugin({
+ template: path.join('templates', 'template.html'),
+ title: jlab.name || 'JupyterLab'
+ }),
+ new webpack.HashedModuleIdsPlugin(),
+ new JupyterFrontEndPlugin({})
+];
+
+if (process.argv.includes('--analyze')) {
+ plugins.push(new Visualizer());
+}
+
+module.exports = [
+ {
+ mode: 'development',
+ entry: {
+ main: ['whatwg-fetch', path.resolve(buildDir, 'index.out.js')]
+ },
+ output: {
+ path: path.resolve(buildDir),
+ publicPath: '{{page_config.bundleUrl}}',
+ filename: '[name].[chunkhash].js'
+ },
+ optimization: {
+ splitChunks: {
+ chunks: 'all'
+ }
+ },
+ module: {
+ rules: [
+ { test: /\.css$/, use: ['style-loader', 'css-loader'] },
+ { test: /\.md$/, use: 'raw-loader' },
+ { test: /\.txt$/, use: 'raw-loader' },
+ {
+ test: /\.js$/,
+ use: ['source-map-loader'],
+ enforce: 'pre',
+ // eslint-disable-next-line no-undef
+ exclude: /node_modules/
+ },
+ { test: /\.(jpg|png|gif)$/, use: 'file-loader' },
+ { test: /\.js.map$/, use: 'file-loader' },
+ {
+ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
+ use: 'url-loader?limit=10000&mimetype=application/font-woff'
+ },
+ {
+ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
+ use: 'url-loader?limit=10000&mimetype=application/font-woff'
+ },
+ {
+ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
+ use: 'url-loader?limit=10000&mimetype=application/octet-stream'
+ },
+ {
+ test: /\.otf(\?v=\d+\.\d+\.\d+)?$/,
+ use: 'url-loader?limit=10000&mimetype=application/octet-stream'
+ },
+ { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, use: 'file-loader' },
+ {
+ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
+ use: 'url-loader?limit=10000&mimetype=image/svg+xml'
+ }
+ ]
+ },
+ watchOptions: {
+ ignored: function(localPath) {
+ localPath = path.resolve(localPath);
+ if (localPath in ignoreCache) {
+ return ignoreCache[localPath];
+ }
+
+ // Ignore files with certain patterns
+ var baseName = localPath.replace(/^.*[\\\/]/, ''); // eslint-disable-line
+ if (
+ ignorePatterns.some(function(rexp) {
+ return baseName.match(rexp);
+ })
+ ) {
+ return true;
+ }
+
+ // Limit the watched files to those in our local linked package dirs.
+ var ignore = true;
+ Object.keys(localLinked).some(function(name) {
+ // Bail if already found.
+ var rootPath = localLinked[name];
+ var contained = localPath.indexOf(rootPath + path.sep) !== -1;
+ if (localPath !== rootPath && !contained) {
+ return false;
+ }
+ var rest = localPath.slice(rootPath.length);
+ if (rest.indexOf('node_modules') === -1) {
+ ignore = false;
+ maybeSync(localPath, name, rest);
+ }
+ return true;
+ });
+ ignoreCache[localPath] = ignore;
+ return ignore;
+ }
+ },
+ node: {
+ fs: 'empty'
+ },
+ bail: true,
+ devtool: 'source-map',
+ externals: ['node-fetch', 'ws'],
+ plugins,
+ stats: {
+ chunkModules: true
+ }
+ }
+].concat(extraConfig);
diff --git a/dev_mode/webpack.prod.config.js b/dev_mode/webpack.prod.config.js
new file mode 100644
index 00000000..440abc2b
--- /dev/null
+++ b/dev_mode/webpack.prod.config.js
@@ -0,0 +1,26 @@
+var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+var merge = require('webpack-merge');
+var config = require('./webpack.config');
+
+config[0] = merge(config[0], {
+ mode: 'production',
+ devtool: 'source-map',
+ optimization: {
+ minimizer: [
+ new UglifyJsPlugin({
+ parallel: true,
+ sourceMap: true,
+ uglifyOptions: {
+ beautify: false,
+ comments: false,
+ compress: false,
+ ecma: 6,
+ mangle: true
+ },
+ cache: process.platform !== 'win32'
+ })
+ ]
+ }
+});
+
+module.exports = config;
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..e8d92b90
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS = -W --keep-going
+SPHINXBUILD = python -msphinx
+SPHINXPROJ = JupyterLab
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/environment.yml b/docs/environment.yml
new file mode 100644
index 00000000..4a880119
--- /dev/null
+++ b/docs/environment.yml
@@ -0,0 +1,8 @@
+name: jupyterlab_documentation
+channels:
+ - conda-forge
+dependencies:
+- python=3.5
+- sphinx>=1.8
+- sphinx_rtd_theme
+- recommonmark
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 00000000..6814d9f1
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=python -msphinx
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+set SPHINXPROJ=JupyterLab
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The Sphinx module was not found. Make sure you have Sphinx installed,
+ echo.then set the SPHINXBUILD environment variable to point to the full
+ echo.path of the 'sphinx-build' executable. Alternatively you may add the
+ echo.Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/docs/scripts/graph-dependencies.js b/docs/scripts/graph-dependencies.js
new file mode 100644
index 00000000..4da89eae
--- /dev/null
+++ b/docs/scripts/graph-dependencies.js
@@ -0,0 +1,69 @@
+var childProcess = require('child_process');
+var fs = require('fs-extra');
+var glob = require('glob');
+var path = require('path');
+var url = require('url');
+
+var basePath = path.resolve('..');
+var baseUrl = 'https://github.com/jupyterlab/jupyterlab/tree/master/packages';
+var packages = glob.sync(path.join(basePath, 'packages/*'));
+
+// Begin the graph specification
+var text = 'digraph G {\n';
+text += 'ratio = 0.6;\n';
+text += 'rankdir=LR;\n';
+
+packages.forEach(function(packagePath) {
+ // Load the package.json data.
+ var dataPath = path.join(packagePath, 'package.json');
+ try {
+ var data = require(dataPath);
+ } catch (e) {
+ return;
+ }
+
+ // Don't include private packages.
+ if (data.private === true) {
+ return;
+ }
+
+ // Only include packages in the @jupyterlab namespace.
+ if (data.name.indexOf('@jupyterlab') === -1) {
+ return;
+ }
+
+ // In order to cut down on the number of graph nodes,
+ // don't include "*-extension" packages.
+ if (data.name.indexOf('-extension') !== -1) {
+ return;
+ }
+
+ // Don't include the metapackage.
+ if (data.name === '@jupyterlab/metapackage') {
+ return;
+ }
+
+ // Construct a URL to the package on GitHub.
+ var Url = url.resolve(baseUrl, 'packages/' + path.basename(packagePath));
+
+ // Remove the '@jupyterlab' part of the name.
+ var name = '"' + data.name.split('/')[1] + '"';
+ text += name + '[URL="' + Url + '"];\n';
+
+ var deps = data.dependencies || [];
+ for (var dep in deps) {
+ // Don't include non-jupyterlab dependencies.
+ if (dep.indexOf('@jupyterlab') === -1) {
+ continue;
+ }
+ dep = '"' + dep.split('/')[1] + '"';
+ text += name + ' -> ' + dep + ';\n';
+ }
+});
+
+text += '}\n';
+fs.writeFileSync('./dependencies.gv', text);
+childProcess.execSync(
+ 'cat dependencies.gv | tred | dot -Tsvg -o dependency-graph.svg'
+);
+fs.unlink('./dependencies.gv');
diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css
new file mode 100644
index 00000000..2b59c601
--- /dev/null
+++ b/docs/source/_static/custom.css
@@ -0,0 +1,59 @@
+.wy-nav-side p.caption {
+ color: #f5f5f5;
+}
+
+div.wy-side-nav-search {
+ background: #f37626;
+}
+
+.wy-nav-content iframe {
+ margin: auto;
+ display: block;
+}
+
+.wy-breadcrumbs-aside img {
+ height: 1em !important;
+}
+
+/* Elevation
+ *
+ * We style box-shadows using Material Design's idea of elevation. These particular numbers are taken from here:
+ *
+ * https://github.com/material-components/material-components-web
+ * https://material-components-web.appspot.com/elevation.html
+ */
+
+.rst-content img.jp-screenshot {
+ border: none;
+ /* MD Elevation 8 */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
+ 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+ margin-bottom: 24px;
+}
+
+/*
+ * The div.jp-youtube-video styling is done to get the YouTube video to size dynamically
+ * to 100% of the content width.
+ */
+
+.rst-content div.jp-youtube-video {
+ position: relative;
+ width: 100%;
+ height: 0px;
+ /* This must be equal to the inverse of the aspect ratio of the video */
+ /* The current value is: 56.25% = 315/560 */
+ padding-bottom: 56.25%;
+ border: none;
+ /* MD Elevation 8 */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
+ 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+ margin-bottom: 24px;
+}
+
+.rst-content div.jp-youtube-video iframe {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+}
diff --git a/docs/source/_static/jupyter_logo.svg b/docs/source/_static/jupyter_logo.svg
new file mode 100644
index 00000000..b85d1f4b
--- /dev/null
+++ b/docs/source/_static/jupyter_logo.svg
@@ -0,0 +1,42 @@
+
diff --git a/docs/source/_templates/breadcrumbs.html b/docs/source/_templates/breadcrumbs.html
new file mode 100644
index 00000000..d53215c2
--- /dev/null
+++ b/docs/source/_templates/breadcrumbs.html
@@ -0,0 +1,49 @@
+{% extends '!breadcrumbs.html' %}
+
+{% block breadcrumbs %}
+
+{% endblock %}
diff --git a/docs/source/_templates/footer.html b/docs/source/_templates/footer.html
new file mode 100644
index 00000000..b91bf270
--- /dev/null
+++ b/docs/source/_templates/footer.html
@@ -0,0 +1,54 @@
+
+
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 00000000..f2b95638
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# JupyterLab documentation build configuration file, created by
+# sphinx-quickstart on Thu Jan 4 15:10:23 2018.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+# For conversion from markdown to html
+import recommonmark.parser
+from recommonmark.transform import AutoStructify
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.intersphinx',
+ 'sphinx.ext.mathjax']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+
+source_parsers = {
+ '.md': 'recommonmark.parser.CommonMarkParser',
+}
+
+source_suffix = ['.rst', '.md']
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'JupyterLab'
+copyright = '2018, Project Jupyter'
+author = 'Project Jupyter'
+
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+
+import os
+_version_py = os.path.join('..', '..', 'jupyterlab', '_version.py')
+version_ns = {}
+
+with open(_version_py, mode='r') as version_file:
+ exec(version_file.read(), version_ns)
+
+# The short X.Y version.
+version = '%i.%i' % version_ns['version_info'][:2]
+# The full version, including alpha/beta/rc tags.
+release = version_ns['__version__']
+
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+import sphinx_rtd_theme
+html_theme = "sphinx_rtd_theme"
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# This is required for the alabaster theme
+# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
+html_sidebars = {
+ '**': [
+ 'about.html',
+ 'navigation.html',
+ 'relations.html', # needs 'show_related': True theme option to display
+ 'searchbox.html',
+ 'donate.html',
+ ]
+}
+
+# Output for github to be used in links
+html_context = {
+ "display_github": True, # Integrate GitHub
+ "github_user": "jupyterlab", # Username
+ "github_repo": "jupyterlab", # Repo name
+ "github_version": "master", # Version
+ "conf_py_path": "/docs/source/", # Path in the checkout to the docs root
+}
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'JupyterLabdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'JupyterLab.tex', 'JupyterLab Documentation',
+ 'Project Jupyter', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'jupyterlab', 'JupyterLab Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'JupyterLab', 'JupyterLab Documentation',
+ author, 'JupyterLab', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+
+# -- Options for Epub output ----------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+epub_author = author
+epub_publisher = author
+epub_copyright = copyright
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
+
+
+# autodoc configuration with AutoStructify
+#
+# See http://recommonmark.readthedocs.io/en/latest/auto_structify.html
+# See the setup function in current conf.py file in the recommonmark repo
+# https://github.com/rtfd/recommonmark/blob/master/docs/conf.py#L296
+github_doc_root = 'https://github.com/jupyterlab/jupyterlab/tree/master/docs/'
+
+# We can't rely on anchors because GitHub dynamically renders them for
+# markdown documents.
+linkcheck_anchors = False
+
+def setup(app):
+ app.add_config_value('recommonmark_config', {
+ 'url_resolver': lambda url: github_doc_root + url,
+ 'auto_toc_tree_section': 'Contents',
+ 'enable_eval_rst': True,
+ 'enable_auto_doc_ref': False,
+ }, True)
+ app.add_transform(AutoStructify)
+ app.add_stylesheet('custom.css') # may also be an URL
diff --git a/docs/source/developer/adding_content.rst b/docs/source/developer/adding_content.rst
new file mode 100644
index 00000000..ab3938a5
--- /dev/null
+++ b/docs/source/developer/adding_content.rst
@@ -0,0 +1,40 @@
+Adding Content
+--------------
+
+As an example: Add a leaflet viewer plugin for geoJSON files.
+
+- Go to npm: search for
+ `leaflet `__ (success!).
+
+- Go to ``jupyterlab`` top level source directory:
+ ``jlpm add leaflet``. This adds the file to the ``dependencies`` in
+ ``package.json``.
+
+- Next we see if there is a typing declaration for leaflet:
+ ``jlpm add --dev @types/leaflet``. Success!
+
+- If there are no typings, we must create our own. An example typings
+ file that exports functions is
+ `codemirror `__.
+ An example with a class is
+ `vdom `__.
+
+- Add a reference to the new library in ``src/typings.d.ts``.
+
+- Create a folder in ``src`` for the plugin.
+
+- Add ``index.ts`` and ``plugin.ts`` files.
+
+- If creating CSS, import them in ``src/default-themes/index.css``.
+
+- The ``index.ts`` file should have the core logic for the plugin. In
+ this case, it should create a widget and widget factory for rendering
+ geojson files (see :ref:`documents`).
+
+- The ``plugin.ts`` file should create the extension and add the
+ content to the application. In this case registering the widget
+ factory using the document registry.
+
+- Run ``jlpm run build`` from ``jupyterlab/jupyterlab``
+
+- Run ``jupyter lab`` and verify changes.
diff --git a/docs/source/developer/css.rst b/docs/source/developer/css.rst
new file mode 100644
index 00000000..3ad6296f
--- /dev/null
+++ b/docs/source/developer/css.rst
@@ -0,0 +1,193 @@
+CSS Patterns
+------------
+
+This document describes the patterns we are using to organize and write
+CSS for JupyterLab. JupyterLab is developed using a set of npm packages
+that are located in ``packages``. Each of these packages has its own
+style, but depend on CSS variables defined in a main theme package.
+
+CSS checklist
+~~~~~~~~~~~~~
+
+- CSS classnames are defined inline in the code. We used to put them as
+ all caps file-level ``const``\ s, but we are moving away from that.
+- CSS files for packages are located within the ``style``
+ subdirectory and imported into the plugin's ``index.css``.
+- The JupyterLab default CSS variables in the ``theme-light-extension``
+ and ``theme-dark-extension`` packages are used to style packages
+ where ever possible. Individual packages should not npm-depend on
+ these packages though, to enable the theme to be swapped out.
+- Additional public/private CSS variables are defined by plugins
+ sparingly and in accordance with the conventions described below.
+
+CSS variables
+~~~~~~~~~~~~~
+
+We are using native CSS variables in JupyterLab. This is to enable
+dynamic theming of built-in and third party plugins. As of December
+2017, CSS variables are supported in the latest stable versions of all
+popular browsers, except for IE. If a JupyterLab deployment needs to
+support these browsers, a server side CSS preprocessor such as Myth or
+cssnext may be used.
+
+Naming of CSS variables
+^^^^^^^^^^^^^^^^^^^^^^^
+
+We use the following convention for naming CSS variables:
+
+- Start all CSS variables with ``--jp-``.
+- Words in the variable name should be lowercase and separated with
+ ``-``.
+- The next segment should refer to the component and subcomponent, such
+ as ``--jp-notebook-cell-``.
+- The next segment should refer to any state modifiers such as
+ ``active``, ``not-active`` or ``focused``:
+ ``--jp-notebook-cell-focused``.
+- The final segment will typically be related to a CSS properties, such
+ as ``color``, ``font-size`` or ``background``:
+ ``--jp-notebook-cell-focused-background``.
+
+Public/private
+^^^^^^^^^^^^^^
+
+Some CSS variables in JupyterLab are considered part of our public API.
+Others are considered private and should not be used by third party
+plugins or themes. The difference between public and private variables
+is simple:
+
+- All private variables begin with ``--jp-private-``
+- All variables without the ``private-`` prefix are public.
+- Public variables should be defined under the ``:root``
+ pseudo-selector. This ensures that public CSS variables can be
+ inspected under the top-level ```` tag in the browser's dev
+ tools.
+- Where possible, private variables should be defined and scoped under
+ an appropriate selector other than ``:root``.
+
+CSS variable usage
+^^^^^^^^^^^^^^^^^^
+
+JupyterLab includes a default set of CSS variables in the file:
+
+``packages/theme-light-extension/style/variables.css``
+
+To ensure consistent design in JupyterLab, all built-in and third party
+extensions should use these variables in their styles if at all
+possible. Documentation about those variables can be found in the
+``variables.css`` file itself.
+
+Plugins are free to define additional public and private CSS variables
+in their own ``index.css`` file, but should do so sparingly.
+
+Again, we consider the names of the public CSS variables in this package
+to be our public API for CSS.
+
+File organization
+~~~~~~~~~~~~~~~~~
+
+We are organizing our CSS files in the following manner:
+
+- Each package in the top-level ``packages`` directory should contain
+ any CSS files in a ``style`` subdirectory that are needed to style
+ itself.
+- Multiple CSS files may be used and organized as needed, but they
+ should be imported into a single ``index.css`` at the top-level of
+ the plugin as `import '../style/index.css';`.
+
+CSS class names
+~~~~~~~~~~~~~~~
+
+We have a fairly formal method for naming our CSS classes.
+
+First, CSS class names are associated with TypeScript classes that
+extend ``phosphor.Widget``:
+
+The ``.node`` of each such widget should have a CSS class that matches
+the name of the TypeScript class:
+
+.. code:: TypeScript
+
+
+
+ class MyWidget extends Widget {
+
+ constructor() {
+ super();
+ this.addClass('jp-MyWidget');
+ }
+
+ }
+
+Second, subclasses should have a CSS class for both the parent and
+child:
+
+.. code:: TypeScript
+
+ class MyWidgetSubclass extends MyWidget {
+
+ constructor() {
+ super(); // Adds `jp-MyWidget`
+ this.addClass('jp-MyWidgetSubclass');
+ }
+
+ }
+
+In both of these cases, CSS class names with caps-case are reserved for
+situations where there is a named TypeScript ``Widget`` subclass. These
+classes are a way of a TypeScript class providing a public API for
+styling.
+
+Third, children nodes of a ``Widget`` should have a third segment in the
+CSS class name that gives a semantic naming of the component, such as:
+
+- ``jp-MyWidget-toolbar``
+- ``jp-MyWidget-button``
+- ``jp-MyWidget-contentButton``
+
+In general, the parent ``MyWidget`` should add these classes to the
+children. This applies when the children are plain DOM nodes or
+``Widget`` instances/subclasses themselves. Thus, the general naming of
+CSS classes is of the form ``jp-WidgetName-semanticChild``. This enables
+the styling of these children in a manner that is independent of the
+children implementation or CSS classes they have themselves.
+
+Fourth, some CSS classes are used to modify the state of a widget:
+
+- ``jp-mod-active``: applied to elements in the active state
+- ``jp-mod-hover``: applied to elements in the hover state
+- ``jp-mod-selected``: applied to elements while selected
+
+Fifth, some CSS classes are used to distinguish different types of a
+widget:
+
+- ``jp-type-separator``: applied to menu items that are separators
+- ``jp-type-directory``: applied to elements in the file browser that
+ are directories
+
+Edge cases
+~~~~~~~~~~
+
+Over time, we have found that there are some edge cases that these rules
+don't fully address. Here, we try to clarify those edge cases.
+
+**When should a parent add a class to children?**
+
+Above, we state that a parent (``MyWidget``), should add CSS classes to
+children that indicate the semantic function of the child. Thus, the
+``MyWidget`` subclass of ``Widget`` should add ``jp-MyWidget`` to itself
+and ``jp-MyWidget-toolbar`` to a toolbar child.
+
+What if the child itself is a ``Widget`` and already has a proper CSS
+class name itself, such as ``jp-Toolbar``? Why not use selectors such as
+``.jp-MyWidget .jp-Toolbar`` or ``.jp-MyWidget > .jp-Toolbar``?
+
+The reason is that these selectors are dependent on the implementation
+of the toolbar having the ``jp-Toolbar`` CSS class. When ``MyWidget``
+adds the ``jp-MyWidget-toolbar`` class, it can style the child
+independent of its implementation. The other reason to add the
+``jp-MyWidget-toolbar`` class is if the DOM structure is highly
+recursive, the usual descendant selectors may not be specific to target
+only the desired children.
+
+When in doubt, there is little harm done in parents adding selectors to
+children.
diff --git a/docs/source/developer/dependency-graph.svg b/docs/source/developer/dependency-graph.svg
new file mode 100644
index 00000000..7fd04ab6
--- /dev/null
+++ b/docs/source/developer/dependency-graph.svg
@@ -0,0 +1,502 @@
+
+
+
+
+
diff --git a/docs/source/developer/documentation.rst b/docs/source/developer/documentation.rst
new file mode 100644
index 00000000..abe35d7c
--- /dev/null
+++ b/docs/source/developer/documentation.rst
@@ -0,0 +1,150 @@
+Writing Documentation
+---------------------
+
+This section provides information about writing documentation for JupyterLab.
+See our `Contributor
+Guide `_ for
+details on installation and testing.
+
+Writing Style
+~~~~~~~~~~~~~
+
+- The documentation should be written in the second person, referring
+ to the reader as "you" and not using the first person plural "we."
+ The author of the documentation is not sitting next to the user, so
+ using "we" can lead to frustration when things don't work as
+ expected.
+- Avoid words that trivialize using JupyterLab such as "simply" or
+ "just." Tasks that developers find simple or easy may not be for
+ users.
+- Write in the active tense, so "drag the notebook cells..." rather
+ than "notebook cells can be dragged..."
+- The beginning of each section should begin with a short (1-2
+ sentence) high-level description of the topic, feature or component.
+- Use "enable" rather than "allow" to indicate what JupyterLab makes
+ possible for users. Using "allow" connotes that we are giving them
+ permission, whereas "enable" connotes empowerment.
+
+User Interface Naming Conventions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Documents, Files, and Activities
+''''''''''''''''''''''''''''''''
+
+Files are referrred to as either files or documents, depending on the context.
+
+Documents are more human centered. If human viewing, interpretation, interaction
+is an important part of the experience, it is a document in that context. For
+example, notebooks and markdown files will often be referring to as documents
+unless referring to the file-ness aspect of it (e.g., the notebook filename).
+
+Files are used in a less human-focused context. For example, we refer to files
+in relation to a file system or file name.
+
+Activities can be either a document or another UI panel that is not file backed,
+such as terminals, consoles or the inspector. An open document or file is an
+activity in that it is represented by a panel that you can interact with.
+
+
+Element Names
+'''''''''''''
+
+- The generic content area of a tabbed UI is a panel, but prefer to refer to the
+ more specific name, such as “File browser.” Tab bars have tabs which toggle
+ panels.
+- The menu bar contains menu items, which have their own submenus.
+- The main work area can be referred to as the work area when the name is unambiguous.
+- When describing elements in the UI, colloquial names are preferred
+ (e.g., “File browser” instead of “Files panel”).
+
+The majority of names are written in lower case. These names include:
+
+- tab
+- panel
+- menu bar
+- sidebar
+- file
+- document
+- activity
+- tab bar
+- main work area
+- file browser
+- command palette
+- cell inspector
+- code console
+
+The following sections of the user interface should be in title case, directly
+quoting a word in the UI:
+
+- File menu
+- Files tab
+- Running panel
+- Tabs panel
+- Single-Document Mode
+
+The capitalized words match the label of the UI element the user is clicking on
+because there does not exist a good colloquial name for the tool, such as “file
+browser” or “command palette”.
+
+See :ref:`interface` for descriptions of elements in the UI.
+
+Keyboard Shortcuts
+~~~~~~~~~~~~~~~~~~
+
+Typeset keyboard shortcuts as follows:
+
+- Monospace typeface, with spaces between individual keys:
+ ``Shift Enter``.
+- For modifiers, use the platform independent word describing key:
+ ``Shift``.
+- For the ``Accel`` key use the phrase: ``Command/Ctrl``.
+- Don’t use platform specific icons for modifier keys, as they are
+ difficult to display in a platform specific way on Sphinx/RTD.
+
+Screenshots and Animations
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Our documentation should contain screenshots and animations that
+illustrate and demonstrate the software. Here are some guidelines for
+preparing them:
+
+- Set screen resolution to non-hidpi (non-retina)
+
+- Set browser viewport to 1280x720 px.
+ The Firefox Web Developer extension and Chrome Developer Tools offer
+ device specific rendering that enables you to set this viewport resolution.
+
+- Capture the viewport, **not the full browser window**,
+ using the capture software of your choice. **Do not include any of the
+ desktop background**.
+
+- For PNGs, reduce their size using pngquant.
+ For movies, upload them to the IPython/Juptyter YouTube channel
+ and embed them in the docs with an iframe. The pngs can live in the main repository.
+ The movies should be added to the ``jupyterlab-media`` repository.
+
+- Use `www.youtube-nocookie.com` website, which can be found by
+ clicking on the 'privacy-enhanced' embedding option in the Share dialog on YouTube.
+ Add the following parameters the end of the URL ``?rel=0&showinfo=0``.
+ This disables the video title and related video suggestions.
+
+- Screenshots or animations should be proceeded by a sentence
+ describing the content, such as "To open a file, double-click on its
+ name in the File Browser:".
+
+- We have custom CSS that will add box shadows, and proper sizing of screenshots and
+ embedded YouTube videos. See examples in the documentation for how to embed these
+ assets.
+
+To help us organize screenshots and animations, please name the files with a prefix
+that matches the names of the source file in which they are used:
+
+ ::
+
+ sourcefile.rst
+ sourcefile_filebrowser.png
+ sourcefile_editmenu.png
+
+This will help us to keep track of the images as documentation content evolves.
+
+
diff --git a/docs/source/developer/documents.rst b/docs/source/developer/documents.rst
new file mode 100644
index 00000000..f7c091db
--- /dev/null
+++ b/docs/source/developer/documents.rst
@@ -0,0 +1,130 @@
+.. _documents:
+
+Documents
+---------
+
+JupyterLab can be extended in several ways:
+
+- Extensions (top level): Application extensions extend the
+ functionality of JupyterLab itself, and we cover them in the
+ :ref:`developer_extensions`.
+- **Document widget extensions (lower level):** Document widget
+ extensions extend the functionality of document widgets added to the
+ application, and we cover them in this section.
+
+For this section, the term 'document' refers to any visual thing that
+is backed by a file stored on disk (i.e. uses Contents API).
+
+Overview of document architecture
+---------------------------------
+
+A 'document' in JupyterLab is represented by a model instance implementing the `IModel `__ interface. The model interface is intentionally fairly small, and concentrates on representing the data in the document and signaling changes to that data. Each model has an associated `context `__ instance as well. The context for a model is the bridge between the internal data of the document, stored in the model, and the file metadata and operations possible on the file, such as save and revert. Since many objects will need both the context and the model, the context contains a reference to the model as its `.model` attribute.
+
+A single file path can have multiple different models (and hence different contexts) representing the file. For example, a notebook can be opened with a notebook model and with a text model. Different models for the same file path do not directly communicate with each other.
+
+`Document widgets `__ represent a view of a document model. There can be multiple document widgets associated with a single document model, and they naturally stay in sync with each other since they are views on the same underlying data model.
+
+
+The `Document
+Registry `__
+is where document types and factories are registered. Plugins can
+require a document registry instance and register their content types
+and providers.
+
+The `Document
+Manager `__
+uses the Document Registry to create models and widgets for documents.
+The Document Manager is only meant to be accessed by the File Browser
+itself.
+
+Document Registry
+~~~~~~~~~~~~~~~~~
+
+*Document widget extensions* in the JupyterLab application can register:
+
+- file types
+- model factories for specific file types
+- widget factories for specific model factories
+- widget extension factories
+- file creators
+
+`Widget Factories `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Create a widget for a given file.
+
+*Example*
+
+- The notebook widget factory that creates NotebookPanel widgets.
+
+`Model Factories `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Create a model for a given file.
+
+Models are generally differentiated by the contents options used to
+fetch the model (e.g. text, base64, notebook).
+
+`Widget Extension Factories `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Adds additional functionality to a widget type. An extension instance is
+created for each widget instance, enabling the extension to add
+functionality to each widget or observe the widget and/or its context.
+
+*Examples*
+
+- The ipywidgets extension that is created for NotebookPanel widgets.
+- Adding a button to the toolbar of each NotebookPanel widget.
+
+`File Types `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Intended to be used in a "Create New" dialog, providing a list of known
+file types.
+
+`File Creators `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Intended for create quick launch file creators.
+
+The default use will be for the "create new" dropdown in the file
+browser, giving list of items that can be created with default options
+(e.g. "Python 3 Notebook").
+
+`Document Models `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Created by the model factories and passed to widget factories and widget
+extension factories. Models are the way in which we interact with the
+data of a document. For a simple text file, we typically only use the
+``to/fromString()`` methods. A more complex document like a Notebook
+contains more points of interaction like the Notebook metadata.
+
+`Document Contexts `__
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Created by the Document Manager and passed to widget factories and
+widget extensions. The context contains the model as one of its
+properties so that we can pass a single object around.
+
+They are used to provide an abstracted interface to the session and
+contents API from ``@jupyterlab/services`` for the given model. They can
+be shared between widgets.
+
+The reason for a separate context and model is so that it is easy to
+create model factories and the heavy lifting of the context is left to
+the Document Manager. Contexts are not meant to be subclassed or
+re-implemented. Instead, the contexts are intended to be the glue
+between the document model and the wider application.
+
+Document Manager
+~~~~~~~~~~~~~~~~
+
+The *Document Manager* handles:
+
+- document models
+- document contexts
+
+The *File Browser* uses the *Document Manager* to open documents and
+manage them.
diff --git a/docs/source/developer/examples.rst b/docs/source/developer/examples.rst
new file mode 100644
index 00000000..b93c932a
--- /dev/null
+++ b/docs/source/developer/examples.rst
@@ -0,0 +1,56 @@
+Examples
+--------
+
+The ``examples`` directory in the JupyterLab repo contains:
+
+- several stand-alone examples (``console``, ``filebrowser``,
+ ``notebook``, ``terminal``)
+- a more complex example (``lab``).
+
+Installation instructions for the examples are found in the project's
+README.
+
+After installing the jupyter notebook server 4.2+, follow the steps for
+installing the development version of JupyterLab. To build the examples,
+enter from the ``jupyterlab`` repo root directory:
+
+::
+
+ jlpm run build:examples
+
+To run a particular example, navigate to the example's subdirectory in
+the ``examples`` directory and enter:
+
+::
+
+ python main.py
+
+Dissecting the 'filebrowser' example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The filebrowser example provides a stand-alone implementation of a
+filebrowser. Here's what the filebrowser's user interface looks like:
+
+|filebrowser user interface|
+
+Let's take a closer look at the source code in ``examples/filebrowser``.
+
+Directory structure of 'filebrowser' example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The filebrowser in ``examples/filebrowser`` is comprised by a handful of
+files and the ``src`` directory:
+
+|filebrowser source code|
+
+The filebrowser example has two key source files:
+
+- ``src/index.ts``: the TypeScript file that defines the functionality
+- ``main.py``: the Python file that enables the example to be run
+
+Reviewing the source code of each file will help you see the role that
+each file plays in the stand-alone filebrowser example.
+
+.. |filebrowser user interface| image:: filebrowser_example.png
+.. |filebrowser source code| image:: filebrowser_source.png
+
diff --git a/docs/source/developer/extension_dev.rst b/docs/source/developer/extension_dev.rst
new file mode 100644
index 00000000..8abab55f
--- /dev/null
+++ b/docs/source/developer/extension_dev.rst
@@ -0,0 +1,604 @@
+.. _developer_extensions:
+
+Extension Developer Guide
+-------------------------
+
+.. warning::
+
+ The extension developer API is not stable and will evolve in JupyterLab
+ releases in the near future.
+
+JupyterLab can be extended in four ways via:
+
+- **application plugins (top level):** Application plugins extend the
+ functionality of JupyterLab itself.
+- **mime renderer extensions (top level):** Mime Renderer extensions are
+ a convenience for creating an extension that can render mime data and
+ potentially render files of a given type.
+- **theme extensions (top level):** Theme extensions allow you to customize the appearance of
+ JupyterLab by adding your own fonts, CSS rules, and graphics to the application.
+- **document widget extensions (lower level):** Document widget extensions
+ extend the functionality of document widgets added to the
+ application, and we cover them in :ref:`documents`.
+
+See :ref:`xkcd_extension_tutorial` to learn how to make a simple JupyterLab extension.
+
+A JupyterLab application is comprised of:
+
+- A core Application object
+- Plugins
+
+Plugins
+~~~~~~~
+
+A plugin adds a core functionality to the application:
+
+- A plugin can require other plugins for operation.
+- A plugin is activated when it is needed by other plugins, or when
+ explicitly activated.
+- Plugins require and provide ``Token`` objects, which are used to
+ provide a typed value to the plugin's ``activate()`` method.
+- The module providing plugin(s) must meet the
+ `JupyterLab.IPluginModule `__
+ interface, by exporting a plugin object or array of plugin objects as
+ the default export.
+
+ We provide two cookie cutters to create JuptyerLab plugin extensions in
+ `CommonJS `__ and
+ `TypeScript `__.
+
+The default plugins in the JupyterLab application include:
+
+- `Terminal `__
+ - Adds the ability to create command prompt terminals.
+- `Shortcuts `__
+ - Sets the default set of shortcuts for the application.
+- `Images `__
+ - Adds a widget factory for displaying image files.
+- `Help `__
+ - Adds a side bar widget for displaying external documentation.
+- `File
+ Browser `__
+ - Creates the file browser and the document manager and the file
+ browser to the side bar.
+- `Editor `__
+ - Add a widget factory for displaying editable source files.
+- `Console `__
+ - Adds the ability to launch Jupyter Console instances for
+ interactive kernel console sessions.
+
+A dependency graph for the core JupyterLab plugins (along with links to
+their source) is shown here: |dependencies|
+
+.. danger::
+
+ Installing an extension allows for arbitrary code execution on the
+ server, kernel, and in the client's browser. You should therefore
+ take steps to protect against malicious changes to your extension's
+ code. This includes ensuring strong authentication for your npm
+ account.
+
+
+Application Object
+~~~~~~~~~~~~~~~~~~
+
+A Jupyter front-end application object is given to each plugin in its
+``activate()`` function. The application object has:
+
+- commands - used to add and execute commands in the application.
+- keymap - used to add keyboard shortcuts to the application.
+- shell - a generic Jupyter front-end shell instance.
+
+Jupyter Front-End Shell
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The Jupyter front-end
+`shell `__
+is used to add and interact with content in the application. The ``IShell``
+interface provides an ``add()`` method for adding widgets to the application.
+In JupyterLab, the application shell consists of:
+
+- A ``top`` area for things like top level menus and toolbars.
+- ``left`` and ``right`` side bar areas for collapsible content.
+- A ``main`` work area for user activity.
+- A ``bottom`` area for things like status bars.
+- A ``header`` area for custom elements.
+
+Phosphor
+~~~~~~~~
+
+The Phosphor library is used as the underlying architecture of
+JupyterLab and provides many of the low level primitives and widget
+structure used in the application. Phosphor provides a rich set of
+widgets for developing desktop-like applications in the browser, as well
+as patterns and objects for writing clean, well-abstracted code. The
+widgets in the application are primarily **Phosphor widgets**, and
+Phosphor concepts, like message passing and signals, are used
+throughout. **Phosphor messages** are a *many-to-one* interaction that
+enables information like resize events to flow through the widget
+hierarchy in the application. **Phosphor signals** are a *one-to-many*
+interaction that enable listeners to react to changes in an observed
+object.
+
+Extension Authoring
+~~~~~~~~~~~~~~~~~~~
+
+An Extension is a valid `npm
+package `__ that
+meets the following criteria:
+
+- Exports one or more JupyterLab plugins as the default export in its
+ main file.
+- Has a ``jupyterlab`` key in its ``package.json`` which has
+ ``"extension"`` metadata. The value can be ``true`` to use the main
+ module of the package, or a string path to a specific module (e.g.
+ ``"lib/foo"``).
+- It is also recommended to include the keyword ``jupyterlab-extension``
+ in the ``package.json``, to aid with discovery (e.g. by the extension
+ manager).
+
+While authoring the extension, you can use the command:
+
+.. code:: bash
+
+ npm install # install npm package dependencies
+ npm run build # optional build step if using TypeScript, babel, etc.
+ jupyter labextension install # install the current directory as an extension
+
+This causes the builder to re-install the source folder before building
+the application files. You can re-build at any time using
+``jupyter lab build`` and it will reinstall these packages. You can also
+link other local npm packages that you are working on simultaneously
+using ``jupyter labextension link``; they will be re-installed but not
+considered as extensions. Local extensions and linked packages are
+included in ``jupyter labextension list``.
+
+When using local extensions and linked packages, you can run the command
+
+::
+
+ jupyter lab --watch
+
+This will cause the application to incrementally rebuild when one of the
+linked packages changes. Note that only compiled JavaScript files (and
+the CSS files) are watched by the WebPack process. This means that if
+your extension is in TypeScript you'll have to run a ``jlpm run build``
+before the changes will be reflected in JupyterLab. To avoid this step
+you can also watch the TypeScript sources in your extension which is
+usually assigned to the ``tsc -w`` shortcut. If WebPack doesn't seem to
+detect the changes, this can be related to `the number of available watches `__.
+
+Note that the application is built against **released** versions of the
+core JupyterLab extensions. If your extension depends on JupyterLab
+packages, it should be compatible with the dependencies in the
+``jupyterlab/static/package.json`` file. Note that building will always use the latest JavaScript packages that meet the dependency requirements of JupyterLab itself and any installed extensions. If you wish to test against a
+specific patch release of one of the core JupyterLab packages you can
+temporarily pin that requirement to a specific version in your own
+dependencies.
+
+If you must install a extension into a development branch of JupyterLab, you have to graft it into the source tree of JupyterLab itself. This may be done using the command
+
+::
+
+ jlpm run add:sibling
+
+in the JupyterLab root directory, where ```` refers either
+to an extension npm package on the local file system, or a URL to a git
+repository for an extension npm package. This operation may be
+subsequently reversed by running
+
+::
+
+ jlpm run remove:package
+
+This will remove the package metadata from the source tree, but will
+**not** remove any files added by the ``addsibling`` script, which
+should be removed manually.
+
+The package should export EMCAScript 5 compatible JavaScript. It can
+import CSS using the syntax ``require('foo.css')``. The CSS files can
+also import CSS from other packages using the syntax
+``@import url('~foo/index.css')``, where ``foo`` is the name of the
+package.
+
+The following file types are also supported (both in JavaScript and
+CSS): ``json``, ``html``, ``jpg``, ``png``, ``gif``, ``svg``,
+``js.map``, ``woff2``, ``ttf``, ``eot``.
+
+If your package uses any other file type it must be converted to one of
+the above types or `include a loader in the import statement `__.
+If you include a loader, the loader must be importable at build time, so if
+it is not already installed by JupyterLab, you must add it as a dependency
+of your extension.
+
+If your JavaScript is written in any other dialect than
+EMCAScript 6 (2015) it should be converted using an appropriate tool.
+You can use Webpack to pre-build your extension to use any of it's features
+not enabled in our build config. To build a compatible package set
+``output.libraryTarget`` to ``"commonjs2"`` in your Webpack config.
+(see `this `__ example repo).
+
+If you publish your extension on ``npm.org``, users will be able to install
+it as simply ``jupyter labextension install ``, where ```` is
+the name of the published npm package. You can alternatively provide a
+script that runs ``jupyter labextension install`` against a local folder
+path on the user's machine or a provided tarball. Any valid
+``npm install`` specifier can be used in
+``jupyter labextension install`` (e.g. ``foo@latest``, ``bar@3.0.0.0``,
+``path/to/folder``, and ``path/to/tar.gz``).
+
+There are a number of helper functions in ``testutils`` in this repo (which
+is a public npm package called ``@jupyterlab/testutils``) that can be used when
+writing tests for an extension. See ``tests/test-application`` for an example
+of the infrastructure needed to run tests. There is a ``karma`` config file
+that points to the parent directory's ``karma`` config, and a test runner,
+``run-test.py`` that starts a Jupyter server.
+
+
+Mime Renderer Extensions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Mime Renderer extensions are a convenience for creating an extension
+that can render mime data and potentially render files of a given type.
+We provide cookiecutters for Mime render extensions in
+`JavaScript `__ and
+`TypeScript `__.
+
+Mime renderer extensions are more declarative than standard extensions.
+The extension is treated the same from the command line perspective
+(``jupyter labextension install`` ), but it does not directly create
+JupyterLab plugins. Instead it exports an interface given in the
+`rendermime-interfaces `__
+package.
+
+The JupyterLab repo has an example mime renderer extension for
+`pdf `__
+files. It provides a mime renderer for pdf data and registers itself as
+a document renderer for pdf file types.
+
+The ``rendermime-interfaces`` package is intended to be the only
+JupyterLab package needed to create a mime renderer extension (using the
+interfaces in TypeScript or as a form of documentation if using plain
+JavaScript).
+
+The only other difference from a standard extension is that has a
+``jupyterlab`` key in its ``package.json`` with ``"mimeExtension"``
+metadata. The value can be ``true`` to use the main module of the
+package, or a string path to a specific module (e.g. ``"lib/foo"``).
+
+The mime renderer can update its data by calling ``.setData()`` on the
+model it is given to render. This can be used for example to add a
+``png`` representation of a dynamic figure, which will be picked up by a
+notebook model and added to the notebook document. When using
+``IDocumentWidgetFactoryOptions``, you can update the document model by
+calling ``.setData()`` with updated data for the rendered MIME type. The
+document can then be saved by the user in the usual manner.
+
+Themes
+~~~~~~
+
+A theme is a JupyterLab extension that uses a ``ThemeManager`` and can
+be loaded and unloaded dynamically. The package must include all static
+assets that are referenced by ``url()`` in its CSS files. Local URLs can
+be used to reference files relative to the location of the referring sibling CSS files. For example ``url('images/foo.png')`` or
+``url('../foo/bar.css')``\ can be used to refer local files in the
+theme. Absolute URLs (starting with a ``/``) or external URLs (e.g.
+``https:``) can be used to refer to external assets. The path to the
+theme asset entry point is specified ``package.json`` under the ``"jupyterlab"``
+key as ``"themePath"``. See the `JupyterLab Light
+Theme `__
+for an example. Ensure that the theme files are included in the
+``"files"`` metadata in package.json. Note that if you want to use SCSS, SASS, or LESS files,
+you must compile them to CSS and point JupyterLab to the CSS files.
+
+To quickly create a theme based on the JupyterLab Light Theme, follow
+the instructions in the `contributing
+guide `__ and
+then run ``jlpm run create:theme`` from the repository root directory.
+Once you select a name, title and a description, a new theme folder will
+be created in the current directory. You can move that new folder to a
+location of your choice, and start making desired changes.
+
+The theme extension is installed in the same way as a regular extension (see
+`extension authoring <#extension-authoring>`__).
+
+Standard (General-Purpose) Extensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JupyterLab's modular architecture is based around the idea
+that all extensions are on equal footing, and that they interact
+with each other through typed interfaces that are provided by ``Token`` objects.
+An extension can provide a ``Token`` to the application,
+which other extensions can then request for their own use.
+
+Core Tokens
+^^^^^^^^^^^
+
+The core packages of JupyterLab provide a set of tokens,
+which are listed here, along with short descriptions of when you
+might want to use them in your extensions.
+
+- ``@jupyterlab/application:ILayoutRestorer``: An interface to the application layout
+ restoration functionality. Use this to have your activities restored across
+ page loads.
+- ``@jupyterlab/application:IMimeDocumentTracker``: An instance tracker for documents
+ rendered using a mime renderer extension. Use this if you want to list and interact
+ with documents rendered by such extensions.
+- ``@jupyterlab/application:IRouter``: The URL router used by the application.
+ Use this to add custom URL-routing for your extension (e.g., to invoke
+ a command if the user navigates to a sub-path).
+- ``@jupyterlab/apputils:ICommandPalette``: An interface to the application command palette
+ in the left panel. Use this to add commands to the palette.
+- ``@jupyterlab/apputils:ISplashScreen``: An interface to the splash screen for the application.
+ Use this if you want to show the splash screen for your own purposes.
+- ``@jupyterlab/apputils:IThemeManager``: An interface to the theme manager for the application.
+ Most extensions will not need to use this, as they can register a
+ `theme extension <#themes>`__.
+- ``@jupyterlab/apputils:IWindowResolver``: An interface to a window resolver for the
+ application. JupyterLab workspaces are given a name, which are determined using
+ the window resolver. Require this if you want to use the name of the current workspace.
+- ``@jupyterlab/codeeditor:IEditorServices``: An interface to the text editor provider
+ for the application. Use this to create new text editors and host them in your
+ UI elements.
+- ``@jupyterlab/completer:ICompletionManager``: An interface to the completion manager
+ for the application. Use this to allow your extension to invoke a completer.
+- ``@jupyterlab/console:IConsoleTracker``: An instance tracker for code consoles.
+ Use this if you want to be able to iterate over and interact with code consoles
+ created by the application.
+- ``@jupyterlab/console:IContentFactory``: A factory object that creates new code
+ consoles. Use this if you want to create and host code consoles in your own UI elements.
+- ``@jupyterlab/coreutils:ISettingRegistry``: An interface to the JupyterLab settings system.
+ Use this if you want to store settings for your application.
+ See `extension settings <#extension-settings>`__ for more information.
+- ``@jupyterlab/coreutils:IStateDB``: An interface to the JupyterLab state database.
+ Use this if you want to store data that will persist across page loads.
+ See `state database <#state-database>`__ for more information.
+- ``@jupyterlab/docmanager:IDocumentManager``: An interface to the manager for all
+ documents used by the application. Use this if you want to open and close documents,
+ create and delete files, and otherwise interact with the file system.
+- ``@jupyterlab/filebrowser:IFileBrowserFactory``: A factory object that creates file browsers.
+ Use this if you want to create your own file browser (e.g., for a custom storage backend),
+ or to interact with other file browsers that have been created by extensions.
+- ``@jupyterlab/fileeditor:IEditorTracker``: An instance tracker for file editors.
+ Use this if you want to be able to iterate over and interact with file editors
+ created by the application.
+- ``@jupyterlab/imageviewer:IImageTracker``: An instance tracker for images.
+ Use this if you want to be able to iterate over and interact with images
+ viewed by the application.
+- ``@jupyterlab/inspector:IInspector``: An interface for adding variable inspectors to widgets.
+ Use this to add the ability to hook into the variable inspector to your extension.
+- ``@jupyterlab/launcher:ILauncher``: An interface to the application activity launcher.
+ Use this to add your extension activities to the launcher panel.
+- ``@jupyterlab/mainmenu:IMainMenu``: An interface to the main menu bar for the application.
+ Use this if you want to add your own menu items.
+- ``@jupyterlab/notebook:ICellTools``: An interface to the ``Cell Tools`` panel in the
+ application left area. Use this to add your own functionality to the panel.
+- ``@jupyterlab/notebook:IContentFactory``: A factory object that creates new notebooks.
+ Use this if you want to create and host notebooks in your own UI elements.
+- ``@jupyterlab/notebook:INotebookTracker``: An instance tracker for code consoles.
+ Use this if you want to be able to iterate over and interact with notebooks
+ created by the application.
+- ``@jupyterlab/rendermime:IRenderMimeRegistry``: An interface to the rendermime registry
+ for the application. Use this to create renderers for various mime-types in your extension.
+ Most extensions will not need to use this, as they can register a
+ `mime renderer extension <#mime-renderer-extensions>`__.
+- ``@jupyterlab/rendermime:ILatexTypesetter``: An interface to the LaTeX typesetter for the
+ application. Use this if you want to typeset math in your extension.
+- ``@jupyterlab/settingeditor:ISettingEditorTracker``: An instance tracker for setting editors.
+ Use this if you want to be able to iterate over and interact with setting editors
+ created by the application.
+- ``@jupyterlab/terminal:ITerminalTracker``: An instance tracker for terminals.
+ Use this if you want to be able to iterate over and interact with terminals
+ created by the application.
+- ``@jupyterlab/tooltip:ITooltipManager``: An interface to the tooltip manager for the application.
+ Use this to allow your extension to invoke a tooltip.
+
+Standard Extension Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For a concrete example of a standard extension, see :ref:`How to extend the Notebook plugin `.
+Notice that the mime renderer and themes extensions above use a limited,
+simplified interface to JupyterLab's extension system. Modifying the
+notebook plugin requires the full, general-purpose interface to the
+extension system.
+
+Storing Extension Data
+^^^^^^^^^^^^^^^^^^^^^^
+
+In addition to the file system that is accessed by using the
+``@jupyterlab/services`` package, JupyterLab offers two ways for
+extensions to store data: a client-side state database that is built on
+top of ``localStorage`` and a plugin settings system that provides for
+default setting values and user overrides.
+
+
+Extension Settings
+``````````````````
+
+An extension can specify user settings using a JSON Schema. The schema
+definition should be in a file that resides in the ``schemaDir``
+directory that is specified in the ``package.json`` file of the
+extension. The actual file name should use is the part that follows the
+package name of extension. So for example, the JupyterLab
+``apputils-extension`` package hosts several plugins:
+
+- ``'@jupyterlab/apputils-extension:menu'``
+- ``'@jupyterlab/apputils-extension:palette'``
+- ``'@jupyterlab/apputils-extension:settings'``
+- ``'@jupyterlab/apputils-extension:themes'``
+
+And in the ``package.json`` for ``@jupyterlab/apputils-extension``, the
+``schemaDir`` field is a directory called ``schema``. Since the
+``themes`` plugin requires a JSON schema, its schema file location is:
+``schema/themes.json``. The plugin's name is used to automatically
+associate it with its settings file, so this naming convention is
+important. Ensure that the schema files are included in the ``"files"``
+metadata in ``package.json``.
+
+See the
+`fileeditor-extension `__
+for another example of an extension that uses settings.
+
+Note: You can override default values of the extension settings by
+defining new default values in an ``overrides.json`` file in the
+application settings directory. So for example, if you would like
+to set the dark theme by default instead of the light one, an
+``overrides.json`` file containing the following lines needs to be
+added in the application settings directory (by default this is the
+``share/jupyter/lab/settings`` folder).
+
+.. code:: json
+
+ {
+ "@jupyterlab/apputils-extension:themes": {
+ "theme": "JupyterLab Dark"
+ }
+ }
+
+State Database
+``````````````
+
+The state database can be accessed by importing ``IStateDB`` from
+``@jupyterlab/coreutils`` and adding it to the list of ``requires`` for
+a plugin:
+
+.. code:: typescript
+
+ const id = 'foo-extension:IFoo';
+
+ const IFoo = new Token(id);
+
+ interface IFoo {}
+
+ class Foo implements IFoo {}
+
+ const plugin: JupyterFrontEndPlugin = {
+ id,
+ requires: [IStateDB],
+ provides: IFoo,
+ activate: (app: JupyterFrontEnd, state: IStateDB): IFoo => {
+ const foo = new Foo();
+ const key = `${id}:some-attribute`;
+
+ // Load the saved plugin state and apply it once the app
+ // has finished restoring its former layout.
+ Promise.all([state.fetch(key), app.restored])
+ .then(([saved]) => { /* Update `foo` with `saved`. */ });
+
+ // Fulfill the plugin contract by returning an `IFoo`.
+ return foo;
+ },
+ autoStart: true
+ };
+
+Context Menus
+^^^^^^^^^^^^^
+
+JupyterLab has an application-wide context menu available as
+``app.contextMenu``. See the Phosphor
+`docs `__
+for the item creation options. If you wish to preempt the
+application context menu, you can use a 'contextmenu' event listener and
+call ``event.stopPropagation`` to prevent the application context menu
+handler from being called (it is listening in the bubble phase on the
+``document``). At this point you could show your own Phosphor
+`contextMenu `__,
+or simply stop propagation and let the system context menu be shown.
+This would look something like the following in a ``Widget`` subclass:
+
+.. code:: javascript
+
+ // In `onAfterAttach()`
+ this.node.addEventListener('contextmenu', this);
+
+ // In `handleEvent()`
+ case 'contextmenu':
+ event.stopPropagation();
+
+.. |dependencies| image:: dependency-graph.svg
+
+
+
+.. _ext-author-companion-packages:
+
+Companion Packages
+^^^^^^^^^^^^^^^^^^
+
+If your extensions depends on the presence of one or more packages in the
+kernel, or on a notebook server extension, you can add metadata to indicate
+this to the extension manager by adding metadata to your package.json file.
+The full options available are::
+
+ "jupyterlab": {
+ "discovery": {
+ "kernel": [
+ {
+ "kernel_spec": {
+ "language": "",
+ "display_name": "" // optional
+ },
+ "base": {
+ "name": ""
+ },
+ "overrides": { // optional
+ "": {
+ "name": ""
+ }
+ },
+ "managers": [ // list of package managers that have your kernel package
+ "pip",
+ "conda"
+ ]
+ }
+ ],
+ "server": {
+ "base": {
+ "name": ""
+ },
+ "overrides": { // optional
+ "": {
+ "name": ""
+ }
+ },
+ "managers": [ // list of package managers that have your server extension package
+ "pip",
+ "conda"
+ ]
+ }
+ }
+ }
+
+
+A typical setup for e.g. a jupyter-widget based package will then be::
+
+ "keywords": [
+ "jupyterlab-extension",
+ "jupyter",
+ "widgets",
+ "jupyterlab"
+ ],
+ "jupyterlab": {
+ "extension": true,
+ "discovery": {
+ "kernel": [
+ {
+ "kernel_spec": {
+ "language": "^python",
+ },
+ "base": {
+ "name": "myipywidgetspackage"
+ },
+ "managers": [
+ "pip",
+ "conda"
+ ]
+ }
+ ]
+ }
+ }
+
+
+Currently supported package managers are:
+
+- ``pip``
+- ``conda``
diff --git a/docs/source/developer/filebrowser_example.png b/docs/source/developer/filebrowser_example.png
new file mode 100644
index 00000000..4f87a48f
Binary files /dev/null and b/docs/source/developer/filebrowser_example.png differ
diff --git a/docs/source/developer/filebrowser_source.png b/docs/source/developer/filebrowser_source.png
new file mode 100644
index 00000000..b4f77352
Binary files /dev/null and b/docs/source/developer/filebrowser_source.png differ
diff --git a/docs/source/developer/notebook.rst b/docs/source/developer/notebook.rst
new file mode 100644
index 00000000..6cc8afa7
--- /dev/null
+++ b/docs/source/developer/notebook.rst
@@ -0,0 +1,284 @@
+Notebook
+--------
+
+Background
+~~~~~~~~~~
+
+.. _architecture-walkthrough:
+
+A JupyterLab architecture walkthrough from June 16, 2016, provides an overview of the notebook architecture.
+
+.. raw:: html
+
+
+
+
+
+
+The most complicated plugin included in the **JupyterLab application**
+is the **Notebook plugin**.
+
+The
+`NotebookWidgetFactory `__
+constructs a new
+`NotebookPanel `__
+from a model and populates the toolbar with default widgets.
+
+Structure of the Notebook plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Notebook plugin provides a model and widgets for dealing with
+notebook files.
+
+Model
+^^^^^
+
+The
+`NotebookModel `__
+contains an observable list of cells.
+
+A `cell
+model `__
+can be:
+
+- a code cell
+- a markdown cell
+- raw cell
+
+A code cell contains a list of **output models**. The list of cells and
+the list of outputs can be observed for changes.
+
+Cell operations
+'''''''''''''''
+
+The NotebookModel cell list supports single-step operations such as
+moving, adding, or deleting cells. Compound cell list operations, such
+as undo/redo, are also supported by the NotebookModel. Right now,
+undo/redo is only supported on cells and is not supported on notebook
+attributes, such as notebook metadata. Currently, undo/redo for
+individual cell input content is supported by the CodeMirror editor's
+undo feature. (Note: CodeMirror editor's undo does not cover cell
+metadata changes.)
+
+Metadata
+''''''''''''''''''''
+
+The notebook model and the cell model (i.e. notebook cells) support
+getting and setting metadata through an
+`IObservableJSON `__
+object. You can use this to get and set notebook/cell metadata,
+as well as subscribe to changes to it.
+
+Notebook widget
+^^^^^^^^^^^^^^^
+
+After the NotebookModel is created, the NotebookWidgetFactory constructs
+a new NotebookPanel from the model. The NotebookPanel widget is added to
+the DockPanel. The **NotebookPanel** contains:
+
+- a
+ `Toolbar `__
+- a `Notebook
+ widget `__.
+
+The NotebookPanel also adds completion logic.
+
+The **NotebookToolbar** maintains a list of widgets to add to the
+toolbar. The **Notebook widget** contains the rendering of the notebook
+and handles most of the interaction logic with the notebook itself (such
+as keeping track of interactions such as selected and active cells and
+also the current edit/command mode).
+
+The NotebookModel cell list provides ways to do fine-grained changes to
+the cell list.
+
+Higher level actions using NotebookActions
+''''''''''''''''''''''''''''''''''''''''''
+
+Higher-level actions are contained in the
+`NotebookActions `__
+namespace, which has functions, when given a notebook widget, to run a
+cell and select the next cell, merge or split cells at the cursor,
+delete selected cells, etc.
+
+Widget hierarchy
+''''''''''''''''
+
+A Notebook widget contains a list of `cell
+widgets `__,
+corresponding to the cell models in its cell list.
+
+- Each cell widget contains an
+ `InputArea `__,
+
+ - which contains n
+ `CodeEditorWrapper `__,
+
+ - which contains a JavaScript CodeMirror instance.
+
+A
+`CodeCell `__
+also contains an
+`OutputArea `__.
+An OutputArea is responsible for rendering the outputs in the
+`OutputAreaModel `__
+list. An OutputArea uses a notebook-specific
+`RenderMimeRegistry `__
+object to render ``display_data`` output messages.
+
+Rendering output messages
+'''''''''''''''''''''''''
+
+A **Rendermime plugin** provides a pluggable system for rendering output
+messages. Default renderers are provided for markdown, html, images,
+text, etc. Extensions can register renderers to be used across the
+entire application by registering a handler and mimetype in the
+rendermime registry. When a notebook is created, it copies the global
+Rendermime singleton so that notebook-specific renderers can be added.
+The ipywidgets widget manager is an example of an extension that adds a
+notebook-specific renderer, since rendering a widget depends on
+notebook-specific widget state.
+
+.. _extend-notebook-plugin:
+
+How to extend the Notebook plugin
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We'll walk through two notebook extensions:
+
+- adding a button to the toolbar
+- adding an ipywidgets extension
+
+Adding a button to the toolbar
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Start from the cookie cutter extension template.
+
+::
+
+ pip install cookiecutter
+ cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
+ cd my-cookie-cutter-name
+
+Install the dependencies. Note that extensions are built against the
+released npm packages, not the development versions.
+
+::
+
+ npm install --save @jupyterlab/notebook @jupyterlab/application @jupyterlab/apputils @jupyterlab/docregistry @phosphor/disposable
+
+Copy the following to ``src/index.ts``:
+
+.. code:: typescript
+
+ import {
+ IDisposable, DisposableDelegate
+ } from '@phosphor/disposable';
+
+ import {
+ JupyterFrontEnd, JupyterFrontEndPlugin
+ } from '@jupyterlab/application';
+
+ import {
+ ToolbarButton
+ } from '@jupyterlab/apputils';
+
+ import {
+ DocumentRegistry
+ } from '@jupyterlab/docregistry';
+
+ import {
+ NotebookActions, NotebookPanel, INotebookModel
+ } from '@jupyterlab/notebook';
+
+
+ /**
+ * The plugin registration information.
+ */
+ const plugin: JupyterFrontEndPlugin = {
+ activate,
+ id: 'my-extension-name:buttonPlugin',
+ autoStart: true
+ };
+
+
+ /**
+ * A notebook widget extension that adds a button to the toolbar.
+ */
+ export
+ class ButtonExtension implements DocumentRegistry.IWidgetExtension {
+ /**
+ * Create a new extension object.
+ */
+ createNew(panel: NotebookPanel, context: DocumentRegistry.IContext): IDisposable {
+ let callback = () => {
+ NotebookActions.runAll(panel.content, context.session);
+ };
+ let button = new ToolbarButton({
+ className: 'myButton',
+ iconClassName: 'fa fa-fast-forward',
+ onClick: callback,
+ tooltip: 'Run All'
+ });
+
+ panel.toolbar.insertItem(0, 'runAll', button);
+ return new DisposableDelegate(() => {
+ button.dispose();
+ });
+ }
+ }
+
+ /**
+ * Activate the extension.
+ */
+ function activate(app: JupyterFrontEnd) {
+ app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
+ };
+
+
+ /**
+ * Export the plugin as default.
+ */
+ export default plugin;
+
+Run the following commands:
+
+::
+
+ npm install
+ npm run build
+ jupyter labextension install .
+ jupyter lab
+
+Open a notebook and observe the new "Run All" button.
+
+The *ipywidgets* third party extension
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This discussion will be a bit confusing since we've been using the term
+*widget* to refer to *phosphor widgets*. In the discussion below,
+*ipython widgets* will be referred to as *ipywidgets*. There is no
+intrinsic relation between *phosphor widgets* and *ipython widgets*.
+
+The *ipywidgets* extension registers a factory for a notebook *widget*
+extension using the `Document
+Registry `__.
+The ``createNew()`` function is called with a NotebookPanel and
+`DocumentContext `__.
+The plugin then creates a ipywidget manager (which uses the context to
+interact the kernel and kernel's comm manager). The plugin then
+registers an ipywidget renderer with the notebook instance's rendermime
+(which is specific to that particular notebook).
+
+When an ipywidget model is created in the kernel, a comm message is sent
+to the browser and handled by the ipywidget manager to create a
+browser-side ipywidget model. When the model is displayed in the kernel,
+a ``display_data`` output is sent to the browser with the ipywidget
+model id. The renderer registered in that notebook's rendermime is asked
+to render the output. The renderer asks the ipywidget manager instance
+to render the corresponding model, which returns a JavaScript promise.
+The renderer creates a container *phosphor widget* which it hands back
+synchronously to the OutputArea, and then fills the container with the
+rendered *ipywidget* when the promise resolves.
+
+Note: The ipywidgets third party extension has not yet been released.
diff --git a/docs/source/developer/patterns.rst b/docs/source/developer/patterns.rst
new file mode 100644
index 00000000..88a3d784
--- /dev/null
+++ b/docs/source/developer/patterns.rst
@@ -0,0 +1,173 @@
+Design Patterns
+---------------
+
+There are several design patterns that are repeated throughout the
+repository. This guide is meant to supplement the `TypeScript Style
+Guide `__.
+
+TypeScript
+~~~~~~~~~~
+
+TypeScript is used in all of the source code. TypeScript is used because
+it provides features from the most recent EMCAScript 6 standards, while
+providing type safety. The TypeScript compiler eliminates an entire
+class of bugs, while making it much easier to refactor code.
+
+Initialization Options
+~~~~~~~~~~~~~~~~~~~~~~
+
+Objects will typically have an ``IOptions`` interface for initializing
+the widget. The use of this interface enables options to be later added
+while preserving backward compatibility.
+
+ContentFactory Option
+~~~~~~~~~~~~~~~~~~~~~
+
+| A common option for a widget is a ``IContentFactory``, which is used
+ to customize the child content in the widget.
+| If not given, a ``defaultRenderer`` instance is used if no arguments
+ are required. In this way, widgets can be customized without
+ subclassing them, and widgets can support customization of their
+ nested content.
+
+Static Namespace
+~~~~~~~~~~~~~~~~
+
+An object class will typically have an exported static namespace sharing
+the same name as the object. The namespace is used to declutter the
+class definition.
+
+Private Module Namespace
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The "Private" module namespace is used to group variables and functions
+that are not intended to be exported and may have otherwise existed as
+module-level variables and functions. The use of the namespace also
+makes it clear when a variable access is to an imported name or from the
+module itself. Finally, the namespace enables the entire section to be
+collapsed in an editor if desired.
+
+Disposables
+~~~~~~~~~~~
+
+| JavaScript does not support "destructors", so the ``IDisposable``
+ pattern is used to ensure resources are freed and can be claimed by
+ the Garbage Collector when no longer needed. It should always be safe
+ to ``dispose()`` of an object more than once. Typically the object
+ that creates another object is responsible for calling the dispose
+ method of that object unless explicitly stated otherwise.
+| To mirror the pattern of construction, ``super.dispose()`` should be
+ called last in the ``dispose()`` method if there is a parent class.
+ Make sure any signal connections are cleared in either the local or
+ parent ``dispose()`` method. Use a sentinel value to guard against
+ reentry, typically by checking if an internal value is null, and then
+ immediately setting the value to null. A subclass should never
+ override the ``isDisposed`` getter, because it short-circuits the
+ parent class getter. The object should not be considered disposed
+ until the base class ``dispose()`` method is called.
+
+Messages
+~~~~~~~~
+
+Messages are intended for many-to-one communication where outside
+objects influence another object. Messages can be conflated and
+processed as a single message. They can be posted and handled on the
+next animation frame.
+
+Signals
+~~~~~~~
+
+Signals are intended for one-to-many communication where outside objects
+react to changes on another object. Signals are always emitted with the
+sender as the first argument, and contain a single second argument with
+the payload. Signals should generally not be used to trigger the
+"default" behavior for an action, but to enable others to trigger
+additional behavior. If a "default" behavior is intended to be provided
+by another object, then a callback should be provided by that object.
+Wherever possible as signal connection should be made with the pattern
+``.connect(this._onFoo, this)``. Providing the ``this`` context enables
+the connection to be properly cleared by ``clearSignalData(this)``.
+Using a private method avoids allocating a closure for each connection.
+
+Models
+~~~~~~
+
+Some of the more advanced widgets have a model associated with them. The
+common pattern used is that the model is settable and must be set
+outside of the constructor. This means that any consumer of the widget
+must account for a model that may be ``null``, and may change at any
+time. The widget should emit a ``modelChanged`` signal to enable
+consumers to handle a change in model. The reason to enable a model to
+swap is that the same widget could be used to display different model
+content while preserving the widget's location in the application. The
+reason the model cannot be provided in the constructor is the
+initialization required for a model may have to call methods that are
+subclassed. The subclassed methods would be called before the subclass
+constructor has finished evaluating, resulting in undefined state.
+
+.. _getters-vs-methods:
+
+Getters vs. Methods
+~~~~~~~~~~~~~~~~~~~
+
+Prefer a method when the return value must be computed each time. Prefer
+a getter for simple attribute lookup. A getter should yield the same
+value every time.
+
+Data Structures
+~~~~~~~~~~~~~~~
+
+For public API, we have three options: JavaScript ``Array``,
+``IIterator``, and ``ReadonlyArray`` (an interface defined by
+TypeScript).
+
+Prefer an ``Array`` for:
+
+- A value that is meant to be mutable.
+
+Prefer a ``ReadonlyArray``
+
+- A return value is the result of a newly allocated array, to avoid the
+ extra allocation of an iterator.
+- A signal payload - since it will be consumed by multiple listeners.
+- The values may need to be accessed randomly.
+- A public attribute that is inherently static.
+
+Prefer an ``IIterator`` for:
+
+- A return value where the value is based on an internal data structure
+ but the value should not need to be accessed randomly.
+- A set of return values that can be computed lazily.
+
+DOM Events
+~~~~~~~~~~
+
+If an object instance should respond to DOM events, create a
+``handleEvent`` method for the class and register the object instance as
+the event handler. The ``handleEvent`` method should switch on the event
+type and could call private methods to carry out the actions. Often a
+widget class will add itself as an event listener to its own node in the
+``onAfterAttach`` method with something like
+``this.node.addEventListener('mousedown', this)`` and unregister itself
+in the ``onBeforeDetach`` method with
+``this.node.removeEventListener('mousedown', this)`` Dispatching events
+from the ``handleEvent`` method makes it easier to trace, log, and debug
+event handling. For more information about the ``handleEvent`` method,
+see the
+`EventListener `__
+API.
+
+Promises
+~~~~~~~~
+
+We use Promises for asynchronous function calls, and a shim for browsers
+that do not support them. When handling a resolved or rejected Promise,
+make sure to check for the current state (typically by checking an
+``.isDisposed`` property) before proceeding.
+
+Command Names
+~~~~~~~~~~~~~
+
+Commands used in the application command registry should be formatted as
+follows: ``package-name:verb-noun``. They are typically grouped into a
+``CommandIDs`` namespace in the extension that is not exported.
diff --git a/docs/source/developer/repo.rst b/docs/source/developer/repo.rst
new file mode 100644
index 00000000..142307de
--- /dev/null
+++ b/docs/source/developer/repo.rst
@@ -0,0 +1,74 @@
+.. _developer-guide:
+
+The JupyterLab Developer Guide is for developing JupyterLab extensions or developing JupyterLab itself.
+
+General Codebase Orientation
+----------------------------
+
+The ``jupyterlab/jupyterlab`` repository contains two packages:
+
+- an npm package indicated by a ``package.json`` file in the repo's
+ root directory
+- a Python package indicated by a ``setup.py`` file in the repo's root
+ directory
+
+The npm package and the Python package are both named ``jupyterlab``.
+
+See the `Contributing
+Guidelines `__
+for developer installation instructions.
+
+Directories
+~~~~~~~~~~~
+
+NPM package: ``src/``, ``lib/``, ``typings/``, ``buildutils/``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``src/``: the source typescript files.
+
+ - ``jlpm run build`` builds the source files into javascript files
+ in ``lib/``.
+ - ``jlpm run clean`` deletes the ``lib/`` directory.
+
+- ``typings/``: type definitions for external libraries that typescript
+ needs.
+- ``buildutils/``: Utilities for managing the repo
+
+Examples: ``examples/``
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``examples/`` directory contains stand-alone examples of components,
+such as a simple notebook on a page, a console, terminal, and a
+filebrowser. The ``lab`` example illustrates a simplified combination of
+components used in JupyterLab. This example shows multiple stand-alone
+components combined to create a more complex application.
+
+Testing: ``test/``
+^^^^^^^^^^^^^^^^^^
+
+The tests are stored and run in the ``test/`` directory. The source
+files are in ``test/src/``.
+
+Notebook extension: ``jupyterlab/``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``jupyterlab/`` directory contains the Jupyter server extension.
+
+The server extension includes a private npm package in order to build
+the **webpack bundle** which the extension serves. The private npm
+package depends on the ``jupyterlab`` npm package found in the repo's
+root directory.
+
+Git hooks: ``git-hooks/``
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``git-hooks/`` directory stores some convenience git hooks that
+automatically rebuild the npm package and server extension every time
+you check out or merge (via pull request or direct push to master) in
+the git repo.
+
+Documentation: ``docs/``
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+After building the docs (``jlpm run docs``), ``docs/index.html`` is the
+entry point to the documentation.
diff --git a/docs/source/developer/terminology.rst b/docs/source/developer/terminology.rst
new file mode 100644
index 00000000..d40ecfc8
--- /dev/null
+++ b/docs/source/developer/terminology.rst
@@ -0,0 +1,20 @@
+Terminology
+-----------
+
+Learning to use a new technology and its architecture can be complicated
+by the jargon used to describe components. We provide this terminology
+guide to help smooth the learning the components.
+
+Terms
+~~~~~
+
+- Application - The main application object that hold the application
+ shell, command registry, and keymap registry. It is provided to all
+ plugins in their activate method.
+- Plugin - An object that provides a service and or extends the
+ application.
+- Phosphor - The JavaScript library that provides the foundation of
+ JupyterLab, enabling desktop-like applications in the browser.
+- Standalone example - An example in the ``examples/`` directory that
+ demonstrates the usage of one or more components of JupyterLab.
+- TypeScript - A statically typed language that compiles to JavaScript.
diff --git a/docs/source/developer/virtualdom.rst b/docs/source/developer/virtualdom.rst
new file mode 100644
index 00000000..f3167ab5
--- /dev/null
+++ b/docs/source/developer/virtualdom.rst
@@ -0,0 +1,25 @@
+Virtual DOM and React
+---------------------
+
+JupyterLab is based on `PhosphorJS `__,
+which provides a flexible ``Widget`` class that handles the following:
+
+- Resize events that propagate down the Widget hierarchy.
+- Lifecycle events (``onBeforeDetach``, ``onAfterAttach``, etc.).
+- Both CSS-based and absolutely positioned layouts.
+
+In situations where these features are needed, we recommend using
+Phosphor's ``Widget`` class directly.
+
+The idea of virtual DOM rendering, which became popular in the
+`React `__ community, is a very elegant and
+efficient way of rendering and updating DOM content in response to
+model/state changes.
+
+Phosphor's ``Widget`` class integrates well with ReactJS and we are now
+using React in JupyterLab to render leaf content when the above
+capabilities are not needed.
+
+An example of using React with Phosphor can be found in the
+`launcher `__
+of JupyterLab.
diff --git a/docs/source/developer/xkcd_extension_tutorial.rst b/docs/source/developer/xkcd_extension_tutorial.rst
new file mode 100644
index 00000000..3cdbbb62
--- /dev/null
+++ b/docs/source/developer/xkcd_extension_tutorial.rst
@@ -0,0 +1,866 @@
+.. _xkcd_extension_tutorial:
+
+Let's Make an xkcd JupyterLab Extension
+---------------------------------------
+
+.. warning::
+
+ The extension developer API is not stable and will evolve in JupyterLab
+ releases in the near future.
+
+JupyterLab extensions add features to the user experience. This page
+describes how to create one type of extension, an *application plugin*,
+that:
+
+- Adds a "Random `xkcd `__ comic" command to the
+ *command palette* sidebar
+- Fetches the comic image and metadata when activated
+- Shows the image and metadata in a tab panel
+
+By working through this tutorial, you'll learn:
+
+- How to setup an extension development environment from scratch on a
+ Linux or OSX machine.
+
+ - Windows users: You'll need to modify the commands slightly.
+
+- How to start an extension project from
+ `jupyterlab/extension-cookiecutter-ts `__
+- How to iteratively code, build, and load your extension in JupyterLab
+- How to version control your work with git
+- How to release your extension for others to enjoy
+
+|Completed xkcd extension screenshot|
+
+Sound like fun? Excellent. Here we go!
+
+Setup a development environment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Install conda using miniconda
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Start by installing miniconda, following
+`Conda's installation documentation `__.
+
+.. _install-nodejs-jupyterlab-etc-in-a-conda-environment:
+
+Install NodeJS, JupyterLab, etc. in a conda environment
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Next create a conda environment that includes:
+
+1. the latest release of JupyterLab
+2. `cookiecutter `__, the tool
+ you'll use to bootstrap your extension project structure
+3. `NodeJS `__, the JavaScript runtime you'll use to
+ compile the web assets (e.g., TypeScript, CSS) for your extension
+4. `git `__, a version control system you'll use to
+ take snapshots of your work as you progress through this tutorial
+
+It's best practice to leave the root conda environment, the one created
+by the miniconda installer, untouched and install your project specific
+dependencies in a named conda environment. Run this command to create a
+new environment named ``jupyterlab-ext``.
+
+.. code:: bash
+
+ conda create -n jupyterlab-ext -c conda-forge --override-channels nodejs jupyterlab cookiecutter git
+
+Now activate the new environment so that all further commands you run
+work out of that environment.
+
+.. code:: bash
+
+ conda activate jupyterlab-ext
+
+Note: You'll need to run the command above in each new terminal you open
+before you can work with the tools you installed in the
+``jupyterlab-ext`` environment.
+
+Create a repository
+~~~~~~~~~~~~~~~~~~~
+
+Create a new repository for your extension. For example, on
+`GitHub `__. This is an
+optional step but highly recommended if you want to share your
+extension.
+
+Create an extension project
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Initialize the project from a cookiecutter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Next use cookiecutter to create a new project for your extension.
+This will create a new folder for your extension in your current directory.
+
+.. code:: bash
+
+ cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
+
+When prompted, enter values like the following for all of the
+cookiecutter prompts.
+
+::
+
+ author_name []: Your Name
+ extension_name [myextension]: jupyterlab_xkcd
+ project_short_description [A JupyterLab extension.]: Show a random xkcd.com comic in a JupyterLab panel
+ repository [https://github.com/my_name/jupyterlab_myextension]: https://github.com/my_name/jupyterlab_xkcd
+
+Note: if not using a repository, leave the field blank. You can come
+back and edit the repository links in the ``package.json`` file later.
+
+Change to the directory the cookiecutter created and list the files.
+
+.. code:: bash
+
+ cd jupyterlab_xkcd
+ ls
+
+You should see a list like the following.
+
+::
+
+ README.md package.json src style tsconfig.json
+
+Build and install the extension for development
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Your new extension project has enough code in it to see it working in
+your JupyterLab. Run the following commands to install the initial
+project dependencies and install it in the JupyterLab environment. We
+defer building since it will be built in the next step.
+
+.. note::
+
+ This tutorial uses ``jlpm`` to install Javascript packages and
+ run build commands, which is JupyterLab's bundled
+ version of ``yarn``. If you prefer, you can use another Javascript
+ package manager like ``npm`` or ``yarn`` itself.
+
+
+.. code:: bash
+
+ jlpm install
+ jupyter labextension install . --no-build
+
+After the install completes, open a second terminal. Run these commands
+to activate the ``jupyterlab-ext`` environment and to start a JupyterLab
+instance in watch mode so that it will keep up with our changes as we
+make them.
+
+.. code:: bash
+
+ conda activate jupyterlab-ext
+ jupyter lab --watch
+
+See the initial extension in action
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+After building with your extension, JupyterLab should open in your
+default web browser.
+
+In that window open the JavaScript console
+by following the instructions for your browser:
+
+- `Accessing the DevTools in Google
+ Chrome `__
+- `Opening the Web Console in
+ Firefox `__
+
+After you reload the page with the console open, you should see a message that says
+``JupyterLab extension jupyterlab_xkcd is activated!`` in the console.
+If you do, congrats, you're ready to start modifying the the extension!
+If not, go back, make sure you didn't miss a step, and `reach
+out `__ if you're stuck.
+
+Note: Leave the terminal running the ``jupyter lab --watch`` command
+open.
+
+Commit what you have to git
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Run the following commands in your ``jupyterlab_xkcd`` folder to
+initialize it as a git repository and commit the current code.
+
+.. code:: bash
+
+ git init
+ git add .
+ git commit -m 'Seed xkcd project from cookiecutter'
+
+Note: This step is not technically necessary, but it is good practice to
+track changes in version control system in case you need to rollback to
+an earlier version or want to collaborate with others. For example, you
+can compare your work throughout this tutorial with the commits in a
+reference version of ``jupyterlab_xkcd`` on GitHub at
+https://github.com/jupyterlab/jupyterlab_xkcd.
+
+Add an xkcd widget
+~~~~~~~~~~~~~~~~~~
+
+Show an empty panel
+^^^^^^^^^^^^^^^^^^^
+
+The *command palette* is the primary view of all commands available to
+you in JupyterLab. For your first addition, you're going to add a
+*Random xkcd comic* command to the palette and get it to show an *xkcd*
+tab panel when invoked.
+
+Fire up your favorite text editor and open the ``src/index.ts`` file in
+your extension project. Add the following import at the top of the file
+to get a reference to the command palette interface.
+
+.. code:: typescript
+
+ import {
+ ICommandPalette
+ } from '@jupyterlab/apputils';
+
+You will also need to install this dependency. Run the following command in the
+repository root folder install the dependency and save it to your
+`package.json`:
+
+.. code:: bash
+
+ jlpm add @jupyterlab/apputils
+
+Locate the ``extension`` object of type ``JupyterFrontEndPlugin``. Change the
+definition so that it reads like so:
+
+.. code:: typescript
+
+ /**
+ * Initialization data for the jupyterlab_xkcd extension.
+ */
+ const extension: JupyterFrontEndPlugin = {
+ id: 'jupyterlab_xkcd',
+ autoStart: true,
+ requires: [ICommandPalette],
+ activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
+ console.log('JupyterLab extension jupyterlab_xkcd is activated!');
+ console.log('ICommandPalette:', palette);
+ }
+ };
+
+The ``requires`` attribute states that your plugin needs an object that
+implements the ``ICommandPalette`` interface when it starts. JupyterLab
+will pass an instance of ``ICommandPalette`` as the second parameter of
+``activate`` in order to satisfy this requirement. Defining
+``palette: ICommandPalette`` makes this instance available to your code
+in that function. The second ``console.log`` line exists only so that
+you can immediately check that your changes work.
+
+Run the following to rebuild your extension.
+
+.. code:: bash
+
+ jlpm run build
+
+JupyterLab will rebuild after the extension does. You can
+see it's progress in the ``jupyter lab --watch`` window. After that
+finishes, return to the browser tab that opened when you
+started JupyterLab. Refresh it and look in the console. You should see
+the same activation message as before, plus the new message about the
+ICommandPalette instance you just added. If you don't, check the output
+of the build command for errors and correct your code.
+
+::
+
+ JupyterLab extension jupyterlab_xkcd is activated!
+ ICommandPalette: Palette {_palette: CommandPalette}
+
+Note that we had to run ``jlpm run build`` in order for the bundle to
+update, because it is using the compiled JavaScript files in ``/lib``.
+If you wish to avoid running ``jlpm run build`` after each change, you
+can open a third terminal, and run the ``jlpm run watch`` command from
+your extension directory, which will automatically compile the
+TypeScript files as they change.
+
+Now return to your editor. Add the following additional import to the
+top of the file.
+
+.. code:: typescript
+
+ import {
+ Widget
+ } from '@phosphor/widgets';
+
+Install this dependency as well:
+
+.. code:: bash
+
+ jlpm add @phosphor/widgets
+
+
+Then modify the ``activate`` function again so that it has the following
+code:
+
+.. code-block:: typescript
+
+ activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
+ console.log('JupyterLab extension jupyterlab_xkcd is activated!');
+
+ // Create a single widget
+ let widget: Widget = new Widget();
+ widget.id = 'xkcd-jupyterlab';
+ widget.title.label = 'xkcd.com';
+ widget.title.closable = true;
+
+ // Add an application command
+ const command: string = 'xkcd:open';
+ app.commands.addCommand(command, {
+ label: 'Random xkcd comic',
+ execute: () => {
+ if (!widget.isAttached) {
+ // Attach the widget to the main work area if it's not there
+ app.shell.add(widget, 'main');
+ }
+ // Activate the widget
+ app.shell.activateById(widget.id);
+ }
+ });
+
+ // Add the command to the palette.
+ palette.addItem({command, category: 'Tutorial'});
+ }
+
+The first new block of code creates a ``Widget`` instance, assigns it a
+unique ID, gives it a label that will appear as its tab title, and makes
+the tab closable by the user. The second block of code add a new command
+labeled *Random xkcd comic* to JupyterLab. When the command executes,
+it attaches the widget to the main display area if it is not already
+present and then makes it the active tab. The last new line of code adds
+the command to the command palette in a section called *Tutorial*.
+
+Build your extension again using ``jlpm run build`` (unless you are using
+``jlpm run watch`` already) and refresh the browser tab. Open the command
+palette on the left side by clicking on *Commands* and type *xkcd* in
+the search box. Your *Random xkcd comic*
+command should appear. Click it or select it with the keyboard and press
+*Enter*. You should see a new, blank panel appear with the tab title
+*xkcd.com*. Click the *x* on the tab to close it and activate the
+command again. The tab should reappear. Finally, click one of the
+launcher tabs so that the *xkcd.com* panel is still open but no longer
+active. Now run the *Random xkcd comic* command one more time. The
+single *xkcd.com* tab should come to the foreground.
+
+|Empty xkcd extension panel|
+
+If your widget is not behaving, compare your code with the reference
+project state at the `01-show-a-panel
+tag `__.
+Once you've got everything working properly, git commit your changes and
+carry on.
+
+.. code-block:: bash
+
+ git add .
+ git commit -m 'Show xkcd command on panel'
+
+Show a comic in the panel
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You've got an empty panel. It's time to add a comic to it. Go back to
+your code editor. Add the following code below the lines that create a
+``Widget`` instance and above the lines that define the command.
+
+.. code-block:: typescript
+
+ // Add an image element to the panel
+ let img = document.createElement('img');
+ widget.node.appendChild(img);
+
+ // Fetch info about a random comic
+ fetch('https:////egszlpbmle.execute-api.us-east-1.amazonaws.com/prod').then(response => {
+ return response.json();
+ }).then(data => {
+ img.src = data.img;
+ img.alt = data.title;
+ img.title = data.alt;
+ });
+
+The first two lines create a new HTML ```` element and add it to
+the widget DOM node. The next lines make a request using the HTML
+`fetch `__
+API that returns information about a random xkcd comic, and set the
+image source, alternate text, and title attributes based on the
+response.
+
+Rebuild your extension if necessary (``jlpm run build``), refresh your
+browser tab, and run the *Random xkcd comic* command again. You should
+now see a comic in the xkcd.com panel when it opens.
+
+|Single xkcd extension panel|
+
+Note that the comic is not centered in the panel nor does the panel
+scroll if the comic is larger than the panel area. Also note that the
+comic does not update no matter how many times you close and reopen the
+panel. You'll address both of these problems in the upcoming sections.
+
+If you don't see a comic at all, compare your code with the
+`02-show-a-comic
+tag `__
+in the reference project. When it's working, make another git commit.
+
+.. code:: bash
+
+ git add .
+ git commit -m 'Show a comic in the panel'
+
+Improve the widget behavior
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Center the comic and add attribution
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Open ``style/index.css`` in our extension project directory for editing.
+Add the following lines to it.
+
+.. code-block:: css
+
+ .jp-xkcdWidget {
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ }
+
+ .jp-xkcdCartoon {
+ margin: auto;
+ }
+
+ .jp-xkcdAttribution {
+ margin: 20px auto;
+ }
+
+The first rule stacks content vertically within the widget panel and
+lets the panel scroll when the content overflows. The other rules center
+the cartoon and attribution badge horizontally and space them out
+vertically.
+
+Return to the ``index.ts`` file. Note that there is already an import of
+the CSS file in the ``index.ts`` file. Modify the the ``activate``
+function to apply the CSS classes and add the attribution badge markup.
+The beginning of the function should read like the following:
+
+.. code-block:: typescript
+ :emphasize-lines: 9,13,16-23
+
+ activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
+ console.log('JupyterLab extension jupyterlab_xkcd is activated!');
+
+ // Create a single widget
+ let widget: Widget = new Widget();
+ widget.id = 'xkcd-jupyterlab';
+ widget.title.label = 'xkcd.com';
+ widget.title.closable = true;
+ widget.addClass('jp-xkcdWidget'); // new line
+
+ // Add an image element to the panel
+ let img = document.createElement('img');
+ img.className = 'jp-xkcdCartoon'; // new line
+ widget.node.appendChild(img);
+
+ // New: add an attribution badge
+ img.insertAdjacentHTML('afterend',
+ `
`
+ );
+
+ // Keep all the remaining fetch and command lines the same
+ // as before from here down ...
+
+Build your extension if necessary (``jlpm run build``) and refresh your
+JupyterLab browser tab. Invoke the *Random xkcd comic* command and
+confirm the comic is centered with an attribution badge below it. Resize
+the browser window or the panel so that the comic is larger than the
+available area. Make sure you can scroll the panel over the entire area
+of the comic.
+
+|Styled xkcd panel with attribution|
+
+If anything is misbehaving, compare your code with the reference project
+`03-style-and-attribute
+tag `__.
+When everything is working as expected, make another commit.
+
+.. code:: bash
+
+ git add .
+ git commit -m 'Add styling, attribution'
+
+Show a new comic on demand
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``activate`` function has grown quite long, and there's still more
+functionality to add. You should refactor the code into two separate
+parts:
+
+1. An ``XkcdWidget`` that encapsulates the xkcd panel elements,
+ configuration, and soon-to-be-added update behavior
+2. An ``activate`` function that adds the widget instance to the UI and
+ decide when the comic should refresh
+
+Start by refactoring the widget code into the new ``XkcdWidget`` class.
+Add the following additional import to the top of the file.
+
+.. code-block:: typescript
+
+ import {
+ Message
+ } from '@phosphor/messaging';
+
+Install this dependency:
+
+.. code:: bash
+
+ jlpm add @phosphor/messaging
+
+
+Then add the class just below the import statements in the ``index.ts``
+file.
+
+.. code-block:: typescript
+
+ /**
+ * An xckd comic viewer.
+ */
+ class XkcdWidget extends Widget {
+ /**
+ * Construct a new xkcd widget.
+ */
+ constructor() {
+ super();
+
+ this.id = 'xkcd-jupyterlab';
+ this.title.label = 'xkcd.com';
+ this.title.closable = true;
+ this.addClass('jp-xkcdWidget');
+
+ this.img = document.createElement('img');
+ this.img.className = 'jp-xkcdCartoon';
+ this.node.appendChild(this.img);
+
+ this.img.insertAdjacentHTML('afterend',
+ `
`
+ );
+ }
+
+ /**
+ * The image element associated with the widget.
+ */
+ readonly img: HTMLImageElement;
+
+ /**
+ * Handle update requests for the widget.
+ */
+ onUpdateRequest(msg: Message): void {
+ fetch('https://egszlpbmle.execute-api.us-east-1.amazonaws.com/prod').then(response => {
+ return response.json();
+ }).then(data => {
+ this.img.src = data.img;
+ this.img.alt = data.title;
+ this.img.title = data.alt;
+ });
+ }
+ };
+
+You've written all of the code before. All you've done is restructure it
+to use instance variables and move the comic request to its own
+function.
+
+Next move the remaining logic in ``activate`` to a new, top-level
+function just below the ``XkcdWidget`` class definition. Modify the code
+to create a widget when one does not exist in the main JupyterLab area
+or to refresh the comic in the exist widget when the command runs again.
+The code for the ``activate`` function should read as follows after
+these changes:
+
+.. code-block:: typescript
+
+ /**
+ * Activate the xckd widget extension.
+ */
+ function activate(app: JupyterFrontEnd, palette: ICommandPalette) {
+ console.log('JupyterLab extension jupyterlab_xkcd is activated!');
+
+ // Create a single widget
+ let widget: XkcdWidget = new XkcdWidget();
+
+ // Add an application command
+ const command: string = 'xkcd:open';
+ app.commands.addCommand(command, {
+ label: 'Random xkcd comic',
+ execute: () => {
+ if (!widget.isAttached) {
+ // Attach the widget to the main work area if it's not there
+ app.shell.add(widget, 'main');
+ }
+ // Refresh the comic in the widget
+ widget.update();
+ // Activate the widget
+ app.shell.activateById(widget.id);
+ }
+ });
+
+ // Add the command to the palette.
+ palette.addItem({ command, category: 'Tutorial' });
+ };
+
+Remove the ``activate`` function definition from the
+``JupyterFrontEndPlugin`` object and refer instead to the top-level function
+like so:
+
+.. code-block:: typescript
+
+ const extension: JupyterFrontEndPlugin = {
+ id: 'jupyterlab_xkcd',
+ autoStart: true,
+ requires: [ICommandPalette],
+ activate: activate
+ };
+
+Make sure you retain the ``export default extension;`` line in the file.
+Now build the extension again and refresh the JupyterLab browser tab.
+Run the *Random xkcd comic* command more than once without closing the
+panel. The comic should update each time you execute the command. Close
+the panel, run the command, and it should both reappear and show a new
+comic.
+
+If anything is amiss, compare your code with the
+`04-refactor-and-refresh
+tag `__
+to debug. Once it's working properly, commit it.
+
+.. code:: bash
+
+ git add .
+ git commit -m 'Refactor, refresh comic'
+
+Restore panel state when the browser refreshes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You may notice that every time you refresh your browser tab, the xkcd
+panel disappears, even if it was open before you refreshed. Other open
+panels, like notebooks, terminals, and text editors, all reappear and
+return to where you left them in the panel layout. You can make your
+extension behave this way too.
+
+Update the imports at the top of your ``index.ts`` file so that the
+entire list of import statements looks like the following:
+
+.. code-block:: typescript
+ :emphasize-lines: 2,6,9-11
+
+ import {
+ JupyterFrontEnd, JupyterFrontEndPlugin, ILayoutRestorer // new
+ } from '@jupyterlab/application';
+
+ import {
+ ICommandPalette, InstanceTracker // new
+ } from '@jupyterlab/apputils';
+
+ import {
+ JSONExt // new
+ } from '@phosphor/coreutils';
+
+ import {
+ Message
+ } from '@phosphor/messaging';
+
+ import {
+ Widget
+ } from '@phosphor/widgets';
+
+ import '../style/index.css';
+
+
+Install this dependency:
+
+.. code:: bash
+
+ jlpm add @phosphor/coreutils
+
+Then, add the ``ILayoutRestorer`` interface to the ``JupyterFrontEndPlugin``
+definition. This addition passes the global ``LayoutRestorer`` to the
+third parameter of the ``activate``.
+
+.. code:: typescript
+
+ const extension: JupyterFrontEndPlugin = {
+ id: 'jupyterlab_xkcd',
+ autoStart: true,
+ requires: [ICommandPalette, ILayoutRestorer],
+ activate: activate
+ };
+
+Finally, rewrite the ``activate`` function so that it:
+
+1. Declares a widget variable, but does not create an instance
+ immediately
+2. Constructs an ``InstanceTracker`` and tells the ``ILayoutRestorer``
+ to use it to save/restore panel state
+3. Creates, tracks, shows, and refreshes the widget panel appropriately
+
+.. code-block:: typescript
+
+ function activate(app: JupyterFrontEnd, palette: ICommandPalette, restorer: ILayoutRestorer) {
+ console.log('JupyterLab extension jupyterlab_xkcd is activated!');
+
+ // Declare a widget variable
+ let widget: XkcdWidget;
+
+ // Add an application command
+ const command: string = 'xkcd:open';
+ app.commands.addCommand(command, {
+ label: 'Random xkcd comic',
+ execute: () => {
+ if (!widget) {
+ // Create a new widget if one does not exist
+ widget = new XkcdWidget();
+ widget.update();
+ }
+ if (!tracker.has(widget)) {
+ // Track the state of the widget for later restoration
+ tracker.add(widget);
+ }
+ if (!widget.isAttached) {
+ // Attach the widget to the main work area if it's not there
+ app.shell.add(widget, 'main');
+ } else {
+ // Refresh the comic in the widget
+ widget.update();
+ }
+ // Activate the widget
+ app.shell.activateById(widget.id);
+ }
+ });
+
+ // Add the command to the palette.
+ palette.addItem({ command, category: 'Tutorial' });
+
+ // Track and restore the widget state
+ let tracker = new InstanceTracker({ namespace: 'xkcd' });
+ restorer.restore(tracker, {
+ command,
+ args: () => JSONExt.emptyObject,
+ name: () => 'xkcd'
+ });
+ };
+
+Rebuild your extension one last time and refresh your browser tab.
+Execute the *Random xkcd comic* command and validate that the panel
+appears with a comic in it. Refresh the browser tab again. You should
+see an xkcd panel appear immediately without running the command. Close
+the panel and refresh the browser tab. You should not see an xkcd tab
+after the refresh.
+
+Refer to the `05-restore-panel-state
+tag `__
+if your extension is misbehaving. Make a commit when the state of your
+extension persists properly.
+
+.. code:: bash
+
+ git add .
+ git commit -m 'Restore panel state'
+
+Congrats! You've implemented all of the behaviors laid out at the start
+of this tutorial. Now how about sharing it with the world?
+
+.. _publish-your-extension-to-npmjsorg:
+
+Publish your extension to npmjs.org
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+npm is both a JavaScript package manager and the de facto registry for
+JavaScript software. You can `sign up for an account on the npmjs.com
+site `__ or create an account from the
+command line by running ``npm adduser`` and entering values when
+prompted. Create an account now if you do not already have one. If you
+already have an account, login by running ``npm login`` and answering
+the prompts.
+
+Next, open the project ``package.json`` file in your text editor. Prefix
+the ``name`` field value with ``@your-npm-username>/`` so that the
+entire field reads ``"name": "@your-npm-username/jupyterlab_xkcd"`` where
+you've replaced the string ``your-npm-username`` with your real
+username. Review the homepage, repository, license, and `other supported
+package.json `__ fields while
+you have the file open. Then open the ``README.md`` file and adjust the
+command in the *Installation* section so that it includes the full,
+username-prefixed package name you just included in the ``package.json``
+file. For example:
+
+.. code:: bash
+
+ jupyter labextension install @your-npm-username/jupyterlab_xkcd
+
+Return to your terminal window and make one more git commit:
+
+.. code:: bash
+
+ git add .
+ git commit -m 'Prepare to publish package'
+
+Now run the following command to publish your package:
+
+.. code:: bash
+
+ npm publish --access=public
+
+Check that your package appears on the npm website. You can either
+search for it from the homepage or visit
+``https://www.npmjs.com/package/@your-username/jupyterlab_xkcd``
+directly. If it doesn't appear, make sure you've updated the package
+name properly in the ``package.json`` and run the npm command correctly.
+Compare your work with the state of the reference project at the
+`06-prepare-to-publish
+tag `__
+for further debugging.
+
+|Extension page on npmjs.com|
+
+You can now try installing your extension as a user would. Open a new
+terminal and run the following commands, again substituting your npm
+username where appropriate
+(make sure to stop the existing ``jupyter lab --watch`` command first):
+
+.. code:: bash
+
+ conda create -n jupyterlab-xkcd jupyterlab nodejs
+ conda activate jupyterlab-xkcd
+ jupyter labextension install @your-npm-username/jupyterlab_xkcd
+ jupyter lab
+
+You should see a fresh JupyterLab browser tab appear. When it does,
+execute the *Random xkcd comic* command to prove that your extension
+works when installed from npm.
+
+Learn more
+~~~~~~~~~~
+
+You've completed the tutorial. Nicely done! If you want to keep
+learning, here are some suggestions about what to try next:
+
+- Assign a hotkey to the *Random xkcd comic* command.
+- Make the image a link to the comic on https://xkcd.com.
+- Push your extension git repository to GitHub.
+- Give users the ability to pin comics in separate, permanent panels.
+- Learn how to write :ref:`other kinds of
+ extensions `.
+
+.. |Completed xkcd extension screenshot| image:: xkcd_tutorial_complete.png
+.. |Empty xkcd extension panel| image:: xkcd_tutorial_empty.png
+.. |Single xkcd extension panel| image:: xkcd_tutorial_single.png
+.. |Styled xkcd panel with attribution| image:: xkcd_tutorial_complete.png
+.. |Extension page on npmjs.com| image:: xkcd_tutorial_npm.png
diff --git a/docs/source/developer/xkcd_tutorial_complete.png b/docs/source/developer/xkcd_tutorial_complete.png
new file mode 100644
index 00000000..fae3fec9
Binary files /dev/null and b/docs/source/developer/xkcd_tutorial_complete.png differ
diff --git a/docs/source/developer/xkcd_tutorial_empty.png b/docs/source/developer/xkcd_tutorial_empty.png
new file mode 100644
index 00000000..38c91d29
Binary files /dev/null and b/docs/source/developer/xkcd_tutorial_empty.png differ
diff --git a/docs/source/developer/xkcd_tutorial_npm.png b/docs/source/developer/xkcd_tutorial_npm.png
new file mode 100644
index 00000000..4302c76a
Binary files /dev/null and b/docs/source/developer/xkcd_tutorial_npm.png differ
diff --git a/docs/source/developer/xkcd_tutorial_single.png b/docs/source/developer/xkcd_tutorial_single.png
new file mode 100644
index 00000000..6f1cb138
Binary files /dev/null and b/docs/source/developer/xkcd_tutorial_single.png differ
diff --git a/docs/source/getting_started/changelog.rst b/docs/source/getting_started/changelog.rst
new file mode 100644
index 00000000..51c4fbc0
--- /dev/null
+++ b/docs/source/getting_started/changelog.rst
@@ -0,0 +1,1078 @@
+.. _changelog:
+
+JupyterLab Changelog
+====================
+
+v1.0.0 (not released yet)
+-------------------------
+
+2019
+^^^^
+
+See the `JupyterLab
+1.0.0 `__
+milestone on GitHub for the full list of pull requests and issues closed.
+
+Features
+^^^^^^^^
+* Enable searching notebooks, code editors, and CSV files. (`#5795 `__, `#5937 `__)
+* Add Commands To Open The Main Menus So That They May Be Assigned Keyboard Shortcuts. (`#5910 `__, `#3074 `__)
+* Add Insertbefore And Insertafter To Toolbar (`#5896 `__, `#5894 `__)
+* Html Viewer (`#5855 `__, `#2369 `__)
+* Simplify Inspector. (`#5776 `__, `#5560 `__)
+* Allow Keyboard To Trigger Toolbar Button Action (`#5769 `__, `#5757 `__)
+* Code Folding (`#5761 `__, `#4083 `__)
+* Configure Terminal Font (`#5732 `__)
+* Links to CSV cells (`#5727 `__, `#5720 `__)
+* Follow File Path Between File Browser And Editor (`#5698 `__, `#4258 `__)
+* Add 'name' Flag To Workspaces Import Cli (`#5695 `__, `#5694 `__)
+* Creating new folder immediately edits its name (`#5667 `__, `#5666 `__)
+* Extension Manager Docs (`#5657 `__)
+* Add An Option To Toggle Document Scrolling Behavior (`#5652 `__, `#4429 `__)
+* Extension manager sort by composite registry score (`#5649 `__)
+* Allow "run All Code"/ "restart Kernel And Run All Code" When Editing Text File (`#5641 `__, `#5579 `__)
+* Remove Trust Notebook From Menu (`#5631 `__, `#5354 `__)
+* Improve Handling Of Uri Fragment Identifiers (`#5630 `__, `#5599 `__)
+* Css: Add Alert, Alert-Info And Alert-Warning Styles (`#5621 `__)
+* Add Scrollback As A Terminal Setting (`#5609 `__, `#3985 `__)
+* Drag Drop Console Cells Into Notebook (`#5585 `__, `#4847 `__)
+* Pressing Ctrl While Dragging Should Copy Files (`#5584 `__, `#3235 `__)
+* Add 'Scroll Past End' Notebook Setting (`#5581 `__, `#897 `__)
+* Drag and drop notebook cells to an editor (`#5571 `__, `#3732 `__)
+* Update Documentation For Terminal Copy/paste (`#5541 `__, `#4143 `__)
+* Codemirror: Add Config Options To Style Selection (`#5529 `__, `#5528 `__)
+* Add "go To Line" And "find" Capabilities To Csvviewer (`#5523 `__)
+* Add Statusbar (`#5508 `__, `#5352 `__, `#5514 `__, `#5577 `__, `#5525 `__)
+* Add 'new Folder' Item To Filebrowser Context Menu (`#5447 `__)
+
+
+Bugs Fixed
+^^^^^^^^^^
+* Fix Focus Issues When Focusing Away From A Notebook In Edit Mode. (`#5925 `__)
+* Start A New Terminal If Connecting To An Old One Fails. (`#5917 `__)
+* Remove Initialcommand From Args Of Terminal Creation. (`#5916 `__)
+* Once More With Carriage Returns (`#5907 `__, `#4822 `__)
+* Update Launcher On Specs Change (`#5904 `__, `#5676 `__)
+* Fix Bug Output View Not Closing With Associated Window (`#5882 `__, `#5873 `__)
+* Fix Navigate Behavior (`#5880 `__)
+* When A Session Is Disposed, Also Unset Any Busy Status. (`#5853 `__, `#5244 `__)
+* Account For Tree Urls When There Is A Workspace Collision. (`#5830 `__, `#5214 `__)
+* Keep Autoscroll Behavior When Clearing Cell Output. (`#5817 `__, `#4028 `__)
+* Show Correct File Type For Reload And Revert Dialogs (`#5746 `__)
+* Reject Instancetracker#add() If Added Widget Is Already Disposed. (`#5724 `__)
+* Don't Display Any Output If Javascript Output Is Empty (`#5706 `__, `#5404 `__)
+* Completer Feature Parity (`#5858 `__, `#4305 `__, `#4165 `__, `#2360 `__)
+* Honor Body Data Upon First Call To Pageconfig.getoption() (`#5800 `__, `#5799 `__)
+* Relative Non File Paths (`#5814 `__)
+* Make Jupyterlab Default Ui When Running Jupyter-Labhub (`#5865 `__)
+* Clean Up Schemas, Setting Editor Toolbar. (`#5820 `__, `#5372 `__)
+* Passing 'noopener' To Window.open() Always Returns Null, Breaking Exporting (`#5771 `__)
+* Find Editor Widgets (`#5758 `__)
+* Add scrolling to cell tools (`#5707 `__, `#5685 `__)
+* Optimize Editor Refresh On Notebook Show (`#5700 `__, `#4292 `__, `#2639 `__)
+* Properly Dispose Of Text Model On Disposal. (`#5686 `__, `#5664 `__)
+* Fix Doc Links (`#5677 `__, `#5602 `__)
+* [Html] External Links Should Add Rel="noopener" (`#5656 `__, `#5655 `__)
+* Remove Download Link For Directories (`#5637 `__, `#1816 `__)
+* Menu Entries Highlight On Mouse Over (`#5629 `__, `#5509 `__)
+* Fix code snippet highlighting in markdown lists (`#5628 `__, `#5616 `__)
+* Fix Linecol Functionality (`#5625 `__)
+* Css: Make Ansi "inverse" Work On Dark Theme (`#5623 `__)
+* Retain Windows file line endings (`#5622 `__, `#4464 `__, `#3901 `__, `#3706 `__)
+* Change File Mod Time Hover To Use Local/locale Time Format (`#5567 `__)
+* Commandpalette Highlight Fix (`#5565 `__, `#5561 `__)
+* Fix _changekernel Bug When Session Dead (`#5551 `__)
+* Clear ``*`` Prompt From Console Cells That Are Not Going To Be Executed (`#5550 `__, `#4916 `__)
+* Fix Alignment Of Latex/mathjax Output Cells (`#5547 `__, `#5276 `__)
+* Make Dom Ids Begin With Prefix 'id-' (`#5539