repro-cli 0.1.0__tar.gz → 0.2.0__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.
- repro_cli-0.2.0/PKG-INFO +139 -0
- repro_cli-0.2.0/README.md +130 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/pyproject.toml +1 -1
- repro_cli-0.2.0/repro/docker/runner.py +290 -0
- repro_cli-0.2.0/repro_cli.egg-info/PKG-INFO +139 -0
- repro_cli-0.1.0/PKG-INFO +0 -8
- repro_cli-0.1.0/README.md +0 -0
- repro_cli-0.1.0/repro/docker/runner.py +0 -182
- repro_cli-0.1.0/repro_cli.egg-info/PKG-INFO +0 -8
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/__init__.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/cli.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/detector/__init__.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/detector/detector.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/docker/__init__.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/github/__init__.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro/github/issue.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro_cli.egg-info/SOURCES.txt +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro_cli.egg-info/dependency_links.txt +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro_cli.egg-info/entry_points.txt +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/repro_cli.egg-info/top_level.txt +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/setup.cfg +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/tests/test_port_parsing.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/tests/test_repo_validation.py +0 -0
- {repro_cli-0.1.0 → repro_cli-0.2.0}/tests/test_runtime_detection.py +0 -0
repro_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repro-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Disposable Docker environments for any GitHub issue
|
|
5
|
+
Author: Sukhdev Thukral
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# REPRO
|
|
11
|
+
|
|
12
|
+
> Disposable Docker environments for any GitHub issue.
|
|
13
|
+
One command. No setup.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install repro-cli
|
|
17
|
+
repro https://github.com/org/repo/issues/42
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it, your terminal boots an isolated sandbox - repo cloned, dependencies installed and IF you have VS Code, it opens automatically **attached directly to the container** so you get a real editor and a single unified terminal, not just a shell. Type `exit` and the container disappears. Your files stay on disk, exactly where they were.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install repro-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Requirements:**
|
|
31
|
+
- Python 3.9+
|
|
32
|
+
- [Docker](https://docs.docker.com/get-docker/) (INSTALLED AND RUNNING)
|
|
33
|
+
- VS Code (optional, but recommended - enables the unified editor/terminal experience below)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Open a sandbox for any publish GitHub issue
|
|
41
|
+
repro https://github.com/org/repo/issues/123
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
#Forward a port (e.g. to view a web app running inside the sandbox)
|
|
46
|
+
repro https://github.com/org/repo/issues/123 -p 3000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Map a different host port to a container port
|
|
51
|
+
repro https://github.com/org/repo/issues/123 -p 8080:3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Private repos - pass a GitHub token
|
|
56
|
+
repro https://github.com/org/private-repo/issues/7 --token ghp_xxx
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
#Or set it once as an env var instead of typing it every single time!
|
|
61
|
+
export DEVSANDBOX_GITHUB_TOKEN=ghp_xxx
|
|
62
|
+
repro https://github.com/org/private-repo/issues/7
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Skip auto-opening an editor
|
|
67
|
+
repro https://github.com/org/repo/issues/123 --no-editor
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
#Help
|
|
72
|
+
repro --help
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What it does
|
|
76
|
+
|
|
77
|
+
1. **Parses** the GitHub issue URL
|
|
78
|
+
2. **Detects** the repo's runtime - Node, Python, Go, Rust, Ruby, PHP, Java, or a devcontainer, based on whats actually in the repo.
|
|
79
|
+
3. **Spins up** a disposable, isolated Docker container with the right base image.
|
|
80
|
+
4. **Bind-mounts** a real folder on your machine into the container, so nothing is trapped - files persist after you exit
|
|
81
|
+
5. **Clones** the repo directly into that shared folder and install dependencies
|
|
82
|
+
6. **Attaches VS Code directly to the running container** (auto-installing the Dev Containers extension if needed) - one window, one terminal, running *inside* the sandbox.
|
|
83
|
+
7. *Destroys* the container the moment you exit. Your edited files remain exactly where they were.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## The unified editor experience
|
|
88
|
+
|
|
89
|
+
- **Real editor** — VS Code attaches directly into the container, so its built-in terminal *is* the sandbox shell. No second window to juggle.
|
|
90
|
+
- **Real persistence** — because the folder is bind-mounted, not just cloned inside the container, your work survives after `exit`. Nothing is lost.
|
|
91
|
+
If VS Code isn't installed, `repro` falls back gracefully — you still get a real folder path printed at the end, ready to open in whatever editor you use.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Supported runtimes
|
|
96
|
+
| File detected | Runtime | Base image |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `package.json` | Node.js | `node:20-alpine` |
|
|
99
|
+
| `yarn.lock` | Node.js (yarn) | `node:20-alpine` |
|
|
100
|
+
| `pnpm-lock.yaml` | Node.js (pnpm) | `node:20-alpine` |
|
|
101
|
+
| `requirements.txt` | Python | `python:3.12-slim` |
|
|
102
|
+
| `pyproject.toml` | Python | `python:3.12-slim` |
|
|
103
|
+
| `Pipfile` | Python (pipenv) | `python:3.12-slim` |
|
|
104
|
+
| `go.mod` | Go | `golang:1.21-alpine` |
|
|
105
|
+
| `Cargo.toml` | Rust | `rust:1.75-slim` |
|
|
106
|
+
| `Gemfile` | Ruby | `ruby:3.3-slim` |
|
|
107
|
+
| `composer.json` | PHP | `php:8.3-cli` |
|
|
108
|
+
| `pom.xml` | Java (Maven) | `maven:3.9-eclipse-temurin-21` |
|
|
109
|
+
| `build.gradle` | Java (Gradle) | `gradle:8-jdk21` |
|
|
110
|
+
| `.devcontainer` | devcontainer | MS universal image |
|
|
111
|
+
| *(none matched)* | Universal fallback | `ubuntu:22.04` |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## The GitHub App
|
|
116
|
+
|
|
117
|
+
Install [**repro-sandbox-bot**](https://github.com/apps/repro-sandbox-bot) on any repo and it automatically comments the exact `repro` command on every new issue - so contributors never have to think about setup.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
🏖 Open this issue in DevSandbox
|
|
121
|
+
|
|
122
|
+
Spin up a disposable Docker environment with this repo already checked out:
|
|
123
|
+
|
|
124
|
+
repro https://github.com/org/repo/issues/42
|
|
125
|
+
|
|
126
|
+
Don't have repro yet? Install it here.
|
|
127
|
+
|
|
128
|
+
Comment /sandbox on any issue to get this again.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also trigger it on demand by commenting `/sandbox` on any issue. The bot runs 24/7, independent of your machine.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
## Contributing
|
|
135
|
+
PRs welcome. Open an issue first for anything beyond small fixes - and yes, you can use `repro` itself to work on `repro`.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# REPRO
|
|
2
|
+
|
|
3
|
+
> Disposable Docker environments for any GitHub issue.
|
|
4
|
+
One command. No setup.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install repro-cli
|
|
8
|
+
repro https://github.com/org/repo/issues/42
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That's it, your terminal boots an isolated sandbox - repo cloned, dependencies installed and IF you have VS Code, it opens automatically **attached directly to the container** so you get a real editor and a single unified terminal, not just a shell. Type `exit` and the container disappears. Your files stay on disk, exactly where they were.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install repro-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Requirements:**
|
|
22
|
+
- Python 3.9+
|
|
23
|
+
- [Docker](https://docs.docker.com/get-docker/) (INSTALLED AND RUNNING)
|
|
24
|
+
- VS Code (optional, but recommended - enables the unified editor/terminal experience below)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Open a sandbox for any publish GitHub issue
|
|
32
|
+
repro https://github.com/org/repo/issues/123
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
#Forward a port (e.g. to view a web app running inside the sandbox)
|
|
37
|
+
repro https://github.com/org/repo/issues/123 -p 3000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Map a different host port to a container port
|
|
42
|
+
repro https://github.com/org/repo/issues/123 -p 8080:3000
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Private repos - pass a GitHub token
|
|
47
|
+
repro https://github.com/org/private-repo/issues/7 --token ghp_xxx
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
#Or set it once as an env var instead of typing it every single time!
|
|
52
|
+
export DEVSANDBOX_GITHUB_TOKEN=ghp_xxx
|
|
53
|
+
repro https://github.com/org/private-repo/issues/7
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Skip auto-opening an editor
|
|
58
|
+
repro https://github.com/org/repo/issues/123 --no-editor
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
#Help
|
|
63
|
+
repro --help
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What it does
|
|
67
|
+
|
|
68
|
+
1. **Parses** the GitHub issue URL
|
|
69
|
+
2. **Detects** the repo's runtime - Node, Python, Go, Rust, Ruby, PHP, Java, or a devcontainer, based on whats actually in the repo.
|
|
70
|
+
3. **Spins up** a disposable, isolated Docker container with the right base image.
|
|
71
|
+
4. **Bind-mounts** a real folder on your machine into the container, so nothing is trapped - files persist after you exit
|
|
72
|
+
5. **Clones** the repo directly into that shared folder and install dependencies
|
|
73
|
+
6. **Attaches VS Code directly to the running container** (auto-installing the Dev Containers extension if needed) - one window, one terminal, running *inside* the sandbox.
|
|
74
|
+
7. *Destroys* the container the moment you exit. Your edited files remain exactly where they were.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## The unified editor experience
|
|
79
|
+
|
|
80
|
+
- **Real editor** — VS Code attaches directly into the container, so its built-in terminal *is* the sandbox shell. No second window to juggle.
|
|
81
|
+
- **Real persistence** — because the folder is bind-mounted, not just cloned inside the container, your work survives after `exit`. Nothing is lost.
|
|
82
|
+
If VS Code isn't installed, `repro` falls back gracefully — you still get a real folder path printed at the end, ready to open in whatever editor you use.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Supported runtimes
|
|
87
|
+
| File detected | Runtime | Base image |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `package.json` | Node.js | `node:20-alpine` |
|
|
90
|
+
| `yarn.lock` | Node.js (yarn) | `node:20-alpine` |
|
|
91
|
+
| `pnpm-lock.yaml` | Node.js (pnpm) | `node:20-alpine` |
|
|
92
|
+
| `requirements.txt` | Python | `python:3.12-slim` |
|
|
93
|
+
| `pyproject.toml` | Python | `python:3.12-slim` |
|
|
94
|
+
| `Pipfile` | Python (pipenv) | `python:3.12-slim` |
|
|
95
|
+
| `go.mod` | Go | `golang:1.21-alpine` |
|
|
96
|
+
| `Cargo.toml` | Rust | `rust:1.75-slim` |
|
|
97
|
+
| `Gemfile` | Ruby | `ruby:3.3-slim` |
|
|
98
|
+
| `composer.json` | PHP | `php:8.3-cli` |
|
|
99
|
+
| `pom.xml` | Java (Maven) | `maven:3.9-eclipse-temurin-21` |
|
|
100
|
+
| `build.gradle` | Java (Gradle) | `gradle:8-jdk21` |
|
|
101
|
+
| `.devcontainer` | devcontainer | MS universal image |
|
|
102
|
+
| *(none matched)* | Universal fallback | `ubuntu:22.04` |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## The GitHub App
|
|
107
|
+
|
|
108
|
+
Install [**repro-sandbox-bot**](https://github.com/apps/repro-sandbox-bot) on any repo and it automatically comments the exact `repro` command on every new issue - so contributors never have to think about setup.
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
🏖 Open this issue in DevSandbox
|
|
112
|
+
|
|
113
|
+
Spin up a disposable Docker environment with this repo already checked out:
|
|
114
|
+
|
|
115
|
+
repro https://github.com/org/repo/issues/42
|
|
116
|
+
|
|
117
|
+
Don't have repro yet? Install it here.
|
|
118
|
+
|
|
119
|
+
Comment /sandbox on any issue to get this again.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
You can also trigger it on demand by commenting `/sandbox` on any issue. The bot runs 24/7, independent of your machine.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
## Contributing
|
|
126
|
+
PRs welcome. Open an issue first for anything beyond small fixes - and yes, you can use `repro` itself to work on `repro`.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
class DockerNotFoundError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
class DockerNotRunningError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class InvalidPortError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class InvalidRepoError(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
22
|
+
|
|
23
|
+
def check_docker_available():
|
|
24
|
+
"""Verify Docker CLI exists and the daemon is reachable before trying anythg"""
|
|
25
|
+
if shutil.which("docker") is None:
|
|
26
|
+
raise DockerNotFoundError(
|
|
27
|
+
"Docker is not installed or not on your PATH.\n"
|
|
28
|
+
" Install it from: https://docs.docker.com/get-docker/"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
result = subprocess.run(["docker", "info"], capture_output=True, text=True)
|
|
32
|
+
if result.returncode != 0:
|
|
33
|
+
raise DockerNotRunningError(
|
|
34
|
+
"Docker is installed but the daemon isnt running.\n"
|
|
35
|
+
" Start Docker Desktop and wait for the icon to go steady, then try again."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def validate_repo_identifiers(owner: str, repo:str):
|
|
40
|
+
""" Guard against shell injection -> owner/repo gets interpolated into a shell command, so anything outside GitHub's allowed charst is rejected
|
|
41
|
+
before it even reaches da subprocess"""
|
|
42
|
+
for label, value in (("owner", owner), ("repo", repo)):
|
|
43
|
+
if not SAFE_NAME.match(value):
|
|
44
|
+
raise InvalidRepoError(f'invalid {label} "{value}" - contains disallowed characters')
|
|
45
|
+
if value in (".", ".."):
|
|
46
|
+
raise InvalidRepoError(f'invalid {label} "{value}"')
|
|
47
|
+
if value.startswith("."):
|
|
48
|
+
raise InvalidRepoError(f'invalid {label} "{value}" - cannot start with a dot')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_port_spec(spec: str) -> tuple[int, int]:
|
|
52
|
+
""" Parse a --port value into (host_port, container_port).
|
|
53
|
+
Accepts "3000" (same both sides) or "8080:3000" (host:container).
|
|
54
|
+
"""
|
|
55
|
+
parts = spec.split(":")
|
|
56
|
+
|
|
57
|
+
if len(parts) == 1:
|
|
58
|
+
host, container = parts[0], parts[0]
|
|
59
|
+
elif len(parts) == 2:
|
|
60
|
+
host, container = parts
|
|
61
|
+
else:
|
|
62
|
+
raise InvalidPortError(f'invalid port spec "{spec}" - use PORT or HOST:CONTAINER')
|
|
63
|
+
|
|
64
|
+
for label, value in (("host", host), ("container", container)):
|
|
65
|
+
if not value.isdigit():
|
|
66
|
+
raise InvalidPortError(f'invalid {label} port "{value}" in "{spec}" - must be a number')
|
|
67
|
+
port_num = int(value)
|
|
68
|
+
if not(1 <= port_num <= 65535):
|
|
69
|
+
raise InvalidPortError(f'{label} port {port_num} out of range (1-65535)')
|
|
70
|
+
|
|
71
|
+
return int(host), int(container)
|
|
72
|
+
|
|
73
|
+
DEVCONTAINERS_EXT_ID = "ms-vscode-remote.remote-containers"
|
|
74
|
+
|
|
75
|
+
def has_devcontainers_extension() -> bool:
|
|
76
|
+
"""Check if VS Code's DEV containers extension is installed"""
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["code", "--list-extensions"],
|
|
80
|
+
capture_output=True, text=True, timeout=5,
|
|
81
|
+
shell=(os.name == "nt"),
|
|
82
|
+
)
|
|
83
|
+
return DEVCONTAINERS_EXT_ID in result.stdout
|
|
84
|
+
except Exception:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def install_devcontainers_extension() -> bool:
|
|
88
|
+
print("📦 Installing VS Code's 'Dev Containers' extension (one-time setup)...")
|
|
89
|
+
try:
|
|
90
|
+
result = subprocess.run(
|
|
91
|
+
["code", "--install-extension", DEVCONTAINERS_EXT_ID],
|
|
92
|
+
capture_output=True, text=True, timeout=60,
|
|
93
|
+
shell=(os.name=="nt")
|
|
94
|
+
)
|
|
95
|
+
if result.returncode == 0:
|
|
96
|
+
print("✅ Dev Containers extension installed.\n")
|
|
97
|
+
return True
|
|
98
|
+
print("⚠️ Couldn't auto-install the extension. Falling back to folder mode.\n")
|
|
99
|
+
return False
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"⚠️ Couldn't auto-install the extension ({e}). Falling back to folder mode.\n")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def attach_vscode_to_container(container_name: str, work_dir:str):
|
|
105
|
+
for _ in range(20):
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
["docker", "inspect", "--format", "{{.State.Running}}", container_name],
|
|
108
|
+
capture_output=True, text=True,
|
|
109
|
+
)
|
|
110
|
+
if result.returncode == 0 and result.stdout.strip() == "true":
|
|
111
|
+
break
|
|
112
|
+
time.sleep(0.5)
|
|
113
|
+
else:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
id_result = subprocess.run(
|
|
117
|
+
["docker", "inspect", "--format", "{{.Id}}", container_name],
|
|
118
|
+
capture_output=True, text=True,
|
|
119
|
+
)
|
|
120
|
+
if id_result.returncode != 0:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
container_id = id_result.stdout.strip()
|
|
124
|
+
hex_id = container_id.encode().hex()
|
|
125
|
+
uri = f"vscode-remote://attached-container+{hex_id}{work_dir}"
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
subprocess.Popen(["code", "--folder-uri", uri], shell=(os.name=="nt"))
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def run_sandbox(issue: dict, runtime: dict, ports: list =None, token: str = None, no_editor: bool = False):
|
|
133
|
+
check_docker_available()
|
|
134
|
+
ports = ports or []
|
|
135
|
+
|
|
136
|
+
owner = issue["owner"]
|
|
137
|
+
repo = issue["repo"]
|
|
138
|
+
number = issue["number"]
|
|
139
|
+
|
|
140
|
+
validate_repo_identifiers(owner, repo)
|
|
141
|
+
|
|
142
|
+
if not isinstance(number, int):
|
|
143
|
+
raise InvalidRepoError("issue number must be an integer")
|
|
144
|
+
|
|
145
|
+
clone_url_public = f"https://github.com/{owner}/{repo}.git"
|
|
146
|
+
work_dir = f"/sandbox/{repo}"
|
|
147
|
+
container_name = f"repro-{owner}-{repo}-{number}"
|
|
148
|
+
|
|
149
|
+
subprocess.run(["docker", "rm", "-f", container_name], capture_output=True)
|
|
150
|
+
|
|
151
|
+
host_dir = tempfile.mkdtemp(prefix=f"repro-{owner}-{repo}-{number}-")
|
|
152
|
+
|
|
153
|
+
print(f"📁 Sandbox files will live at: {host_dir}")
|
|
154
|
+
print(" (this folder survives after you exit - edit here with your own editor)\n")
|
|
155
|
+
|
|
156
|
+
editor_mode = None
|
|
157
|
+
if not no_editor and shutil.which("code"):
|
|
158
|
+
if not has_devcontainers_extension():
|
|
159
|
+
install_devcontainers_extension()
|
|
160
|
+
|
|
161
|
+
if has_devcontainers_extension():
|
|
162
|
+
editor_mode="attached"
|
|
163
|
+
print("🖊️ VS Code will attach directly to the container once it's up...")
|
|
164
|
+
print(" (its built-in terminal will run INSIDE the sandbox — one window, no split)\n")
|
|
165
|
+
else:
|
|
166
|
+
editor_mode= "folder"
|
|
167
|
+
print("🖊️ Opening the folder in VS Code (editing only)...")
|
|
168
|
+
print(" 💡 Install the 'Dev Containers' extension for a fully unified terminal.\n")
|
|
169
|
+
elif not no_editor:
|
|
170
|
+
print("💡 No auto-attach editor detected (currently supports VS Code :( .\n")
|
|
171
|
+
print(" Open the folder above in whatever editor you use - its a real folder on your machine!! .\n")
|
|
172
|
+
|
|
173
|
+
startup_script = build_startup_script(clone_url_public, work_dir, runtime["install"], number, ports, has_token=bool(token))
|
|
174
|
+
|
|
175
|
+
args = [
|
|
176
|
+
"docker", "run",
|
|
177
|
+
"--rm",
|
|
178
|
+
"-i",
|
|
179
|
+
"-t",
|
|
180
|
+
"--name", container_name,
|
|
181
|
+
"--hostname", f"sandbox-issue-{number}",
|
|
182
|
+
"-v", f"{host_dir}:{work_dir}",
|
|
183
|
+
"-e", f"ISSUE_NUMBER={number}",
|
|
184
|
+
"-e", f"REPO={owner}/{repo}",
|
|
185
|
+
"-e", "TERM=xterm-256color",
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
for host_port, container_port in ports:
|
|
189
|
+
args += ["-p", f"127.0.0.1:{host_port}:{container_port}"]
|
|
190
|
+
|
|
191
|
+
env_file_path = None
|
|
192
|
+
if token:
|
|
193
|
+
fd, env_file_path = tempfile.mkstemp(prefix="repro-", suffix=".env")
|
|
194
|
+
try:
|
|
195
|
+
os.chmod(env_file_path, 0o600)
|
|
196
|
+
with os.fdopen(fd, "w") as f:
|
|
197
|
+
f.write(f"GIT_TOKEN={token}\n")
|
|
198
|
+
args+=["--env-file", env_file_path]
|
|
199
|
+
except Exception:
|
|
200
|
+
if os.path.exists(env_file_path):
|
|
201
|
+
os.remove(env_file_path)
|
|
202
|
+
raise
|
|
203
|
+
|
|
204
|
+
args += [runtime["image"], "sh", "-c", startup_script]
|
|
205
|
+
|
|
206
|
+
print(f"🚀 Starting sandbox for {owner}/{repo} issue #{number}")
|
|
207
|
+
print(" (container will be deleted automatically when you exit)\n")
|
|
208
|
+
|
|
209
|
+
env = os.environ.copy()
|
|
210
|
+
env["DOCKER_CLI_HINTS"] = "false"
|
|
211
|
+
|
|
212
|
+
if editor_mode == "attached":
|
|
213
|
+
threading.Thread(
|
|
214
|
+
target=attach_vscode_to_container,
|
|
215
|
+
args=(container_name, work_dir),
|
|
216
|
+
daemon=True,
|
|
217
|
+
).start()
|
|
218
|
+
elif editor_mode == "folder":
|
|
219
|
+
try:
|
|
220
|
+
subprocess.Popen(["code", host_dir], shell=(os.name=="nt"))
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f"⚠️ Couldn't auto-open VS Code: {e}\n")
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
try:
|
|
226
|
+
result = subprocess.run(args, env=env)
|
|
227
|
+
except KeyboardInterrupt:
|
|
228
|
+
print("Interrupted - cleaning up the sandbox...")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
if result.returncode not in (0,130):
|
|
232
|
+
if result.returncode == 125:
|
|
233
|
+
print(f"\n⚠️ Docker couldn't start the container (exit 125) — this often means"
|
|
234
|
+
f"the image failed to pull. Check your internet connection and try again.")
|
|
235
|
+
else:
|
|
236
|
+
print(f"Sandbox exited with code {result.returncode} - check the output above.")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
print("\n Sandbox destroyed. Back to reality.")
|
|
240
|
+
|
|
241
|
+
finally:
|
|
242
|
+
if env_file_path and os.path.exists(env_file_path):
|
|
243
|
+
os.remove(env_file_path)
|
|
244
|
+
print(f"📁 Your files are still here: {host_dir}")
|
|
245
|
+
print(" (delete manually whenever you're done)")
|
|
246
|
+
|
|
247
|
+
def build_startup_script(clone_url_public, work_dir, install_cmd, issue_number, ports, has_token: bool = False) -> str:
|
|
248
|
+
install_step = (
|
|
249
|
+
f'echo "Installing dependencies....." && {install_cmd}'
|
|
250
|
+
if install_cmd
|
|
251
|
+
else 'echo "No install step, dropping into shell... "'
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
host_and_path = clone_url_public[len("https://"):]
|
|
255
|
+
clone_cmd = (
|
|
256
|
+
f'if [ -n "$GIT_TOKEN" ]; then '
|
|
257
|
+
f'CLONE_URL="https://${{GIT_TOKEN}}@{host_and_path}";'
|
|
258
|
+
f'else CLONE_URL="{clone_url_public}"; fi && '
|
|
259
|
+
f'git clone --depth=1 "$CLONE_URL" {work_dir}'
|
|
260
|
+
f'|| (echo "❌ Clone failed — check the repo exists, the URL is correct, and your token has access." && exit 1)'
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
lines = [
|
|
264
|
+
"set -e",
|
|
265
|
+
"which git > /dev/null 2>&1 || (apk add --no-cache git curl 2>/dev/null || (apt-get update -qq && apt-get install -y -qq git curl))",
|
|
266
|
+
'echo ""',
|
|
267
|
+
'echo "=================================="',
|
|
268
|
+
f'echo " Repro - Issue #{issue_number}"',
|
|
269
|
+
'echo "=================================="',
|
|
270
|
+
'echo ""',
|
|
271
|
+
f'echo "Cloning repo..."',
|
|
272
|
+
clone_cmd,
|
|
273
|
+
f"cd {work_dir} && git remote set-url origin {clone_url_public} && unset GIT_TOKEN",
|
|
274
|
+
install_step,
|
|
275
|
+
'echo ""',
|
|
276
|
+
f'echo "Ready! You are in: {work_dir}"',
|
|
277
|
+
f'echo "Working on issue #{issue_number}"',
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
if ports:
|
|
281
|
+
lines.append('echo ""')
|
|
282
|
+
for host_port, container_port in ports:
|
|
283
|
+
lines.append(f'echo "Port {container_port} forwarded -> http://localhost:{host_port}"')
|
|
284
|
+
lines.append('echo " Start your app inside this shell (e.g. npm start / node server.js) to use it."')
|
|
285
|
+
|
|
286
|
+
lines.append('echo ""')
|
|
287
|
+
lines.append(f"cd {work_dir} && exec sh")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
return " && ".join(lines)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repro-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Disposable Docker environments for any GitHub issue
|
|
5
|
+
Author: Sukhdev Thukral
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# REPRO
|
|
11
|
+
|
|
12
|
+
> Disposable Docker environments for any GitHub issue.
|
|
13
|
+
One command. No setup.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install repro-cli
|
|
17
|
+
repro https://github.com/org/repo/issues/42
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it, your terminal boots an isolated sandbox - repo cloned, dependencies installed and IF you have VS Code, it opens automatically **attached directly to the container** so you get a real editor and a single unified terminal, not just a shell. Type `exit` and the container disappears. Your files stay on disk, exactly where they were.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install repro-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Requirements:**
|
|
31
|
+
- Python 3.9+
|
|
32
|
+
- [Docker](https://docs.docker.com/get-docker/) (INSTALLED AND RUNNING)
|
|
33
|
+
- VS Code (optional, but recommended - enables the unified editor/terminal experience below)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Open a sandbox for any publish GitHub issue
|
|
41
|
+
repro https://github.com/org/repo/issues/123
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
#Forward a port (e.g. to view a web app running inside the sandbox)
|
|
46
|
+
repro https://github.com/org/repo/issues/123 -p 3000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Map a different host port to a container port
|
|
51
|
+
repro https://github.com/org/repo/issues/123 -p 8080:3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Private repos - pass a GitHub token
|
|
56
|
+
repro https://github.com/org/private-repo/issues/7 --token ghp_xxx
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
#Or set it once as an env var instead of typing it every single time!
|
|
61
|
+
export DEVSANDBOX_GITHUB_TOKEN=ghp_xxx
|
|
62
|
+
repro https://github.com/org/private-repo/issues/7
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Skip auto-opening an editor
|
|
67
|
+
repro https://github.com/org/repo/issues/123 --no-editor
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
#Help
|
|
72
|
+
repro --help
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What it does
|
|
76
|
+
|
|
77
|
+
1. **Parses** the GitHub issue URL
|
|
78
|
+
2. **Detects** the repo's runtime - Node, Python, Go, Rust, Ruby, PHP, Java, or a devcontainer, based on whats actually in the repo.
|
|
79
|
+
3. **Spins up** a disposable, isolated Docker container with the right base image.
|
|
80
|
+
4. **Bind-mounts** a real folder on your machine into the container, so nothing is trapped - files persist after you exit
|
|
81
|
+
5. **Clones** the repo directly into that shared folder and install dependencies
|
|
82
|
+
6. **Attaches VS Code directly to the running container** (auto-installing the Dev Containers extension if needed) - one window, one terminal, running *inside* the sandbox.
|
|
83
|
+
7. *Destroys* the container the moment you exit. Your edited files remain exactly where they were.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## The unified editor experience
|
|
88
|
+
|
|
89
|
+
- **Real editor** — VS Code attaches directly into the container, so its built-in terminal *is* the sandbox shell. No second window to juggle.
|
|
90
|
+
- **Real persistence** — because the folder is bind-mounted, not just cloned inside the container, your work survives after `exit`. Nothing is lost.
|
|
91
|
+
If VS Code isn't installed, `repro` falls back gracefully — you still get a real folder path printed at the end, ready to open in whatever editor you use.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Supported runtimes
|
|
96
|
+
| File detected | Runtime | Base image |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `package.json` | Node.js | `node:20-alpine` |
|
|
99
|
+
| `yarn.lock` | Node.js (yarn) | `node:20-alpine` |
|
|
100
|
+
| `pnpm-lock.yaml` | Node.js (pnpm) | `node:20-alpine` |
|
|
101
|
+
| `requirements.txt` | Python | `python:3.12-slim` |
|
|
102
|
+
| `pyproject.toml` | Python | `python:3.12-slim` |
|
|
103
|
+
| `Pipfile` | Python (pipenv) | `python:3.12-slim` |
|
|
104
|
+
| `go.mod` | Go | `golang:1.21-alpine` |
|
|
105
|
+
| `Cargo.toml` | Rust | `rust:1.75-slim` |
|
|
106
|
+
| `Gemfile` | Ruby | `ruby:3.3-slim` |
|
|
107
|
+
| `composer.json` | PHP | `php:8.3-cli` |
|
|
108
|
+
| `pom.xml` | Java (Maven) | `maven:3.9-eclipse-temurin-21` |
|
|
109
|
+
| `build.gradle` | Java (Gradle) | `gradle:8-jdk21` |
|
|
110
|
+
| `.devcontainer` | devcontainer | MS universal image |
|
|
111
|
+
| *(none matched)* | Universal fallback | `ubuntu:22.04` |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## The GitHub App
|
|
116
|
+
|
|
117
|
+
Install [**repro-sandbox-bot**](https://github.com/apps/repro-sandbox-bot) on any repo and it automatically comments the exact `repro` command on every new issue - so contributors never have to think about setup.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
🏖 Open this issue in DevSandbox
|
|
121
|
+
|
|
122
|
+
Spin up a disposable Docker environment with this repo already checked out:
|
|
123
|
+
|
|
124
|
+
repro https://github.com/org/repo/issues/42
|
|
125
|
+
|
|
126
|
+
Don't have repro yet? Install it here.
|
|
127
|
+
|
|
128
|
+
Comment /sandbox on any issue to get this again.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also trigger it on demand by commenting `/sandbox` on any issue. The bot runs 24/7, independent of your machine.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
## Contributing
|
|
135
|
+
PRs welcome. Open an issue first for anything beyond small fixes - and yes, you can use `repro` itself to work on `repro`.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
repro_cli-0.1.0/PKG-INFO
DELETED
repro_cli-0.1.0/README.md
DELETED
|
File without changes
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import shutil
|
|
3
|
-
import re
|
|
4
|
-
import subprocess
|
|
5
|
-
import tempfile
|
|
6
|
-
|
|
7
|
-
class DockerNotFoundError(Exception):
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
class DockerNotRunningError(Exception):
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
class InvalidPortError(Exception):
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
class InvalidRepoError(Exception):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
20
|
-
|
|
21
|
-
def check_docker_available():
|
|
22
|
-
"""Verify Docker CLI exists and the daemon is reachable before trying anythg"""
|
|
23
|
-
if shutil.which("docker") is None:
|
|
24
|
-
raise DockerNotFoundError(
|
|
25
|
-
"Docker is not installed or not on your PATH.\n"
|
|
26
|
-
" Install it from: https://docs.docker.com/get-docker/"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
result = subprocess.run(["docker", "info"], capture_output=True, text=True)
|
|
30
|
-
if result.returncode != 0:
|
|
31
|
-
raise DockerNotRunningError(
|
|
32
|
-
"Docker is installed but the daemon isnt running.\n"
|
|
33
|
-
" Start Docker Desktop and wait for the icon to go steady, then try again."
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
def validate_repo_identifiers(owner: str, repo:str):
|
|
37
|
-
""" Guard against shell injection -> owner/repo gets interpolated into a shell command, so anything outside GitHub's allowed charst is rejected
|
|
38
|
-
before it even reaches da subprocess"""
|
|
39
|
-
for label, value in (("owner", owner), ("repo", repo)):
|
|
40
|
-
if not SAFE_NAME.match(value):
|
|
41
|
-
raise InvalidRepoError(f'invalid {label} "{value}" - contains disallowed characters')
|
|
42
|
-
if value in (".", ".."):
|
|
43
|
-
raise InvalidRepoError(f'invalid {label} "{value}"')
|
|
44
|
-
if value.startswith("."):
|
|
45
|
-
raise InvalidRepoError(f'invalid {label} "{value}" - cannot start with a dot')
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def parse_port_spec(spec: str) -> tuple[int, int]:
|
|
49
|
-
""" Parse a --port value into (host_port, container_port).
|
|
50
|
-
Accepts "3000" (same both sides) or "8080:3000" (host:container).
|
|
51
|
-
"""
|
|
52
|
-
parts = spec.split(":")
|
|
53
|
-
|
|
54
|
-
if len(parts) == 1:
|
|
55
|
-
host, container = parts[0], parts[0]
|
|
56
|
-
elif len(parts) == 2:
|
|
57
|
-
host, container = parts
|
|
58
|
-
else:
|
|
59
|
-
raise InvalidPortError(f'invalid port spec "{spec}" - use PORT or HOST:CONTAINER')
|
|
60
|
-
|
|
61
|
-
for label, value in (("host", host), ("container", container)):
|
|
62
|
-
if not value.isdigit():
|
|
63
|
-
raise InvalidPortError(f'invalid {label} port "{value}" in "{spec}" - must be a number')
|
|
64
|
-
port_num = int(value)
|
|
65
|
-
if not(1 <= port_num <= 65535):
|
|
66
|
-
raise InvalidPortError(f'{label} port {port_num} out of range (1-65535)')
|
|
67
|
-
|
|
68
|
-
return int(host), int(container)
|
|
69
|
-
|
|
70
|
-
def run_sandbox(issue: dict, runtime: dict, ports: list =None, token: str = None):
|
|
71
|
-
check_docker_available()
|
|
72
|
-
ports = ports or []
|
|
73
|
-
|
|
74
|
-
owner = issue["owner"]
|
|
75
|
-
repo = issue["repo"]
|
|
76
|
-
number = issue["number"]
|
|
77
|
-
|
|
78
|
-
validate_repo_identifiers(owner, repo)
|
|
79
|
-
|
|
80
|
-
if not isinstance(number, int):
|
|
81
|
-
raise InvalidRepoError("issue number must be an integer")
|
|
82
|
-
|
|
83
|
-
clone_url_public = f"https://github.com/{owner}/{repo}.git"
|
|
84
|
-
clone_url_auth = f"https://{token}@github.com/{owner}/{repo}.git" if token else clone_url_public
|
|
85
|
-
|
|
86
|
-
work_dir = f"/sandbox/{repo}"
|
|
87
|
-
container_name = f"repro-{owner}-{repo}-{number}"
|
|
88
|
-
|
|
89
|
-
startup_script = build_startup_script(clone_url_auth, clone_url_public, work_dir, runtime["install"], number, ports, has_token=bool(token))
|
|
90
|
-
|
|
91
|
-
args = [
|
|
92
|
-
"docker", "run",
|
|
93
|
-
"--rm",
|
|
94
|
-
"-i",
|
|
95
|
-
"-t",
|
|
96
|
-
"--name", container_name,
|
|
97
|
-
"--hostname", f"sandbox-issue-{number}",
|
|
98
|
-
"-e", f"ISSUE_NUMBER={number}",
|
|
99
|
-
"-e", f"REPO={owner}/{repo}",
|
|
100
|
-
"-e", "TERM=xterm-256color",
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
for host_port, container_port in ports:
|
|
104
|
-
args += ["-p", f"127.0.0.1:{host_port}:{container_port}"]
|
|
105
|
-
|
|
106
|
-
env_file_path = None
|
|
107
|
-
if token:
|
|
108
|
-
#writing the toekn to a temp file passed via --env-fle instead of -e/embedded-in-command
|
|
109
|
-
fd, env_file_path = tempfile.mkstemp(prefix="repro-", suffix=".env")
|
|
110
|
-
try:
|
|
111
|
-
os.chmod(env_file_path, 0o600)
|
|
112
|
-
with os.fdopen(fd, "w") as f:
|
|
113
|
-
f.write(f"GIT_TOKEN={token}\n")
|
|
114
|
-
except Exception:
|
|
115
|
-
if os.path.exists(env_file_path):
|
|
116
|
-
os.remove(env_file_path)
|
|
117
|
-
raise
|
|
118
|
-
|
|
119
|
-
args += [runtime["image"], "sh", "-c", startup_script]
|
|
120
|
-
|
|
121
|
-
print(f"Starting sandbox for {owner}/{repo} issue #{number}")
|
|
122
|
-
print(" (container will be deleted automatically when you exit)\n")
|
|
123
|
-
|
|
124
|
-
env = os.environ.copy()
|
|
125
|
-
env["DOCKER_CLI_HINTS"] = "false"
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
result = subprocess.run(args, env=env)
|
|
129
|
-
except KeyboardInterrupt:
|
|
130
|
-
print("Interrupted - cleaning up the sandbox...")
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
if result.returncode not in (0,130):
|
|
134
|
-
print(f"Sandbox exited with code {result.returncode} - check the output above.")
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
print("\n Sandbox destroyed. Back to reality.")
|
|
138
|
-
|
|
139
|
-
def build_startup_script(clone_url_auth, clone_url_public, work_dir, install_cmd, issue_number, ports) -> str:
|
|
140
|
-
install_step = (
|
|
141
|
-
f'echo "Installing dependencies....." && {install_cmd}'
|
|
142
|
-
if install_cmd
|
|
143
|
-
else 'echo "No install step, dropping into shell... "'
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
host_and_path = clone_url_public[len("https://"):]
|
|
147
|
-
clone_cmd = (
|
|
148
|
-
f'if [ -n "$GIT_TOEKN" ]; then '
|
|
149
|
-
f'CLONE_URL="https://${{GIT_TOKEN}}@{host_and_path}";'
|
|
150
|
-
f'else CLONE_URL="{clone_url_public}"; fi && '
|
|
151
|
-
f'git clone --depth=1 "$CLONE_URL" {work_dir} 2>&1 | tail -5'
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
lines = [
|
|
155
|
-
"set -e",
|
|
156
|
-
"which git > /dev/null 2>&1 || (apk add --no-cache git curl)",
|
|
157
|
-
'echo ""',
|
|
158
|
-
'echo "=================================="',
|
|
159
|
-
f'echo " DevSandbox - Issue #{issue_number}"',
|
|
160
|
-
'echo "=================================="',
|
|
161
|
-
'echo ""',
|
|
162
|
-
f'echo "Cloning repo..."',
|
|
163
|
-
clone_cmd,
|
|
164
|
-
f"git clone --depth=1 {clone_url_auth} {work_dir} 2>&1 | tail -5",
|
|
165
|
-
f"cd {work_dir} && git remote set-url origin {clone_url_public} && unset GIT_TOKEN",
|
|
166
|
-
install_step,
|
|
167
|
-
'echo ""',
|
|
168
|
-
f'echo "Ready! You are in: {work_dir}"',
|
|
169
|
-
f'echo "Working on issue #{issue_number}"',
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
if ports:
|
|
173
|
-
lines.append('echo ""')
|
|
174
|
-
for host_port, container_port in ports:
|
|
175
|
-
lines.append(f'echo "Port {container_port} forwarded -> http://localhost:{host_port}"')
|
|
176
|
-
lines.append('echo " Start your app inside this shell (e.g. npm start / node server.js) to use it."')
|
|
177
|
-
|
|
178
|
-
lines.append('echo ""')
|
|
179
|
-
lines.append(f"cd {work_dir} && exec sh")
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return " && ".join(lines)
|
|
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
|