dev-bubble 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/.claude/CLAUDE.md +26 -0
- dev_bubble-0.2.1/.github/workflows/publish.yml +35 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/PKG-INFO +6 -4
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/README.md +5 -3
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/cli.py +26 -12
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/hooks/lean.py +1 -1
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/images/scripts/base.sh +3 -8
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/network.py +1 -1
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/relay.py +27 -3
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/repo_registry.py +1 -1
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/PKG-INFO +6 -4
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/SOURCES.txt +1 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/pyproject.toml +1 -1
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_target.py +0 -3
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/.github/workflows/ci.yml +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/.gitignore +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/LICENSE +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/__init__.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/automation.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/clean.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/config.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/git_store.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/hooks/__init__.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/images/__init__.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/images/builder.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/images/scripts/lean-toolchain.sh +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/images/scripts/lean.sh +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/lifecycle.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/naming.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/runtime/__init__.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/runtime/base.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/runtime/colima.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/runtime/incus.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/target.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/bubble/vscode.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/claude-skill/SKILL.md +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/config/com.bubble.git-update.plist +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/config/com.bubble.image-refresh.plist +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/config/com.bubble.relay-daemon.plist +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/dependency_links.txt +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/entry_points.txt +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/requires.txt +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/dev_bubble.egg-info/top_level.txt +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/setup.cfg +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/conftest.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_config.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_git_store.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_hooks.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_integration.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_lifecycle.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_naming.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_network.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_relay.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_repo_registry.py +0 -0
- {dev_bubble-0.2.0 → dev_bubble-0.2.1}/tests/test_vscode.py +0 -0
|
@@ -164,3 +164,29 @@ VS Code must be restarted after modifying this database.
|
|
|
164
164
|
|
|
165
165
|
### Pre-baked VS Code Server
|
|
166
166
|
The base image pre-installs the VS Code Server binary matching the host's `code --version` commit hash. On each `bubble open`, if the hash has changed (VS Code updated), a background `bubble images build base` is triggered. The current bubble proceeds immediately; the next one gets the pre-baked server.
|
|
167
|
+
|
|
168
|
+
## PyPI Publishing
|
|
169
|
+
|
|
170
|
+
The package is published to PyPI as **`dev-bubble`** (the CLI command is still `bubble`). Users install with `pipx install dev-bubble` or `uv tool install dev-bubble`.
|
|
171
|
+
|
|
172
|
+
### Releasing a New Version
|
|
173
|
+
|
|
174
|
+
When making changes that warrant a release (new features, bug fixes, improvements), create a new version tag:
|
|
175
|
+
|
|
176
|
+
1. Bump `version` in `pyproject.toml` (use semver: patch for fixes, minor for features)
|
|
177
|
+
2. Commit the version bump
|
|
178
|
+
3. Tag and push:
|
|
179
|
+
```bash
|
|
180
|
+
git tag v0.X.Y
|
|
181
|
+
git push origin v0.X.Y
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The `.github/workflows/publish.yml` workflow runs tests then publishes to PyPI automatically via trusted publishing (no API tokens needed).
|
|
185
|
+
|
|
186
|
+
### When to Release
|
|
187
|
+
|
|
188
|
+
**Proactively tag a new minor/patch version** after completing work that changes user-visible behavior. Don't let changes accumulate unreleased — small frequent releases are preferred. If you've just made a meaningful change, bump the version and tag it.
|
|
189
|
+
|
|
190
|
+
### Trusted Publisher Setup
|
|
191
|
+
|
|
192
|
+
PyPI is configured to trust GitHub Actions from `kim-em/bubble` with the `publish.yml` workflow and `pypi` environment. No secrets or API tokens are stored in the repository.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write # Required for trusted publishing
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- run: pip install -e ".[dev]"
|
|
21
|
+
- run: ruff check bubble/ tests/
|
|
22
|
+
- run: pytest -v -m "not integration"
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: test
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment: pypi
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.12"
|
|
33
|
+
- run: pip install build
|
|
34
|
+
- run: python -m build
|
|
35
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dev-bubble
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Containerized development environments powered by Incus
|
|
5
5
|
Author-email: Kim Morrison <kim@tqft.net>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -37,9 +37,11 @@ Containerized development environments for the Lean language, powered by [Incus]
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
# Install
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
# Install
|
|
41
|
+
uv tool install git+https://github.com/kim-em/bubble.git
|
|
42
|
+
|
|
43
|
+
# Also available on PyPI: uv tool install dev-bubble
|
|
44
|
+
# For development: uv pip install -e '.[dev]'
|
|
43
45
|
|
|
44
46
|
# Open a bubble for a GitHub PR — just paste the URL, and you get a containerized VSCode window!
|
|
45
47
|
bubble https://github.com/leanprover-community/mathlib4/pull/35219
|
|
@@ -5,9 +5,11 @@ Containerized development environments for the Lean language, powered by [Incus]
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Install
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
# Install
|
|
9
|
+
uv tool install git+https://github.com/kim-em/bubble.git
|
|
10
|
+
|
|
11
|
+
# Also available on PyPI: uv tool install dev-bubble
|
|
12
|
+
# For development: uv pip install -e '.[dev]'
|
|
11
13
|
|
|
12
14
|
# Open a bubble for a GitHub PR — just paste the URL, and you get a containerized VSCode window!
|
|
13
15
|
bubble https://github.com/leanprover-community/mathlib4/pull/35219
|
|
@@ -12,11 +12,11 @@ from pathlib import Path
|
|
|
12
12
|
import click
|
|
13
13
|
|
|
14
14
|
from . import __version__
|
|
15
|
-
from .
|
|
15
|
+
from .clean import CleanStatus, check_clean, format_reasons
|
|
16
|
+
from .config import ensure_dirs, load_config, repo_short_name, save_config
|
|
16
17
|
from .git_store import bare_repo_path, ensure_repo, fetch_ref, github_url, update_all_repos
|
|
17
18
|
from .hooks import select_hook
|
|
18
19
|
from .images.builder import VSCODE_COMMIT_FILE, get_vscode_commit
|
|
19
|
-
from .clean import CleanStatus, check_clean, format_reasons
|
|
20
20
|
from .lifecycle import load_registry, register_bubble, unregister_bubble
|
|
21
21
|
from .naming import deduplicate_name, generate_name
|
|
22
22
|
from .repo_registry import RepoRegistry
|
|
@@ -669,8 +669,8 @@ def _provision_container(runtime, name, image_name, ref_path, mount_name, config
|
|
|
669
669
|
connect=connect_addr,
|
|
670
670
|
listen="unix:/bubble/relay.sock",
|
|
671
671
|
bind="container",
|
|
672
|
-
uid="
|
|
673
|
-
gid="
|
|
672
|
+
uid="1001",
|
|
673
|
+
gid="1001",
|
|
674
674
|
)
|
|
675
675
|
from .relay import generate_relay_token
|
|
676
676
|
|
|
@@ -681,14 +681,17 @@ def _provision_container(runtime, name, image_name, ref_path, mount_name, config
|
|
|
681
681
|
"bash",
|
|
682
682
|
"-c",
|
|
683
683
|
f"echo {shlex.quote(token)} > /bubble/relay-token"
|
|
684
|
-
" && chown
|
|
684
|
+
" && chown user:user /bubble/relay-token"
|
|
685
685
|
" && chmod 600 /bubble/relay-token",
|
|
686
686
|
],
|
|
687
687
|
)
|
|
688
688
|
|
|
689
689
|
|
|
690
690
|
def _get_pr_metadata(owner: str, repo: str, pr_number: str) -> tuple[str, str, str] | None:
|
|
691
|
-
"""Query GitHub API for PR head branch info.
|
|
691
|
+
"""Query GitHub API for PR head branch info.
|
|
692
|
+
|
|
693
|
+
Returns (head_ref, head_repo, clone_url) or None.
|
|
694
|
+
"""
|
|
692
695
|
try:
|
|
693
696
|
result = subprocess.run(
|
|
694
697
|
[
|
|
@@ -967,7 +970,7 @@ def _format_bytes(n: int) -> str:
|
|
|
967
970
|
return f"{n:.1f} PB"
|
|
968
971
|
|
|
969
972
|
|
|
970
|
-
def _format_age(dt: "datetime | None") -> str:
|
|
973
|
+
def _format_age(dt: "datetime | None") -> str: # noqa: F821
|
|
971
974
|
"""Format a datetime as a human-readable age string."""
|
|
972
975
|
if dt is None:
|
|
973
976
|
return "-"
|
|
@@ -1042,16 +1045,23 @@ def list_bubbles(as_json, verbose, show_clean):
|
|
|
1042
1045
|
|
|
1043
1046
|
if verbose:
|
|
1044
1047
|
if show_clean:
|
|
1045
|
-
click.echo(
|
|
1048
|
+
click.echo(
|
|
1049
|
+
f"{'NAME':<30} {'STATE':<10} {'CREATED':<12} {'LAST USED':<12}"
|
|
1050
|
+
f" {'DISK':<10} {'IPv4':<16} {'STATUS'}"
|
|
1051
|
+
)
|
|
1046
1052
|
click.echo("-" * 110)
|
|
1047
1053
|
else:
|
|
1048
|
-
click.echo(
|
|
1054
|
+
click.echo(
|
|
1055
|
+
f"{'NAME':<30} {'STATE':<10} {'CREATED':<12} {'LAST USED':<12}"
|
|
1056
|
+
f" {'DISK':<10} {'IPv4':<16}"
|
|
1057
|
+
)
|
|
1049
1058
|
click.echo("-" * 90)
|
|
1050
1059
|
for c in containers:
|
|
1051
1060
|
disk = _format_bytes(c.disk_usage) if c.disk_usage else "-"
|
|
1052
1061
|
created = _format_age(c.created_at)
|
|
1053
1062
|
used = _format_age(c.last_used_at)
|
|
1054
|
-
|
|
1063
|
+
ipv4 = c.ipv4 or "-"
|
|
1064
|
+
line = f"{c.name:<30} {c.state:<10} {created:<12} {used:<12} {disk:<10} {ipv4:<16}"
|
|
1055
1065
|
if show_clean:
|
|
1056
1066
|
cs = clean_statuses.get(c.name)
|
|
1057
1067
|
line += f" {cs.summary}" if cs else ""
|
|
@@ -1140,7 +1150,10 @@ def destroy(name, force):
|
|
|
1140
1150
|
@main.command()
|
|
1141
1151
|
@click.option("-n", "--dry-run", is_flag=True, help="Show what would be destroyed")
|
|
1142
1152
|
@click.option("-f", "--force", is_flag=True, help="Skip confirmation prompt")
|
|
1143
|
-
@click.option(
|
|
1153
|
+
@click.option(
|
|
1154
|
+
"-a", "--all", "check_all", is_flag=True,
|
|
1155
|
+
help="Start stopped/frozen bubbles to check them",
|
|
1156
|
+
)
|
|
1144
1157
|
@click.option("--age", type=int, default=0, help="Only clean up bubbles unused for N+ days")
|
|
1145
1158
|
def cleanup(dry_run, force, check_all, age):
|
|
1146
1159
|
"""Destroy all clean bubbles (safe, no unsaved work)."""
|
|
@@ -1210,7 +1223,8 @@ def cleanup(dry_run, force, check_all, age):
|
|
|
1210
1223
|
return
|
|
1211
1224
|
|
|
1212
1225
|
if dry_run:
|
|
1213
|
-
|
|
1226
|
+
n = len(clean_list)
|
|
1227
|
+
click.echo(f"\nWould destroy {n} clean bubble{'s' if n != 1 else ''}.")
|
|
1214
1228
|
# Re-stop clean containers that were started for checking
|
|
1215
1229
|
for name in clean_list:
|
|
1216
1230
|
if name in started_names:
|
|
@@ -7,8 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
|
|
10
|
-
from . import Hook
|
|
11
10
|
from ..runtime.base import ContainerRuntime
|
|
11
|
+
from . import Hook
|
|
12
12
|
|
|
13
13
|
# Matches stable releases (v4.16.0) and release candidates (v4.16.0-rc2)
|
|
14
14
|
_STABLE_OR_RC_RE = re.compile(r"^v\d+\.\d+\.\d+(-rc\d+)?$")
|
|
@@ -73,14 +73,9 @@ def main():
|
|
|
73
73
|
s.settimeout(30)
|
|
74
74
|
s.connect(sock_path)
|
|
75
75
|
request = json.dumps({"target": target, "token": token})
|
|
76
|
-
s.sendall(request.encode()
|
|
77
|
-
|
|
78
|
-
data =
|
|
79
|
-
while True:
|
|
80
|
-
chunk = s.recv(4096)
|
|
81
|
-
if not chunk:
|
|
82
|
-
break
|
|
83
|
-
data += chunk
|
|
76
|
+
s.sendall(request.encode())
|
|
77
|
+
# Read response (single recv — response is always < 4KB)
|
|
78
|
+
data = s.recv(4096)
|
|
84
79
|
response = json.loads(data)
|
|
85
80
|
print(response.get("message", ""))
|
|
86
81
|
sys.exit(0 if response.get("status") == "ok" else 1)
|
|
@@ -95,7 +95,7 @@ def _build_allowlist_script(domains: list[str]) -> str:
|
|
|
95
95
|
resolve_domain = domain[2:]
|
|
96
96
|
lines.append(f"IPS=$(getent ahostsv4 {resolve_domain} 2>/dev/null"
|
|
97
97
|
" | awk '{print $1}' | sort -u)")
|
|
98
|
-
lines.append(
|
|
98
|
+
lines.append('if [ -z "$IPS" ]; then')
|
|
99
99
|
lines.append(f' echo "Warning: wildcard domain {domain} did not resolve.'
|
|
100
100
|
f' Use explicit subdomains instead." >&2')
|
|
101
101
|
lines.append("else")
|
|
@@ -28,7 +28,6 @@ import threading
|
|
|
28
28
|
import time
|
|
29
29
|
from collections import deque
|
|
30
30
|
from concurrent.futures import ThreadPoolExecutor
|
|
31
|
-
from pathlib import Path
|
|
32
31
|
|
|
33
32
|
from .config import DATA_DIR
|
|
34
33
|
from .git_store import repo_is_known
|
|
@@ -196,6 +195,10 @@ def validate_relay_target(target: str) -> tuple[str, str]:
|
|
|
196
195
|
if target.startswith((".", "/", "~")):
|
|
197
196
|
return "error", "Local paths are not allowed via relay."
|
|
198
197
|
|
|
198
|
+
# Reject targets starting with '-' to prevent CLI option injection
|
|
199
|
+
if target.startswith("-"):
|
|
200
|
+
return "error", "Invalid target."
|
|
201
|
+
|
|
199
202
|
if "--path" in target:
|
|
200
203
|
return "error", "The --path flag is not allowed via relay."
|
|
201
204
|
|
|
@@ -350,12 +353,20 @@ def _open_bubble(target: str, runtime_factory):
|
|
|
350
353
|
import subprocess
|
|
351
354
|
|
|
352
355
|
subprocess.Popen(
|
|
353
|
-
["bubble", "open", "--no-clone", target],
|
|
356
|
+
["bubble", "open", "--no-clone", "--no-interactive", target],
|
|
354
357
|
stdout=subprocess.DEVNULL,
|
|
355
358
|
stderr=subprocess.DEVNULL,
|
|
356
359
|
)
|
|
357
360
|
|
|
358
361
|
|
|
362
|
+
def _guarded_handle(semaphore, conn, rate_limiter, token_registry, runtime_factory):
|
|
363
|
+
"""Wrapper that releases the handler semaphore after connection handling."""
|
|
364
|
+
try:
|
|
365
|
+
_handle_connection(conn, rate_limiter, token_registry, runtime_factory)
|
|
366
|
+
finally:
|
|
367
|
+
semaphore.release()
|
|
368
|
+
|
|
369
|
+
|
|
359
370
|
def run_daemon(runtime_factory=None):
|
|
360
371
|
"""Run the relay daemon.
|
|
361
372
|
|
|
@@ -390,6 +401,9 @@ def run_daemon(runtime_factory=None):
|
|
|
390
401
|
rate_limiter = RateLimiter()
|
|
391
402
|
token_registry = TokenRegistry()
|
|
392
403
|
executor = ThreadPoolExecutor(max_workers=MAX_CONCURRENT_HANDLERS)
|
|
404
|
+
# Pre-auth connection cap: reject new connections when all handler slots
|
|
405
|
+
# are busy. Prevents unauthenticated DoS from blocking legitimate requests.
|
|
406
|
+
handler_semaphore = threading.Semaphore(MAX_CONCURRENT_HANDLERS)
|
|
393
407
|
|
|
394
408
|
logger.info("Relay daemon started on %s", listen_addr)
|
|
395
409
|
print(f"Relay daemon listening on {listen_addr}")
|
|
@@ -397,7 +411,17 @@ def run_daemon(runtime_factory=None):
|
|
|
397
411
|
try:
|
|
398
412
|
while True:
|
|
399
413
|
conn, _ = server.accept()
|
|
400
|
-
|
|
414
|
+
if not handler_semaphore.acquire(blocking=False):
|
|
415
|
+
# All handler slots busy — drop the connection immediately
|
|
416
|
+
try:
|
|
417
|
+
conn.close()
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
continue
|
|
421
|
+
executor.submit(
|
|
422
|
+
_guarded_handle, handler_semaphore, conn,
|
|
423
|
+
rate_limiter, token_registry, runtime_factory,
|
|
424
|
+
)
|
|
401
425
|
except KeyboardInterrupt:
|
|
402
426
|
logger.info("Relay daemon stopped")
|
|
403
427
|
finally:
|
|
@@ -8,7 +8,7 @@ from .config import REPOS_FILE
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class RepoRegistry:
|
|
11
|
-
"""Maps short repo names
|
|
11
|
+
"""Maps short repo names to full owner/repo pairs.
|
|
12
12
|
|
|
13
13
|
Repos are learned on first use and stored in ~/.bubble/repos.json.
|
|
14
14
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dev-bubble
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Containerized development environments powered by Incus
|
|
5
5
|
Author-email: Kim Morrison <kim@tqft.net>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -37,9 +37,11 @@ Containerized development environments for the Lean language, powered by [Incus]
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
# Install
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
# Install
|
|
41
|
+
uv tool install git+https://github.com/kim-em/bubble.git
|
|
42
|
+
|
|
43
|
+
# Also available on PyPI: uv tool install dev-bubble
|
|
44
|
+
# For development: uv pip install -e '.[dev]'
|
|
43
45
|
|
|
44
46
|
# Open a bubble for a GitHub PR — just paste the URL, and you get a containerized VSCode window!
|
|
45
47
|
bubble https://github.com/leanprover-community/mathlib4/pull/35219
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"""Tests for the target parsing module."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import subprocess
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
7
6
|
|
|
8
7
|
from bubble.repo_registry import RepoRegistry
|
|
9
8
|
from bubble.target import (
|
|
10
|
-
Target,
|
|
11
9
|
TargetParseError,
|
|
12
|
-
_git_repo_info,
|
|
13
10
|
_parse_github_remote,
|
|
14
11
|
_parse_local_path,
|
|
15
12
|
parse_target,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|