#!/usr/bin/env python
# coding: utf-8
# # CLI
# In[ ]:
import os, pathlib, tempfile, shutil, atexit, hashlib, pprint
from IPython.display import *
from IPython import get_ipython # needed for `jupyter_execute` because magics?
RTD = os.environ.get("READTHEDOCS")
# The `jupyter lite` (or `jupyter-lite`) CLI provides tools for lifecycle of combining...
# - **user-authored content** like _Notebooks_ and _Lab Extensions_.
# - the core JupyterLite **static assets**
#
# ... into a **ready-to-[deploy](./deploying.md)** (and optionally **reproducible**) Jupyter sites which require no server.
# ## Installation
# In[ ]:
get_ipython().system('pip install jupyterlite')
get_ipython().system('jupyter lite --version')
# ### Extras
# Some extra features of different _addons_ have additional dependencies.
#
# ```bash
# pip install jupyterlite[contents] # jupyter_server for better contents API
# pip install jupyterlite[serve] # tornado for better local preview
# pip install jupyterlite[lab] # a known-compatible jupyterlab (entails `contents` and `serve`)
# # TODO: [archive] # use libarchive
# ```
# ## The Lite Dir
#
# When you run `jupyter lite` commands, it assumes your current working directory is the partial contents of a JupyterLite site. You can override this with `--lite-dir`. By default, the built site will be created in `_output`, but can be overridden with `--output-dir`.
# In[ ]:
if "TMP_DIR" not in globals():
TMP_DIR = pathlib.Path(tempfile.mkdtemp(prefix="_my_lite_dir_"))
def clean():
shutil.rmtree(TMP_DIR)
atexit.register(clean)
os.chdir(TMP_DIR)
print(pathlib.Path.cwd())
# ### Well-known Files
#
# Some files in your `--lite-dir` that have special meaning:
# | paths | file | if found |
# |-----------------------------|---------------------|------------------------------------------------|
# | `.`
`./lab`
`./retro` | `jupyter-lite.{json,ipynb}` | merge contents with static in `_output/{path}/jupyter-lite.{json,ipynb}` |
# | `.`
`./lab`
`./retro` | `overrides.json` | merge with static `_output/{*}/jupyter-lite.json` ||
# | `./files/` | `*` | copy verbatim to `_output/files/*` and index in `/api/contents` |
# ## Usage
# ### Common Parameters
#
# | parameter | description | default | environment variable |
# | ------------------------ | ----------------------------------------------------------------------------------- | ----------------------------- | --------------------------- |
# | `--lite-dir` | configuration and content for the site | current working directory | `JUPYTERLITE_DIR` |
# | `--output-dir` | where the hostable site will be created | `_output` | `JUPYTERLITE_OUTPUT_DIR` |
# | `--cache-dir` | a cache directory for downloads | `/.cache` | `JUPYTERLITE_CACHE_DIR` |
# | `--app-archive` | an alternate site to base off of | bundled | |
# | `--files` | directory to copy to `_output/files/` and available as _Contents_ | `./files` | |
# | `--ignore-files` | patterns that should _never_ be included in `/files/` (even if found in `lite-dir`) | various | |
# | `--output-archive` | the path to the archive | `-jupyterlite.tgz` | `JUPYTERLAB_OUTPUT_ARCHIVE` |
# | `--port` | port on `127.0.0.1` to serve the test server | `8000` | `JUPYTERLITE_PORT` |
# | `--base-url` | the URL prefix to include before the site | `/` | `JUPYTERLITE_BASE_URL` |
# | `--source-date-epoch` | optionally enable additional reproducible build measures (best-effort!) | | `SOURCE_DATE_EPOCH` |
# | `--federated-extensions` | paths to folders, `pip`/`conda` packages with extensions [see note](#conda-packages)| | |
# | `--ignore-sys-prefix` | don't copy any contents, such as install labextensions, from `sys.prefix` | `False` | |
# All parameters may be configured via a `jupyter_lite_config.json` in the directory where `jupyter lite` is launched, or given via `--config`.
#
# ```{hint}
# For an advanced example, see the [configuration](https://github.com/jupyterlite/jupyterlite/tree/main/examples) used for this documentation.
# ```
# ### Help
#
# The CLI provides its own documentation, under `--help` (or `-h`).
# In[ ]:
get_ipython().system('jupyter lite --help')
# ### Status
#
# Always safe to run, this command provides an overview of what JupyterLite has been doing.
# In[ ]:
get_ipython().system('jupyter lite status')
# ### List
#
# Always safe to run, this command provides an overview of what JupyterLite _might_ do.
#
# > _TODO: improve on default output_
# In[ ]:
get_ipython().system('jupyter lite list')
# ### Init
#
# Copy all the static data to the `--output-dir`.
# In[ ]:
get_ipython().system('jupyter lite init')
# ### Build
#
# Copy all the **user-authored content** to the `--output-dir`, and applies appropriate changes to e.g. generated Contents API responses.
#
# Special well-known files will be _merged_ appropriately, but generally, files that exist in the user directory will overwrite any existing content.
# In[ ]:
get_ipython().system('jupyter lite build')
# ### Serve
#
# Serve the `--output-dir` on `http://127.0.0.1:{--port=8000}{--base-url=/}`.
#
# ```{warning}
# This is _not_ a production server. Please consider _any_ of the [deployment](./deploying.md) options
# before trying to make this something it isn't.
# ```
# In[ ]:
get_ipython().system('jupyter lite serve --help')
# ### Check
#
# Use all available mechanisms to verify that the build folder conforms to schema, etc.
# In[ ]:
get_ipython().system('jupyter lite check')
# ### Archive
#
# Turn the _output directory_ into a `.tgz` file. This is usually easier to move around than (sometimes) hundreds of files, and can be used as the baseline for future sites.
#
# > This command is _relatively_ expensive, and is skipped for documentation purposes
# In[ ]:
get_ipython().system('jupyter lite archive --help')
if not RTD:
get_ipython().system('jupyter lite archive')
# But let's talk about a more _reproducible_ asset.
# In[ ]:
# we clean out the TMP_DIR for the reproducibility examples
shutil.rmtree(TMP_DIR / "_output", ignore_errors=True)
#
#
# #### Reproducible Archives
#
# > _🛠️ This feature is a **work-in-progress**, and should not be relied upon by any production workflows **Just Yet**._
# If `--source-date-epoch` is given, a number of measures will be taken to *try to ensure* that the output of `jupyter lite archive`, an npm-compatible `tgz` package, always returns a bit-for-bit [reproducible build](https://reproducible-builds.org).
#
# The most obvious change is that the modified time of each file "clamped" to that time. Some other changes:
# - file ownership will be reset
# - predictable sorting will be used
# - additional checks will be applied
#
# ```{note}
# This is a shortcut for setting the environment variable `SOURCE_DATE_EPOCH`:
#
# | platform | command |
# |------------------|-------------------------------------------------------|
# | Linux
MacOS | `export SOURCE_DATE_EPOCH=` |
# | Windows | `set SOURCE_DATE_EPOCH=` |
# | Python | `os.environ.update(SOURCE_DATE_EPOCH, )` |
# ```
# In[ ]:
if not "source_date_epoch" in globals():
from datetime import datetime
source_date_epoch = int(datetime.utcnow().timestamp())
print("SOURCE_DATE_EPOCH is", source_date_epoch)
# In[ ]:
if not RTD:
get_ipython().system('jupyter lite archive --source-date-epoch {source_date_epoch} --output-archive ./a.tgz')
# If we clear out our `_output`...
# In[ ]:
if not RTD:
shutil.rmtree(TMP_DIR / "_output", ignore_errors=True)
pprint.pprint([*TMP_DIR.rglob("*")])
# ...and rebuild, we should always get the same file.
# In[ ]:
if not RTD:
get_ipython().system('jupyter lite archive --source-date-epoch {source_date_epoch} --output-archive ./b.tgz')
# In[ ]:
if not RTD:
a, b = [
hashlib.sha256((TMP_DIR / f"{x}.tgz").read_bytes()).hexdigest()
for x in "ab"
]
print("We built app archives with the SHA256SUMS of:\n", a, "\n", b)
try:
assert a == b, "We did not reproducibly build today.\n- {}\n- {}\n\n".format(a, b)
except AssertionError as err:
if shutil.which("diffoscope"):
print("We did NOT reproducibly build today, checking in with `diffoscope`...")
get_ipython().system('diffoscope a.tgz b.tgz')
print("...but at least we tried REALLY hard!\n")
# ## Miscellaneous
# ### conda packages
#
# While `--federated-extensions` support the `.tar.bz2` created by most `conda` packages, there are some issues:
#
# - `anaconda.org` uses non-standard HTTP headers to S3 buckets to provide packages
# - the `conda-forge` channel provides all of its builds as GitHub releases, and can be predictably transformed, e.g.
#
# ```
# https://anaconda.org/conda-forge/jupyterlab_widgets/1.0.0/download/noarch/jupyterlab_widgets-1.0.0-pyhd8ed1ab_1.tar.bz2
# | |
# | +---------------------------------------------+
# v v v
# https://github.com/conda-forge/releases/releases/download/noarch/jupyterlab_widgets-1.0.0-pyhd8ed1ab_1.tar.bz2/jupyterlab_widgets-1.0.0-pyhd8ed1ab_1.tar.bz2
# ```