mirror of
https://github.com/saymrwulf/QuantumLearning.git
synced 2026-05-14 20:58:00 +00:00
Harden Jupyter state lifecycle and cleanup
This commit is contained in:
parent
ef778073de
commit
377c96de8a
9 changed files with 268 additions and 95 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,8 @@
|
||||||
.ipython/
|
.ipython/
|
||||||
.jupyter_runtime/
|
.jupyter_runtime/
|
||||||
.jupyter_data/lab/workspaces/
|
.jupyter_data/lab/workspaces/
|
||||||
|
.jupyter_data/nbsignatures.db
|
||||||
|
.jupyter_data/notebook_secret
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,18 +0,0 @@
|
||||||
SrXNuCCg+wrEA1L1jVumnXf3ChsNATdtqH3UiZud+nbMUHxjUqqRRPcHmyufdB8eMh0TPu+cIe8Z
|
|
||||||
t0caRkarRaz/ouQ9rwyFjxgt4UMFFRHXqAofkPH4zF89S4s83U+C2mZ911tUoAr7rql6lW0UDrro
|
|
||||||
U4RYWQFd356AfiDwmGhC2AVO7/Yt2NmOgawxWvqsuAaIzSIjDvFY/kU+UNu2nyW7FrhumjgmBp93
|
|
||||||
Hk4BOZb8xnEcz24E9wVQ/9Fn2XXcIFGB+DowTQPkai3xIo2zx1o5+6+Vbsq1b7dfESlliXj6Nh0o
|
|
||||||
R3haoHFr4B6Q/biz7YMZq3njh2Mq/6NzoK71exnJ4vjm6/ucLlX94DEeyvlsK8XtBf+rO/Ux2kQ4
|
|
||||||
P8U3VqqEcsFE36k7jVGqMv00m/PKL1W4A/C2HdutD6WsGDu8F6+jI3lKgrWVcylwqZf0t0TQ9zKf
|
|
||||||
pqlFO1PmsIubDRwd6rfiZbBC8S1fMzBMWfs4jM2FVWALIz+fQZZ2pUBxmMPsk3M7SmmKw8Zyc/nj
|
|
||||||
U+SroBGM1eb4W7UERCCsa990B00zmNCz5s/VJkrn9TVwAT48vVuxphKPxMeyiwtHuEHiSkN2fb/l
|
|
||||||
8aaaL1DiFk8KSogKsJZ9JgEpnqMXqxEJ6JF+IRJrfHHD8jPuatXjPqVjdMgpSnWG6ngrbAd2cw4A
|
|
||||||
lHOqJG8EuRieYh/vx/xc+b8+LumXol0m1Gz7W1pVRXXrh+rVrMk3eUJO4yAbcXIZM3/Sqe8N7ZAH
|
|
||||||
yxtzFCce5mgNGdk+1I3GfLTU999fbdERodpsnlKoSX7eB+fT+8z68kucSx4OYswIaXLCk58Fjz+1
|
|
||||||
ZuvO8SsiNoSoQ9tIv1B0aKFGST4BLyQ1NccTIJjhAGiE65jzpDuaqRl9HyTXF3Wj7TeSlOYbOJLh
|
|
||||||
1H3CupTXl3PjH9i6K1zrx4TrOEe5J4b/iaIXlw8PzUSBL/yeaGHou2NzW2oTOQ1Ev9P9n9SfyIU3
|
|
||||||
BuCvqiUTbm2k8Ktk9g7mcHUw+63syE0b9CTPXzdAlvI30j+K2s+zs6fWtpZXsIy+Xz9rcBX3Nztm
|
|
||||||
nl/Z3yLa3+khqS7hY/kxgURjvm0XxPwuGoWt9dJFkkoQXLiOrzKiCD773qwrLHEyHx0TI4ntfFV0
|
|
||||||
TducvMBpiAvaN1KnlJU8K6yyeqZdJytxJngZGcRgtz/IjySByb75zLpInoHf0HKxgjPfSHvXCVlP
|
|
||||||
vuKq5a16PU7Dlj01u3szIR16bh/qXeKn3h89gslw0fR+ILWnzTuy8VlqtbgfSW+Ohj5X5VCok6k/
|
|
||||||
dr21sWY2J1GlZmu9N/4Hpj49kORRj/lb5UXZIGsqxSeK4yHDGTGR1YBK+GPmARkzzvF/qDY1FQ==
|
|
||||||
|
|
@ -23,6 +23,13 @@ Use this the first time you open the platform, or anytime you want a clean local
|
||||||
bash ./scripts/app.sh start --open
|
bash ./scripts/app.sh start --open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If JupyterLab later looks haunted by old tabs or stale session state, reset the repo-local Jupyter state and start again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
|
bash ./scripts/app.sh start --open
|
||||||
|
```
|
||||||
|
|
||||||
4. Open the localhost URL printed by the script.
|
4. Open the localhost URL printed by the script.
|
||||||
5. Confirm the kernel in the top-right corner is `QuantumLearning (.venv)`.
|
5. Confirm the kernel in the top-right corner is `QuantumLearning (.venv)`.
|
||||||
6. Run `Kernel -> Restart Kernel and Run All Cells...` inside `notebooks/COURSE_BLUEPRINT.ipynb`.
|
6. Run `Kernel -> Restart Kernel and Run All Cells...` inside `notebooks/COURSE_BLUEPRINT.ipynb`.
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ Other normal consumer commands:
|
||||||
bash ./scripts/app.sh status
|
bash ./scripts/app.sh status
|
||||||
bash ./scripts/app.sh restart
|
bash ./scripts/app.sh restart
|
||||||
bash ./scripts/app.sh stop
|
bash ./scripts/app.sh stop
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
bash ./scripts/app.sh logs -f
|
bash ./scripts/app.sh logs -f
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -106,11 +107,18 @@ It reports:
|
||||||
- whether `.venv/` exists and which Python version it uses
|
- whether `.venv/` exists and which Python version it uses
|
||||||
- whether the project-local Playwright browser runtime is installed
|
- whether the project-local Playwright browser runtime is installed
|
||||||
- whether a repo-local Jupyter server is running and, if so, its full URL
|
- whether a repo-local Jupyter server is running and, if so, its full URL
|
||||||
|
- whether saved Jupyter UI state is still present and how to reset it
|
||||||
|
|
||||||
If you lose the Jupyter URL, `bash ./scripts/app.sh status` is the fastest recovery path.
|
If you lose the Jupyter URL, `bash ./scripts/app.sh status` is the fastest recovery path.
|
||||||
|
|
||||||
In restricted automation sandboxes, the script may report that the live process probe is unavailable. In a normal terminal on your Mac, it will report the running server directly when the runtime file and process are both visible.
|
In restricted automation sandboxes, the script may report that the live process probe is unavailable. In a normal terminal on your Mac, it will report the running server directly when the runtime file and process are both visible.
|
||||||
|
|
||||||
|
If JupyterLab reopens old tabs or appears to remember stale notebook activity, that is usually saved workspace state rather than a still-running repo-local server. Clear it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
|
```
|
||||||
|
|
||||||
## Validation Modes
|
## Validation Modes
|
||||||
|
|
||||||
Quick local validation:
|
Quick local validation:
|
||||||
|
|
@ -176,6 +184,7 @@ If something feels wrong, use this order:
|
||||||
2. `bash ./scripts/app.sh validate --quick`
|
2. `bash ./scripts/app.sh validate --quick`
|
||||||
3. if needed, `bash ./scripts/app.sh validate --full`
|
3. if needed, `bash ./scripts/app.sh validate --full`
|
||||||
4. restart Jupyter with `bash ./scripts/app.sh restart`
|
4. restart Jupyter with `bash ./scripts/app.sh restart`
|
||||||
|
5. if the UI still looks stale, run `bash ./scripts/app.sh reset-state`
|
||||||
|
|
||||||
If `.venv/` is missing or broken, re-run:
|
If `.venv/` is missing or broken, re-run:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ bash ./scripts/app.sh start --open
|
||||||
bash ./scripts/app.sh status
|
bash ./scripts/app.sh status
|
||||||
bash ./scripts/app.sh restart
|
bash ./scripts/app.sh restart
|
||||||
bash ./scripts/app.sh stop
|
bash ./scripts/app.sh stop
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want a fast operational check immediately after bootstrap:
|
If you want a fast operational check immediately after bootstrap:
|
||||||
|
|
@ -96,6 +97,12 @@ If you want to inspect the live log:
|
||||||
bash ./scripts/app.sh logs -f
|
bash ./scripts/app.sh logs -f
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If JupyterLab reopens old tabs, looks like notebooks are still running, or you want a true clean slate for the repo-local Jupyter state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
|
```
|
||||||
|
|
||||||
Run the tests:
|
Run the tests:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
231
scripts/app.sh
231
scripts/app.sh
|
|
@ -22,6 +22,9 @@ Commands:
|
||||||
stop
|
stop
|
||||||
Stop the repo-local Jupyter server for this project.
|
Stop the repo-local Jupyter server for this project.
|
||||||
|
|
||||||
|
reset-state
|
||||||
|
Remove repo-local Jupyter runtime and saved UI state files.
|
||||||
|
|
||||||
restart [--port PORT] [--open] [--foreground]
|
restart [--port PORT] [--open] [--foreground]
|
||||||
Restart the repo-local Jupyter server.
|
Restart the repo-local Jupyter server.
|
||||||
|
|
||||||
|
|
@ -46,6 +49,7 @@ Examples:
|
||||||
bash ./scripts/app.sh restart
|
bash ./scripts/app.sh restart
|
||||||
bash ./scripts/app.sh status
|
bash ./scripts/app.sh status
|
||||||
bash ./scripts/app.sh stop
|
bash ./scripts/app.sh stop
|
||||||
|
bash ./scripts/app.sh reset-state
|
||||||
bash ./scripts/app.sh validate --quick
|
bash ./scripts/app.sh validate --quick
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +228,90 @@ if verbose:
|
||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
purge_runtime_artifacts() {
|
||||||
|
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$ROOT_DIR" "${1:-0}"
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path(sys.argv[1]).resolve()
|
||||||
|
verbose = sys.argv[2] == "1"
|
||||||
|
runtime_dir = root / ".jupyter_runtime"
|
||||||
|
patterns = (
|
||||||
|
"jpserver-*.json",
|
||||||
|
"jpserver-*-open.html",
|
||||||
|
"nbserver-*.json",
|
||||||
|
"nbserver-*-open.html",
|
||||||
|
"kernel-*.json",
|
||||||
|
)
|
||||||
|
removed = []
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
for path in sorted(runtime_dir.glob(pattern)):
|
||||||
|
if path.exists():
|
||||||
|
path.unlink()
|
||||||
|
removed.append(path)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
for path in removed:
|
||||||
|
print(path)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
purge_saved_ui_state() {
|
||||||
|
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$ROOT_DIR" "${1:-0}"
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path(sys.argv[1]).resolve()
|
||||||
|
verbose = sys.argv[2] == "1"
|
||||||
|
data_dir = root / ".jupyter_data"
|
||||||
|
paths = [
|
||||||
|
data_dir / "nbsignatures.db",
|
||||||
|
data_dir / "notebook_secret",
|
||||||
|
]
|
||||||
|
workspace_dir = data_dir / "lab" / "workspaces"
|
||||||
|
removed = []
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
if path.exists():
|
||||||
|
path.unlink()
|
||||||
|
removed.append(path)
|
||||||
|
|
||||||
|
for path in sorted(workspace_dir.glob("*.jupyterlab-workspace")):
|
||||||
|
if path.exists():
|
||||||
|
path.unlink()
|
||||||
|
removed.append(path)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
for path in removed:
|
||||||
|
print(path)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
print_ui_state_summary() {
|
||||||
|
local workspace_count="0"
|
||||||
|
if compgen -G "$ROOT_DIR/.jupyter_data/lab/workspaces/*.jupyterlab-workspace" >/dev/null; then
|
||||||
|
workspace_count="$(find "$ROOT_DIR/.jupyter_data/lab/workspaces" -maxdepth 1 -name '*.jupyterlab-workspace' | wc -l | tr -d ' ')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local trust_db="absent"
|
||||||
|
local notebook_secret="absent"
|
||||||
|
[[ -f "$ROOT_DIR/.jupyter_data/nbsignatures.db" ]] && trust_db="present"
|
||||||
|
[[ -f "$ROOT_DIR/.jupyter_data/notebook_secret" ]] && notebook_secret="present"
|
||||||
|
|
||||||
|
if [[ "$workspace_count" == "0" && "$trust_db" == "absent" && "$notebook_secret" == "absent" ]]; then
|
||||||
|
echo "Jupyter UI state: clean"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Jupyter UI state: workspaces=$workspace_count, trust-db=$trust_db, notebook-secret=$notebook_secret"
|
||||||
|
echo " Clean slate: bash ./scripts/app.sh reset-state"
|
||||||
|
}
|
||||||
|
|
||||||
have_running_server() {
|
have_running_server() {
|
||||||
local json_path
|
local json_path
|
||||||
cleanup_stale_runtime_files >/dev/null
|
cleanup_stale_runtime_files >/dev/null
|
||||||
|
|
@ -256,24 +344,34 @@ stop_server() {
|
||||||
ensure_venv
|
ensure_venv
|
||||||
ensure_dirs
|
ensure_dirs
|
||||||
|
|
||||||
local json_path pid
|
local json_path pid removed_paths
|
||||||
cleanup_stale_runtime_files >/dev/null
|
cleanup_stale_runtime_files >/dev/null
|
||||||
json_path="$(server_json_for_root || true)"
|
json_path="$(server_json_for_root || true)"
|
||||||
if [[ -z "$json_path" ]]; then
|
if [[ -z "$json_path" ]]; then
|
||||||
echo "No repo-local Jupyter runtime file found."
|
echo "No repo-local Jupyter runtime file found."
|
||||||
|
removed_paths="$(purge_runtime_artifacts 1 || true)"
|
||||||
|
if [[ -n "$removed_paths" ]]; then
|
||||||
|
echo "Removed stale runtime files:"
|
||||||
|
echo "$removed_paths"
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pid="$(server_field "$json_path" "pid")"
|
pid="$(server_field "$json_path" "pid")"
|
||||||
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
||||||
local removed_paths
|
|
||||||
removed_paths="$(cleanup_stale_runtime_files 1 || true)"
|
removed_paths="$(cleanup_stale_runtime_files 1 || true)"
|
||||||
|
local extra_removed
|
||||||
|
extra_removed="$(purge_runtime_artifacts 1 || true)"
|
||||||
echo "Jupyter runtime file exists, but the process is not running."
|
echo "Jupyter runtime file exists, but the process is not running."
|
||||||
echo "Runtime file: $json_path"
|
echo "Runtime file: $json_path"
|
||||||
if [[ -n "$removed_paths" ]]; then
|
if [[ -n "$removed_paths" ]]; then
|
||||||
echo "Removed stale runtime files:"
|
echo "Removed stale runtime files:"
|
||||||
echo "$removed_paths"
|
echo "$removed_paths"
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$extra_removed" ]]; then
|
||||||
|
echo "Removed leftover runtime artifacts:"
|
||||||
|
echo "$extra_removed"
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -281,6 +379,7 @@ stop_server() {
|
||||||
for _ in $(seq 1 30); do
|
for _ in $(seq 1 30); do
|
||||||
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
||||||
cleanup_stale_runtime_files >/dev/null
|
cleanup_stale_runtime_files >/dev/null
|
||||||
|
purge_runtime_artifacts >/dev/null
|
||||||
echo "Stopped Jupyter (pid $pid)."
|
echo "Stopped Jupyter (pid $pid)."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
@ -317,6 +416,8 @@ start_server() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
purge_runtime_artifacts >/dev/null
|
||||||
|
|
||||||
if [[ "$foreground" == "1" ]]; then
|
if [[ "$foreground" == "1" ]]; then
|
||||||
exec "$ROOT_DIR/.venv/bin/jupyter" lab \
|
exec "$ROOT_DIR/.venv/bin/jupyter" lab \
|
||||||
--no-browser \
|
--no-browser \
|
||||||
|
|
@ -378,6 +479,124 @@ validate_command() {
|
||||||
bash "$ROOT_DIR/scripts/run_validation.sh" "$@"
|
bash "$ROOT_DIR/scripts/run_validation.sh" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset_state_command() {
|
||||||
|
ensure_venv
|
||||||
|
ensure_dirs
|
||||||
|
|
||||||
|
if have_running_server; then
|
||||||
|
stop_server >/dev/null
|
||||||
|
else
|
||||||
|
cleanup_stale_runtime_files >/dev/null
|
||||||
|
purge_runtime_artifacts >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
local removed_runtime removed_ui
|
||||||
|
removed_runtime="$(purge_runtime_artifacts 1 || true)"
|
||||||
|
removed_ui="$(purge_saved_ui_state 1 || true)"
|
||||||
|
|
||||||
|
if [[ -z "$removed_runtime" && -z "$removed_ui" ]]; then
|
||||||
|
echo "Jupyter state was already clean."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$removed_runtime" ]]; then
|
||||||
|
echo "Removed runtime artifacts:"
|
||||||
|
echo "$removed_runtime"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$removed_ui" ]]; then
|
||||||
|
echo "Removed saved UI state:"
|
||||||
|
echo "$removed_ui"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status_command() {
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
ensure_dirs
|
||||||
|
|
||||||
|
local branch commit git_state
|
||||||
|
branch="$(git branch --show-current)"
|
||||||
|
commit="$(git rev-parse --short HEAD)"
|
||||||
|
if [[ -n "$(git status --short)" ]]; then
|
||||||
|
git_state="dirty"
|
||||||
|
else
|
||||||
|
git_state="clean"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Root: $ROOT_DIR"
|
||||||
|
echo "Git: $branch @ $commit ($git_state)"
|
||||||
|
|
||||||
|
if [[ -x "$ROOT_DIR/.venv/bin/python" ]]; then
|
||||||
|
local python_version removed_runtime json_path pid url
|
||||||
|
python_version="$("$ROOT_DIR/.venv/bin/python" -c 'import sys; print(sys.version.split()[0])')"
|
||||||
|
echo "Venv: present (Python $python_version)"
|
||||||
|
cleanup_stale_runtime_files >/dev/null
|
||||||
|
if ! have_running_server; then
|
||||||
|
removed_runtime="$(purge_runtime_artifacts 1 || true)"
|
||||||
|
else
|
||||||
|
removed_runtime=""
|
||||||
|
fi
|
||||||
|
json_path="$(server_json_for_root || true)"
|
||||||
|
|
||||||
|
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
||||||
|
echo "Browser runtime: installed"
|
||||||
|
else
|
||||||
|
echo "Browser runtime: missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
|
echo "Jupyter log: $LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "Jupyter log: none yet"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$json_path" ]]; then
|
||||||
|
pid="$(server_field "$json_path" "pid")"
|
||||||
|
url="$(server_url_from_json "$json_path")"
|
||||||
|
if kill -0 "$pid" >/dev/null 2>&1; then
|
||||||
|
echo "Jupyter: running (pid $pid)"
|
||||||
|
echo " URL: $url"
|
||||||
|
echo " Runtime file: $json_path"
|
||||||
|
else
|
||||||
|
echo "Jupyter: runtime file exists but process is not running"
|
||||||
|
echo " Runtime file: $json_path"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Jupyter: no repo-local runtime file found"
|
||||||
|
if [[ -n "${removed_runtime:-}" ]]; then
|
||||||
|
echo " Purged stale runtime artifacts:"
|
||||||
|
echo "$removed_runtime"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_ui_state_summary
|
||||||
|
else
|
||||||
|
echo "Venv: missing"
|
||||||
|
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
||||||
|
echo "Browser runtime: installed"
|
||||||
|
else
|
||||||
|
echo "Browser runtime: missing"
|
||||||
|
fi
|
||||||
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
|
echo "Jupyter log: $LOG_FILE"
|
||||||
|
else
|
||||||
|
echo "Jupyter log: none yet"
|
||||||
|
fi
|
||||||
|
echo "Jupyter: repo-local environment is missing"
|
||||||
|
print_ui_state_summary
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Consumer:"
|
||||||
|
echo " Start: bash ./scripts/app.sh start --open"
|
||||||
|
echo " Stop: bash ./scripts/app.sh stop"
|
||||||
|
echo " Restart: bash ./scripts/app.sh restart"
|
||||||
|
echo " Reset: bash ./scripts/app.sh reset-state"
|
||||||
|
echo " Logs: bash ./scripts/app.sh logs -f"
|
||||||
|
echo "Validation:"
|
||||||
|
echo " Quick: bash ./scripts/app.sh validate --quick"
|
||||||
|
echo " Full: bash ./scripts/app.sh validate --full"
|
||||||
|
}
|
||||||
|
|
||||||
start_command() {
|
start_command() {
|
||||||
local port="8888"
|
local port="8888"
|
||||||
local foreground="0"
|
local foreground="0"
|
||||||
|
|
@ -441,11 +660,17 @@ case "$COMMAND" in
|
||||||
stop)
|
stop)
|
||||||
stop_server
|
stop_server
|
||||||
;;
|
;;
|
||||||
|
reset-state)
|
||||||
|
reset_state_command "$@"
|
||||||
|
;;
|
||||||
restart)
|
restart)
|
||||||
restart_command "$@"
|
restart_command "$@"
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
exec bash "$ROOT_DIR/scripts/project_status.sh"
|
status_command
|
||||||
|
;;
|
||||||
|
_status_internal)
|
||||||
|
status_command
|
||||||
;;
|
;;
|
||||||
url)
|
url)
|
||||||
ensure_venv
|
ensure_venv
|
||||||
|
|
|
||||||
77
scripts/project_status.sh
Executable file → Normal file
77
scripts/project_status.sh
Executable file → Normal file
|
|
@ -8,8 +8,8 @@ usage() {
|
||||||
Usage: bash ./scripts/project_status.sh
|
Usage: bash ./scripts/project_status.sh
|
||||||
|
|
||||||
Print the current local operational state of the QuantumLearning repo:
|
Print the current local operational state of the QuantumLearning repo:
|
||||||
git state, repo-local Python environment, Playwright browser runtime, and
|
git state, repo-local Python environment, Playwright browser runtime,
|
||||||
the latest repo-local Jupyter server if one is running.
|
repo-local Jupyter status, and saved Jupyter UI state.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,75 +18,4 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
exec bash "$ROOT_DIR/scripts/app.sh" _status_internal
|
||||||
|
|
||||||
BRANCH="$(git branch --show-current)"
|
|
||||||
COMMIT="$(git rev-parse --short HEAD)"
|
|
||||||
if [[ -n "$(git status --short)" ]]; then
|
|
||||||
GIT_STATE="dirty"
|
|
||||||
else
|
|
||||||
GIT_STATE="clean"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Root: $ROOT_DIR"
|
|
||||||
echo "Git: $BRANCH @ $COMMIT ($GIT_STATE)"
|
|
||||||
|
|
||||||
if [[ -x "$ROOT_DIR/.venv/bin/python" ]]; then
|
|
||||||
PYTHON_VERSION="$("$ROOT_DIR/.venv/bin/python" -c 'import sys; print(sys.version.split()[0])')"
|
|
||||||
echo "Venv: present (Python $PYTHON_VERSION)"
|
|
||||||
else
|
|
||||||
echo "Venv: missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
|
||||||
echo "Browser runtime: installed"
|
|
||||||
else
|
|
||||||
echo "Browser runtime: missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "$ROOT_DIR/.logs/jupyterlab.log" ]]; then
|
|
||||||
echo "Jupyter log: $ROOT_DIR/.logs/jupyterlab.log"
|
|
||||||
else
|
|
||||||
echo "Jupyter log: none yet"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if compgen -G "$ROOT_DIR/.jupyter_runtime/jpserver-*.json" >/dev/null; then
|
|
||||||
LATEST_SERVER_JSON="$(ls -t "$ROOT_DIR"/.jupyter_runtime/jpserver-*.json | head -n 1)"
|
|
||||||
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$LATEST_SERVER_JSON"
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
path = Path(sys.argv[1])
|
|
||||||
data = json.loads(path.read_text())
|
|
||||||
pid = int(data["pid"])
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.kill(pid, 0)
|
|
||||||
except ProcessLookupError:
|
|
||||||
print("Jupyter: runtime file exists but process is not running")
|
|
||||||
print(f" Runtime file: {path}")
|
|
||||||
except PermissionError:
|
|
||||||
print("Jupyter: runtime file present; live process probe is unavailable in this environment")
|
|
||||||
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
|
|
||||||
print(f" Runtime file: {path}")
|
|
||||||
else:
|
|
||||||
print(f"Jupyter: running (pid {pid})")
|
|
||||||
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
|
|
||||||
print(f" Runtime file: {path}")
|
|
||||||
PY
|
|
||||||
else
|
|
||||||
echo "Jupyter: no repo-local runtime file found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Consumer:"
|
|
||||||
echo " Start: bash ./scripts/app.sh start --open"
|
|
||||||
echo " Stop: bash ./scripts/app.sh stop"
|
|
||||||
echo " Restart: bash ./scripts/app.sh restart"
|
|
||||||
echo " Logs: bash ./scripts/app.sh logs -f"
|
|
||||||
echo "Validation:"
|
|
||||||
echo " Quick: bash ./scripts/app.sh validate --quick"
|
|
||||||
echo " Full: bash ./scripts/app.sh validate --full"
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ def test_project_status_script_runs():
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert "Git:" in result.stdout
|
assert "Git:" in result.stdout
|
||||||
assert "Validation:" in result.stdout
|
assert "Validation:" in result.stdout
|
||||||
|
assert "Jupyter UI state:" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_app_status_runs():
|
def test_app_status_runs():
|
||||||
|
|
@ -69,12 +70,15 @@ def test_app_status_runs():
|
||||||
|
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert "Consumer:" in result.stdout
|
assert "Consumer:" in result.stdout
|
||||||
|
assert "Reset:" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_app_script_uses_detached_launch_and_stale_runtime_cleanup():
|
def test_app_script_uses_detached_launch_and_stale_runtime_cleanup():
|
||||||
text = (project_root() / "scripts" / "app.sh").read_text()
|
text = (project_root() / "scripts" / "app.sh").read_text()
|
||||||
|
|
||||||
assert "cleanup_stale_runtime_files" in text
|
assert "cleanup_stale_runtime_files" in text
|
||||||
|
assert "purge_runtime_artifacts" in text
|
||||||
|
assert "reset-state" in text
|
||||||
assert "nohup " in text
|
assert "nohup " in text
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,6 +87,7 @@ def test_operations_docs_use_tested_command_forms():
|
||||||
expected_commands = [
|
expected_commands = [
|
||||||
"bash ./scripts/app.sh bootstrap",
|
"bash ./scripts/app.sh bootstrap",
|
||||||
"bash ./scripts/app.sh status",
|
"bash ./scripts/app.sh status",
|
||||||
|
"bash ./scripts/app.sh reset-state",
|
||||||
"bash ./scripts/app.sh validate --quick",
|
"bash ./scripts/app.sh validate --quick",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -90,3 +95,10 @@ def test_operations_docs_use_tested_command_forms():
|
||||||
text = (root / relative_path).read_text()
|
text = (root / relative_path).read_text()
|
||||||
for command in expected_commands:
|
for command in expected_commands:
|
||||||
assert command in text
|
assert command in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_jupyter_runtime_noise_is_ignored():
|
||||||
|
text = (project_root() / ".gitignore").read_text()
|
||||||
|
|
||||||
|
assert ".jupyter_data/nbsignatures.db" in text
|
||||||
|
assert ".jupyter_data/notebook_secret" in text
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue