···1313 runs-on: ubuntu-latest
1414 steps:
1515 - name: Checkout
1616- uses: actions/checkout@v3
1616+ uses: actions/checkout@v4
1717+1818+ - name: Set up Python
1919+ uses: actions/setup-python@v5
2020+ with:
2121+ python-version: '3.11'
17221818- - name: Base Setup
1919- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
2323+ - name: Set up Node
2424+ uses: actions/setup-node@v4
2525+ with:
2626+ node-version: '20'
20272128 - name: Check Release
2229 uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2
2330 with:
2431 token: ${{ secrets.GITHUB_TOKEN }}
3232+ # build-python/check-python are skipped because this is a monorepo
3333+ # with two Python packages in subdirectories. Python build correctness
3434+ # is verified by the separate build.yml workflow instead.
3535+ steps_to_skip: "build-python,check-python"
25362637 - name: Upload Distributions
2727- uses: actions/upload-artifact@v3
3838+ uses: actions/upload-artifact@v4
2839 with:
2929- name: jupyterlab_blockly-releaser-dist-${{ github.run_number }}
4040+ name: jupyter-blocks-releaser-dist-${{ github.run_number }}
3041 path: |
3142 .jupyter_releaser_checkout/dist
+14-3
.github/workflows/publish-dev.yml
···6565 pkg = json.load(f)
6666 pkg["version"] = dev_js_version
6767 with path.open("w") as f:
6868- json.dump(pkg, f, indent=2)
6868+ json.dump(pkg, f, indent=2, ensure_ascii=False)
6969 f.write("\n")
70707171 print(f"Set version to: {dev_js_version}")
7272 EOF
73737474+ - name: Copy package.json into Python package directories
7575+ # hatch-nodejs-version needs package.json present for version resolution.
7676+ # force-include in pyproject.toml handles the sdist case; this copy
7777+ # ensures the file exists for the initial version-reading step.
7878+ run: |
7979+ rm -f jupyter_blocks/package.json jupyter_tidyblocks/package.json
8080+ cp packages/blocks-extension/package.json jupyter_blocks/package.json
8181+ cp packages/tidyblocks-extension/package.json jupyter_tidyblocks/package.json
8282+7483 - name: Build JS packages
7584 # Build from repo root so labextension artifacts land in the expected
7685 # locations and hatch-jupyter-builder's skip-if-exists check passes.
7786 run: npm run build:prod
78877988 - name: Build jupyter_blocks
8080- run: python -m build jupyter_blocks/
8989+ run: python -m build --wheel jupyter_blocks/
81908291 - name: Build jupyter_tidyblocks
8383- run: python -m build jupyter_tidyblocks/
9292+ run: python -m build --wheel jupyter_tidyblocks/
84938594 - name: Publish jupyter_blocks to TestPyPI
8695 uses: pypa/gh-action-pypi-publish@release/v1
···8897 repository-url: https://test.pypi.org/legacy/
8998 packages-dir: jupyter_blocks/dist/
9099 skip-existing: true
100100+ verbose: true
9110192102 - name: Publish jupyter_tidyblocks to TestPyPI
93103 uses: pypa/gh-action-pypi-publish@release/v1
···95105 repository-url: https://test.pypi.org/legacy/
96106 packages-dir: jupyter_tidyblocks/dist/
97107 skip-existing: true
108108+ verbose: true
+1-1
docs/modernization-plan.md
···144144145145A new monorepo package providing tidy-data analysis blocks ported and extended from [gvwilson/tidyblocks](https://github.com/gvwilson/tidyblocks).
146146147147-See [`tidyblocks-features.md`](tidyblocks-features.md) for the complete block-by-block specification.
147147+See [`jupyter-tidyblocks-features.md`](jupyter-tidyblocks-features.md) for the complete block-by-block specification.
148148149149**Built with Vite** (library mode, same setup as Phase 3b — no module federation needed).
150150
+30-1
jupyter_blocks/LICENSE
···11-../LICENSE11+BSD 3-Clause License
22+33+Copyright (c) 2024, QuantStack
44+Copyright (c) 2026, Teon L. Brooks
55+All rights reserved.
66+77+Redistribution and use in source and binary forms, with or without
88+modification, are permitted provided that the following conditions are met:
99+1010+1. Redistributions of source code must retain the above copyright notice, this
1111+ list of conditions and the following disclaimer.
1212+1313+2. Redistributions in binary form must reproduce the above copyright notice,
1414+ this list of conditions and the following disclaimer in the documentation
1515+ and/or other materials provided with the distribution.
1616+1717+3. Neither the name of the copyright holder nor the names of its
1818+ contributors may be used to endorse or promote products derived from
1919+ this software without specific prior written permission.
2020+2121+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
2222+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2323+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2424+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
2525+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2626+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2727+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2828+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2929+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3030+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+95-1
jupyter_blocks/README.md
···11-../README.md11+# jupyter-blocks
22+33+[](https://github.com/teonbrooks/jupyter-blocks/actions/workflows/build.yml)
44+55+A monorepo containing two independently installable JupyterLab extensions:
66+77+- **`jupyter-blocks`** — a generic [Google Blockly](https://developers.google.com/blockly) editor for JupyterLab. Opens `.jblk` files, runs the generated code in a kernel, and exposes `IBlocklyRegistry` so other extensions can add their own toolboxes, blocks, and generators.
88+99+- **`jupyter-tidyblocks`** — a tidy-data analysis layer on top of `jupyter-blocks`. Inspired by [Greg Wilson's tidyblocks](https://github.com/gvwilson/tidyblocks) and originally built on [QuantStack/jupyterlab-blockly](https://github.com/QuantStack/jupyterlab-blockly). Provides drag-and-drop pandas pipelines (filter, select, group, join, plot, …) that generate executable Python.
1010+1111+## Install
1212+1313+### Tidy-data analysis (installs both packages)
1414+1515+```bash
1616+pip install jupyter-tidyblocks
1717+```
1818+1919+or via conda-forge (once published):
2020+2121+```bash
2222+conda install -c conda-forge jupyter-tidyblocks
2323+```
2424+2525+### Generic Blockly editor only
2626+2727+```bash
2828+pip install jupyter-blocks
2929+```
3030+3131+### Supported kernels
3232+3333+- ipykernel (Python)
3434+- xeus-python
3535+- xeus-lua
3636+- [ijavascript](https://github.com/n-riesco/ijavascript#installation)
3737+- [tslab](https://github.com/yunabe/tslab)
3838+3939+## Uninstall
4040+4141+```bash
4242+pip uninstall jupyter-tidyblocks # also removes jupyter-blocks if unused
4343+pip uninstall jupyter-blocks
4444+```
4545+4646+## Contributing
4747+4848+### Development install
4949+5050+```bash
5151+conda create -n blocks -c conda-forge python nodejs pre-commit jupyterlab ipykernel
5252+conda activate blocks
5353+5454+git clone https://github.com/teonbrooks/jupyter-blocks
5555+cd jupyter-blocks
5656+5757+pre-commit install
5858+npm install
5959+npm run build
6060+6161+# Install Python packages in editable mode
6262+pip install -e ./jupyter_blocks
6363+pip install -e ./jupyter_tidyblocks
6464+6565+# Register labextensions for development
6666+jupyter labextension develop ./jupyter_blocks --overwrite
6767+jupyter labextension develop ./jupyter_tidyblocks --overwrite
6868+```
6969+7070+Watch mode (two terminals):
7171+7272+```bash
7373+# Terminal 1 — rebuild on source changes
7474+npm run watch
7575+7676+# Terminal 2 — run JupyterLab
7777+jupyter lab
7878+```
7979+8080+### Development uninstall
8181+8282+```bash
8383+pip uninstall jupyter-tidyblocks jupyter-blocks
8484+```
8585+8686+Remove the symlinks created by `jupyter labextension develop`:
8787+8888+```bash
8989+jupyter labextension list # find labextensions folder
9090+# remove jupyter-blocks-extension and jupyter-tidyblocks-extension symlinks
9191+```
9292+9393+### Packaging
9494+9595+See [RELEASE.md](RELEASE.md).
···11-../LICENSE11+BSD 3-Clause License
22+33+Copyright (c) 2024, QuantStack
44+Copyright (c) 2026, Teon L. Brooks
55+All rights reserved.
66+77+Redistribution and use in source and binary forms, with or without
88+modification, are permitted provided that the following conditions are met:
99+1010+1. Redistributions of source code must retain the above copyright notice, this
1111+ list of conditions and the following disclaimer.
1212+1313+2. Redistributions in binary form must reproduce the above copyright notice,
1414+ this list of conditions and the following disclaimer in the documentation
1515+ and/or other materials provided with the distribution.
1616+1717+3. Neither the name of the copyright holder nor the names of its
1818+ contributors may be used to endorse or promote products derived from
1919+ this software without specific prior written permission.
2020+2121+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
2222+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2323+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2424+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
2525+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2626+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2727+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2828+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2929+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3030+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+95-1
jupyter_tidyblocks/README.md
···11-../README.md11+# jupyter-blocks
22+33+[](https://github.com/teonbrooks/jupyter-blocks/actions/workflows/build.yml)
44+55+A monorepo containing two independently installable JupyterLab extensions:
66+77+- **`jupyter-blocks`** — a generic [Google Blockly](https://developers.google.com/blockly) editor for JupyterLab. Opens `.jblk` files, runs the generated code in a kernel, and exposes `IBlocklyRegistry` so other extensions can add their own toolboxes, blocks, and generators.
88+99+- **`jupyter-tidyblocks`** — a tidy-data analysis layer on top of `jupyter-blocks`. Inspired by [Greg Wilson's tidyblocks](https://github.com/gvwilson/tidyblocks) and originally built on [QuantStack/jupyterlab-blockly](https://github.com/QuantStack/jupyterlab-blockly). Provides drag-and-drop pandas pipelines (filter, select, group, join, plot, …) that generate executable Python.
1010+1111+## Install
1212+1313+### Tidy-data analysis (installs both packages)
1414+1515+```bash
1616+pip install jupyter-tidyblocks
1717+```
1818+1919+or via conda-forge (once published):
2020+2121+```bash
2222+conda install -c conda-forge jupyter-tidyblocks
2323+```
2424+2525+### Generic Blockly editor only
2626+2727+```bash
2828+pip install jupyter-blocks
2929+```
3030+3131+### Supported kernels
3232+3333+- ipykernel (Python)
3434+- xeus-python
3535+- xeus-lua
3636+- [ijavascript](https://github.com/n-riesco/ijavascript#installation)
3737+- [tslab](https://github.com/yunabe/tslab)
3838+3939+## Uninstall
4040+4141+```bash
4242+pip uninstall jupyter-tidyblocks # also removes jupyter-blocks if unused
4343+pip uninstall jupyter-blocks
4444+```
4545+4646+## Contributing
4747+4848+### Development install
4949+5050+```bash
5151+conda create -n blocks -c conda-forge python nodejs pre-commit jupyterlab ipykernel
5252+conda activate blocks
5353+5454+git clone https://github.com/teonbrooks/jupyter-blocks
5555+cd jupyter-blocks
5656+5757+pre-commit install
5858+npm install
5959+npm run build
6060+6161+# Install Python packages in editable mode
6262+pip install -e ./jupyter_blocks
6363+pip install -e ./jupyter_tidyblocks
6464+6565+# Register labextensions for development
6666+jupyter labextension develop ./jupyter_blocks --overwrite
6767+jupyter labextension develop ./jupyter_tidyblocks --overwrite
6868+```
6969+7070+Watch mode (two terminals):
7171+7272+```bash
7373+# Terminal 1 — rebuild on source changes
7474+npm run watch
7575+7676+# Terminal 2 — run JupyterLab
7777+jupyter lab
7878+```
7979+8080+### Development uninstall
8181+8282+```bash
8383+pip uninstall jupyter-tidyblocks jupyter-blocks
8484+```
8585+8686+Remove the symlinks created by `jupyter labextension develop`:
8787+8888+```bash
8989+jupyter labextension list # find labextensions folder
9090+# remove jupyter-blocks-extension and jupyter-tidyblocks-extension symlinks
9191+```
9292+9393+### Packaging
9494+9595+See [RELEASE.md](RELEASE.md).
···11+[tool.jupyter-releaser.options]
22+version_cmd = "python scripts/bump-version.py --force"
33+# jupyter_releaser build-python runs `pipx run build .` from this directory.
44+# Point it at jupyter_blocks so the root (which has no build-system) is not used.
55+# jupyter_tidyblocks is released separately or via publish-dev.yml.
66+python_package = "jupyter_blocks"
77+88+[tool.jupyter-releaser.hooks]
99+before-bump-version = [
1010+ "python -m pip install 'jupyterlab>=4.0.0,<5'"
1111+]
1212+before-build-npm = [
1313+ "npm install",
1414+ "npm run build:prod"
1515+]
1616+before-build-python = [
1717+ "npm run build:prod"
1818+]
+39
scripts/build-local.sh
···11+#!/usr/bin/env bash
22+# Local build script — mirrors the publish-dev.yml workflow steps.
33+# Run from the repo root: bash scripts/build-local.sh
44+set -euxo pipefail
55+66+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
77+cd "$REPO_ROOT"
88+99+# 1. Install Python dependencies
1010+pip install -U "jupyterlab>=4.0.0,<5" build
1111+1212+# 2. Install JS dependencies
1313+npm install
1414+1515+# 3. Copy package.json as a real file so hatch can find it inside the sdist.
1616+# LICENSE and README.md are now real committed files (not symlinks).
1717+# Remove symlinks first so cp doesn't complain about identical source/dest.
1818+rm -f jupyter_blocks/package.json jupyter_tidyblocks/package.json
1919+cp packages/blocks-extension/package.json jupyter_blocks/package.json
2020+cp packages/tidyblocks-extension/package.json jupyter_tidyblocks/package.json
2121+2222+# 4. Build JS (produces labextension artifacts used by hatch-jupyter-builder)
2323+npm run build:prod
2424+2525+# 5. Build Python packages (--wheel builds directly from source, avoiding the
2626+# sdist-then-wheel flow which breaks on symlinks in the extracted temp dir)
2727+python -m build --wheel jupyter_blocks/
2828+python -m build --wheel jupyter_tidyblocks/
2929+3030+echo ""
3131+echo "Build complete. Artifacts:"
3232+ls jupyter_blocks/dist/
3333+ls jupyter_tidyblocks/dist/
3434+3535+# 6. Restore package.json symlinks
3636+ln -sf ../packages/blocks-extension/package.json jupyter_blocks/package.json
3737+ln -sf ../packages/tidyblocks-extension/package.json jupyter_tidyblocks/package.json
3838+3939+echo "Symlinks restored."
+10-3
scripts/bump-version.py
···1212from pathlib import Path
13131414import click
1515-from jupyter_releaser.util import get_version, run
1515+from jupyter_releaser.util import run
1616from packaging.version import Version
17171818# All package.json files whose "version" field must be kept in sync.
···3838 if len(status) > 0:
3939 raise Exception("Must be in a clean git state with no untracked files")
40404141- curr = Version(get_version())
4141+ with VERSION_SOURCE.open() as f:
4242+ js_curr = json.load(f)["version"]
4343+ # Convert JS semver to PEP 440: "0.1.0-alpha.1" → "0.1.0a1"
4444+ import re
4545+ py_curr = re.sub(r"-alpha\.(\d+)", r"a\1", js_curr)
4646+ py_curr = re.sub(r"-beta\.(\d+)", r"b\1", py_curr)
4747+ py_curr = re.sub(r"-rc\.(\d+)", r"rc\1", py_curr)
4848+ curr = Version(py_curr)
4249 if spec == 'next':
4350 spec = f"{curr.major}.{curr.minor}."
4451 if curr.pre:
···7279 data = json.load(f)
7380 data["version"] = js_version
7481 with path.open("w") as f:
7575- json.dump(data, f, indent=2)
8282+ json.dump(data, f, indent=2, ensure_ascii=False)
7683 f.write("\n")
77847885 print(f"Bumped all packages to {js_version}")