RELEASE.md
This document guides a contributor through creating a release of JupyterLab.
JupyterLab follows semver for the versioning of the Python and JavaScript packages.
Although the commitments listed below are "best effort", the JupyterLab team tries to follow a couple of guidelines:
Release Plans are tracked in dedicated issues, and are closed when the final release. See the following two issues as an example:
Alpha releases have a fairly low bar. Their purpose is to start putting the new JupyterLab version into the hands of users and extension authors.
The requirements for an alpha release should be that JupyterLab can be installed and run. Bugs and breaking changes are accepted.
Beta releases usually try to not have breaking changes in the API, although breaking changes can sometimes happen during that phase if they were missed during the alpha stage.
The recommended time period for the Beta phase is a minimum of 2 weeks.
The draft changelog describing user-facing changes will be published with the first Beta release.
The community of extension developers and active users will be invited to commence testing the new Beta release including the draft user-facing changelog, and an invitation to open issues for any major:
The start of the Beta-testing period will be announced on Jupyter mailing group and Jupyter Discourse for major releases, and only via a Discourse post for minor releases.
All bug reports raised during the Beta-testing period should be triaged (but not necessarily addressed) before releasing the first release candidate.
Release Candidates (RC) are a signal to the extension developer community that they should start migrating to the new version to test it. At that point we consider the software stable.
The RC stage is often a good time to address final release documentation changes or minor UX tweaks. During the RC phase, the JupyterLab developers and maintainers start updating third-party extensions over to the new version to test it. This work during the RC phase, and giving time for feedback from extension developers, can take up to a couple of weeks.
The recommended time period for the Release Candidate phase is a minimum of 1 week for minor releases, and 2 weeks for major releases.
The recommended way to make a release is to use the following Jupyter Releaser workflows.
The full process is documented in the Jupyter Release docs. There is a recording of the full workflow on YouTube.
Here is a quick summary of the action items, with each step linked to further context below.
New Version Specifier: next if releasing a patch release (these are the most common), otherwise major, minor or release as per the bump version tableThe branch to target: leave blank if releasing the main branch, otherwise fill in with the repo branch you are releasingIt is good practice to let other maintainers and users know when starting a new release.
We usually leave a small message in the "Release Coordination" topic of the jupyterlab channel on Zulip. Once the release is done, we also post a message with a link to the release notes.
The first step is to generate a new draft GitHub Release for the upcoming release. This step does not modify the source in the repo and does not release packages. We use the Prep Release workflow as documented here. We run this workflow from the main branch (i.e., we want the latest workflow configuration), even if we are releasing a different branch.
The Prep Release workflow takes a couple of input parameters. Commonly specified parameters are:
| Input | Description | Default Value (used if blank) |
|---|---|---|
| New Version Specifier | The version we will be releasing. The default next bumps the patch release of the current version in the target branch. The version spec follows the specification documented below in the Bump Version section. | next |
| The branch to target | The branch that is being released | main |
Other fields that are not commonly set in JupyterLab releases:
Post Version SpecifierSet a placeholder in the changelog and don't publish the release.Use PRs with activity since this date or git reference: defaults to the last tag. This is never configured in JupyterLab releases.Use PRs with activity since the last stable git tag:Click on "Run workflow", then once completed:
The second step is to generate and publish release artifacts. We use the Publish Release workflow to build JupyterLab, generate Javascript and Python packages, and publish those to NPM and PyPI. We run this workflow from the main branch (i.e., we want the latest workflow configuration), even if we are releasing a different branch.
The Publish Release workflow takes a couple of input parameters. Commonly specified parameters are:
| Input | Description | Default Value (used if blank) |
|---|---|---|
| The target branch | The branch that is being released | main |
| The URL of the draft GitHub release | The URL of the draft GitHub release that was generated by the Prep Release worfklow in the previous step | the most recent GitHub draft release |
Other fields that are not commonly set in JupyterLab releases:
Comma separated list of steps to skip: it is possible to skip some steps of the release manually. We almost never use this functionality any more.The "Publish Release" workflow:
jupyterlab Python package to PyPI@jupyterlab/* packages and uploads them to npmmain, creates a PR to forward-port the new changelog entry to the main branchAfter the release is published, consider the Post-release candidate checklist if this was a major or minor release.
Review CONTRIBUTING.md. Make sure all the tools needed to generate the
built JavaScript files are properly installed.
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 for twine setup instructions and for why twine is the recommended method.
For convenience, here is a script 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 correct branch. The script creates a conda env, pulls down a git checkout with the
appropriate branch, and installs JupyterLab with pip install -e ..
Make sure you are running an sh-compatible shell, and it is set up to be able to do conda activate. Then do:
source scripts/release_prep.sh <branch_name>
The next step is to bump the appropriate version numbers. We use bump2version to manage the Python version, and we keep the JS versions and tags in sync with the release cycle.
Here is an example of how version numbers progress through a release process. Choose and run an appropriate command to bump version numbers for this release.
| Command | Python Version Change | NPM Version change |
|---|---|---|
jlpm bumpversion major | x.y.z-> (x+1).0.0.a0 | All a.b.c -> a.(b+10).0-alpha.0 |
jlpm bumpversion minor | x.y.z-> x.(y+1).0.a0 | All a.b.c -> a.(b+1).0-alpha.0 |
jlpm bumpversion build | x.y.z.a0-> x.y.z.a1 | All a.b.c-alpha.0 -> a.b.c-alpha.1 |
jlpm bumpversion release | x.y.z.a1-> x.y.z.b0 | All a.b.c-alpha.1 -> a.b.c-beta.0 |
jlpm bumpversion release | x.y.z.b1-> x.y.z.rc0 | All a.b.c-beta.1 -> a.b.c-rc.0 |
jlpm bumpversion release | x.y.z.rc0-> x.y.z | All a.b.c-rc0 -> a.b.c |
jlpm bumpversion patch | x.y.z -> x.y.(z+1) | Changed a.b.c -> a.b.(c+1) |
Note: For a major release, we bump the JS packages by 10 versions so that we are not competing amongst the minor releases for version numbers. We are essentially sub-dividing semver to allow us to bump minor versions of the JS packages as many times as we need to for minor releases of the top level JupyterLab application.
In a major Python release, we can have one or more JavaScript packages also have a major bump. During the prerelease stage of a major release, if there is a backwards-incompatible change to a JS package, bump the major version number for that JS package:
jlpm bump:js:major [...packages]
NOTE You should rebase before running jlpm bump:js:major to avoid a cascade of merge conflicts.
Results:
alpha, beta, or rc).Now publish the JS packages
npm run publish:js
If there is a network error during JS publish, run npm run publish:js --skip-build to resume publish without requiring another clean and build phase of the JS packages.
Note that the use of npm instead of jlpm is significant on Windows.
Next, prepare the python release by running:
npm run prepare:python-release
This will update the Python package to use the new JS packages and create the Python release assets. Note: sometimes the npm registry is slow to update with the new packages, so this script tries to fetch the packages until they are available.
At this point, run the ./scripts/release_test.sh to test the wheel in
a fresh conda environment with and without extensions installed. Open and run
the Outputs notebook and verify everything runs properly. Also add a cell with the following code and make sure the widget renders:
from ipywidgets import IntSlider
IntSlider()
Follow instructions printed at the end of the publish step above:
twine upload dist/*
git push origin --tags <BRANCH>
These lines:
--tags option.python scripts/milestone_check.py to check the issues assigned to this milestoneloghub jupyterlab/jupyterlab -m XXX -t $GITHUB_TOKEN --template scripts/release_template.txt
npm access public @jupyterlab/<name> to make it public.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).python scripts/milestone_check.py to check the issues assigned to this milestone one more time. Update changelog if necessary.Now do the actual final release:
jlpm run bumpversion release to switch to final releasenpm run publish:all to publish the packagesAfter a few days (to allow for possible patch releases), set up development for the next release:
jlpm run bumpversion minor to bump to alpha for the next alpha releasenpm run publish:all to publish the packagesgit clone [email protected]:jupyterlab/jupyterlab_apod.git
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:
git checkout -b BRANCH # whatever the new version is, e.g., 1.0
git rebase -i --root
To seed the latest version of the extension template (first commit), you can execute (assuming you are editing the first commit):
copier copy --UNSAFE https://github.com/jupyterlab/extension-template .
# Fix any conflicts
git commit --amend '-S'
"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 extension tutorial. If the edits are more substantial than just
updating package versions, then do the next steps instead.
git checkout --orphan name-of-branch
git rm -rf .
git clean -dfx
copier copy --UNSAFE https://github.com/jupyterlab/extension-template .
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.Tag commits in the branch with the appropriate branch-step tag. If you are at the final commit, you can tag all commits with the below, setting BRANCH with the branch name (e.g., 1.0-01-show-a-panel)
export BRANCH=<branch-name>
git tag ${BRANCH}-01-show-a-panel HEAD~4
git tag ${BRANCH}-02-show-an-image HEAD~3
git tag ${BRANCH}-03-style-and-attribute HEAD~2
git tag ${BRANCH}-04-refactor-and-refresh HEAD~1
git tag ${BRANCH}-05-restore-panel-state HEAD
Push the branch with the new tags
git push origin ${BRANCH} --tags
Set the branch as the default branch (see github.com/jupyterlab/jupyterlab_apod/settings/branches).
If there were changes to the example in the documentation, submit a PR to JupyterLab
Publish the new jupyterlab_apod python package. Make sure to update the version
number in the last commit of the branch.
twine upload dist/*
If you make a mistake and need to start over, clear the tags using the following pattern:
git tag | grep ${BRANCH} | xargs git tag -d
shasum -a 256 dist/*.tar.gz
recipe/meta.yaml with the new version and sha256 and reset the build number to 0./packages/package-folder-name (note that multiple packages can be given, or no packages for a Python-only patch release):jlpm run patch:release package-folder-name
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.
ensure_repo.ts and run jlpm integrity to update links - source should be the previous release branchensure_repo.ts and jlpm integrity to update links - source should be the previous branchHere is a list of previous issues that happened while releasing JupyterLab, that can be used as reference in case new issues show up in the future:
4.0.0a23): https://github.com/jupyterlab/jupyterlab/issues/123244.0.0b2): https://github.com/jupyterlab/jupyterlab/issues/144314.3.0rc0: Project size too large on PyPI: https://github.com/jupyterlab/jupyterlab/issues/168574.4.0a0: 403 Client Error: Server failed to authenticate the request: https://github.com/jupyterlab/jupyterlab/issues/16976