plain.dev 0.32.0__tar.gz → 0.33.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.
- {plain_dev-0.32.0 → plain_dev-0.33.0}/PKG-INFO +17 -3
- plain_dev-0.33.0/plain/dev/CHANGELOG.md +39 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/README.md +16 -1
- plain_dev-0.33.0/plain/dev/cli.py +283 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/contribute/cli.py +1 -2
- plain_dev-0.32.0/plain/dev/cli.py → plain_dev-0.33.0/plain/dev/core.py +53 -200
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/printer.py +31 -1
- plain_dev-0.33.0/plain/dev/precommit/cli.py +113 -0
- plain_dev-0.33.0/plain/dev/process.py +127 -0
- plain_dev-0.33.0/plain/dev/services.py +48 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/pyproject.toml +1 -2
- plain_dev-0.32.0/plain/dev/CHANGELOG.md +0 -14
- plain_dev-0.32.0/plain/dev/dev_pid.py +0 -36
- plain_dev-0.32.0/plain/dev/precommit/cli.py +0 -117
- plain_dev-0.32.0/plain/dev/services.py +0 -106
- {plain_dev-0.32.0 → plain_dev-0.33.0}/.gitignore +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/LICENSE +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/README.md +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/__init__.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/contribute/README.md +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/contribute/__init__.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/debug.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/default_settings.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/entrypoints.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/gunicorn_logging.json +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/mkcert.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/pdb.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/__init__.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/color.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/compat.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/manager.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/poncho/process.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/precommit/__init__.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/plain/dev/utils.py +0 -0
- {plain_dev-0.32.0 → plain_dev-0.33.0}/tests/settings.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: plain.dev
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.33.0
|
4
4
|
Summary: Local development tools for Plain.
|
5
5
|
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
6
|
License-Expression: BSD-3-Clause
|
@@ -10,7 +10,6 @@ Requires-Dist: click>=8.0.0
|
|
10
10
|
Requires-Dist: gunicorn>20
|
11
11
|
Requires-Dist: inotify
|
12
12
|
Requires-Dist: plain<1.0.0
|
13
|
-
Requires-Dist: psycopg[binary]~=3.2.2
|
14
13
|
Requires-Dist: python-dotenv~=1.0.0
|
15
14
|
Requires-Dist: requests>=2.0.0
|
16
15
|
Requires-Dist: rich
|
@@ -26,6 +25,7 @@ The `plain.dev` package can be [installed from PyPI](https://pypi.org/project/pl
|
|
26
25
|
|
27
26
|
- [`plain dev`](#plain-dev)
|
28
27
|
- [`plain dev services`](#plain-dev-services)
|
28
|
+
- [`plain dev logs`](#plain-dev-logs)
|
29
29
|
- [`plain pre-commit`](#plain-pre-commit)
|
30
30
|
- [`plain contrib`](#plain-contrib)
|
31
31
|
- [VS Code debugging](#vscode-debugging)
|
@@ -62,12 +62,26 @@ Unlike [services](#services), custom processes are _only_ run during `plain dev`
|
|
62
62
|
```toml
|
63
63
|
# pyproject.toml
|
64
64
|
[tool.plain.dev.run]
|
65
|
-
ngrok = {command = "ngrok http $PORT"}
|
65
|
+
ngrok = {command = "ngrok http $PORT"}
|
66
66
|
```
|
67
67
|
|
68
68
|
## `plain dev services`
|
69
69
|
|
70
70
|
Starts your [services](#services) by themselves.
|
71
|
+
Logs are stored in `.plain/dev/logs/services/`.
|
72
|
+
|
73
|
+
## `plain dev logs`
|
74
|
+
|
75
|
+
Show output from recent `plain dev` runs.
|
76
|
+
|
77
|
+
Logs are stored in `.plain/dev/logs/run/`.
|
78
|
+
|
79
|
+
```bash
|
80
|
+
plain dev logs # print last log
|
81
|
+
plain dev logs -f # follow the latest log
|
82
|
+
plain dev logs --pid 1234
|
83
|
+
plain dev logs --path
|
84
|
+
```
|
71
85
|
|
72
86
|
## `plain pre-commit`
|
73
87
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# plain-dev changelog
|
2
|
+
|
3
|
+
## [0.33.0](https://github.com/dropseed/plain/releases/plain-dev@0.33.0) (2025-07-18)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Added automatic background startup of dev services when running `plain dev` commands. Services defined in `pyproject.toml` will now start automatically ([0a5ffc6de5](https://github.com/dropseed/plain/commit/0a5ffc6de5)).
|
8
|
+
- Added `plain dev logs` command to view output from recent `plain dev` runs. Supports options like `--follow`, `--pid`, `--path`, and `--services` to manage and view different log outputs ([0a5ffc6de5](https://github.com/dropseed/plain/commit/0a5ffc6de5)).
|
9
|
+
- Added `--start` and `--stop` flags to both `plain dev` and `plain dev services` commands for running processes in the background. Use `plain dev --start` to launch the dev server in background mode and `plain dev --stop` to terminate it ([0a5ffc6de5](https://github.com/dropseed/plain/commit/0a5ffc6de5)).
|
10
|
+
- Improved process management with better PID tracking and graceful shutdown handling for both dev server and services ([0a5ffc6de5](https://github.com/dropseed/plain/commit/0a5ffc6de5)).
|
11
|
+
- Improved CLI error handling by using `click.UsageError` instead of manual error printing and `sys.exit()` ([88f06c5184](https://github.com/dropseed/plain/commit/88f06c5184)).
|
12
|
+
- Removed `psycopg[binary]` dependency from plain-dev as database drivers should be installed separately based on project needs ([63224001c9](https://github.com/dropseed/plain/commit/63224001c9)).
|
13
|
+
|
14
|
+
### Upgrade instructions
|
15
|
+
|
16
|
+
- No changes required
|
17
|
+
|
18
|
+
## [0.32.1](https://github.com/dropseed/plain/releases/plain-dev@0.32.1) (2025-06-27)
|
19
|
+
|
20
|
+
### What's changed
|
21
|
+
|
22
|
+
- Fixed an error when running `plain dev precommit` (or the `plain precommit` helper) that passed an extra `default` argument to `plain preflight --database`. The flag now correctly aligns with the current `plain preflight` CLI ([db65930](https://github.com/dropseed/plain/commit/db659304129a453676c0dcc20c13b606254ce1c2)).
|
23
|
+
|
24
|
+
### Upgrade instructions
|
25
|
+
|
26
|
+
- No changes required.
|
27
|
+
|
28
|
+
## [0.32.0](https://github.com/dropseed/plain/releases/plain-dev@0.32.0) (2025-06-23)
|
29
|
+
|
30
|
+
### What's changed
|
31
|
+
|
32
|
+
- `plain dev` now writes a PID file and will refuse to start if it detects that another `plain dev` instance is already running in the same project ([75b7a50](https://github.com/dropseed/plain/commit/75b7a505ae3c60675099ffd440f35cf8f30665da)).
|
33
|
+
- When no `--port` is provided, `plain dev` now checks if port 8443 is available and, if not, automatically selects the next free port. Supplying `--port` will error if that port is already in use ([3f5141f](https://github.com/dropseed/plain/commit/3f5141f54a65455f5784ed3f97be2d153ed10a23)).
|
34
|
+
- The development request-log UI has been removed for now, along with its related endpoints and templates ([8ac6f71](https://github.com/dropseed/plain/commit/8ac6f7170efa72e6069bae3cc91809b5fe0f8a7d)).
|
35
|
+
- `plain contrib --all` skips any installed `plainx-*` packages instead of erroring when it can’t locate their repository ([3a26aee](https://github.com/dropseed/plain/commit/3a26aee25e586a66e02a348aa24ee6e048ea0b71)).
|
36
|
+
|
37
|
+
### Upgrade instructions
|
38
|
+
|
39
|
+
- No changes required.
|
@@ -8,6 +8,7 @@ The `plain.dev` package can be [installed from PyPI](https://pypi.org/project/pl
|
|
8
8
|
|
9
9
|
- [`plain dev`](#plain-dev)
|
10
10
|
- [`plain dev services`](#plain-dev-services)
|
11
|
+
- [`plain dev logs`](#plain-dev-logs)
|
11
12
|
- [`plain pre-commit`](#plain-pre-commit)
|
12
13
|
- [`plain contrib`](#plain-contrib)
|
13
14
|
- [VS Code debugging](#vscode-debugging)
|
@@ -44,12 +45,26 @@ Unlike [services](#services), custom processes are _only_ run during `plain dev`
|
|
44
45
|
```toml
|
45
46
|
# pyproject.toml
|
46
47
|
[tool.plain.dev.run]
|
47
|
-
ngrok = {command = "ngrok http $PORT"}
|
48
|
+
ngrok = {command = "ngrok http $PORT"}
|
48
49
|
```
|
49
50
|
|
50
51
|
## `plain dev services`
|
51
52
|
|
52
53
|
Starts your [services](#services) by themselves.
|
54
|
+
Logs are stored in `.plain/dev/logs/services/`.
|
55
|
+
|
56
|
+
## `plain dev logs`
|
57
|
+
|
58
|
+
Show output from recent `plain dev` runs.
|
59
|
+
|
60
|
+
Logs are stored in `.plain/dev/logs/run/`.
|
61
|
+
|
62
|
+
```bash
|
63
|
+
plain dev logs # print last log
|
64
|
+
plain dev logs -f # follow the latest log
|
65
|
+
plain dev logs --pid 1234
|
66
|
+
plain dev logs --path
|
67
|
+
```
|
53
68
|
|
54
69
|
## `plain pre-commit`
|
55
70
|
|
@@ -0,0 +1,283 @@
|
|
1
|
+
import os
|
2
|
+
import subprocess
|
3
|
+
import sys
|
4
|
+
import time
|
5
|
+
from importlib.metadata import entry_points
|
6
|
+
|
7
|
+
import click
|
8
|
+
|
9
|
+
from plain.cli import register_cli
|
10
|
+
from plain.runtime import APP_PATH, PLAIN_TEMP_PATH
|
11
|
+
|
12
|
+
from .core import ENTRYPOINT_GROUP, DevProcess
|
13
|
+
from .services import ServicesProcess
|
14
|
+
|
15
|
+
|
16
|
+
class DevGroup(click.Group):
|
17
|
+
"""Custom group that ensures *services* are running on CLI startup."""
|
18
|
+
|
19
|
+
def __init__(self, *args, **kwargs):
|
20
|
+
super().__init__(*args, **kwargs)
|
21
|
+
self._auto_start_services()
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def _auto_start_services():
|
25
|
+
"""Start dev *services* in the background if not already running."""
|
26
|
+
|
27
|
+
if os.environ.get("PLAIN_DEV_SERVICES_AUTO", "true") not in [
|
28
|
+
"1",
|
29
|
+
"true",
|
30
|
+
"yes",
|
31
|
+
]:
|
32
|
+
return
|
33
|
+
|
34
|
+
# Don't do anything if it looks like a "services" command is being run explicitly
|
35
|
+
if "services" in sys.argv or "--stop" in sys.argv:
|
36
|
+
return
|
37
|
+
|
38
|
+
if not ServicesProcess.get_services(APP_PATH.parent):
|
39
|
+
return
|
40
|
+
|
41
|
+
if ServicesProcess.running_pid():
|
42
|
+
return
|
43
|
+
|
44
|
+
click.secho(
|
45
|
+
"Starting background dev services (terminate with `plain dev --stop`)...",
|
46
|
+
dim=True,
|
47
|
+
)
|
48
|
+
|
49
|
+
subprocess.Popen(
|
50
|
+
[sys.executable, "-m", "plain", "dev", "services", "--start"],
|
51
|
+
start_new_session=True,
|
52
|
+
stdout=subprocess.DEVNULL,
|
53
|
+
stderr=subprocess.DEVNULL,
|
54
|
+
)
|
55
|
+
|
56
|
+
time.sleep(0.5) # Give it a moment to start
|
57
|
+
|
58
|
+
# If it's already dead, show the output and quit
|
59
|
+
if not ServicesProcess.running_pid():
|
60
|
+
click.secho(
|
61
|
+
"Failed to start dev services. Here are the logs:",
|
62
|
+
fg="red",
|
63
|
+
)
|
64
|
+
subprocess.run(
|
65
|
+
["plain", "dev", "logs", "--services"],
|
66
|
+
check=False,
|
67
|
+
)
|
68
|
+
sys.exit(1)
|
69
|
+
|
70
|
+
|
71
|
+
@register_cli("dev")
|
72
|
+
@click.group(cls=DevGroup, invoke_without_command=True)
|
73
|
+
@click.pass_context
|
74
|
+
@click.option(
|
75
|
+
"--port",
|
76
|
+
"-p",
|
77
|
+
default="",
|
78
|
+
type=str,
|
79
|
+
help=(
|
80
|
+
"Port to run the web server on. "
|
81
|
+
"If omitted, tries 8443 and picks the next free port."
|
82
|
+
),
|
83
|
+
)
|
84
|
+
@click.option(
|
85
|
+
"--hostname",
|
86
|
+
"-h",
|
87
|
+
default=None,
|
88
|
+
type=str,
|
89
|
+
help="Hostname to run the web server on",
|
90
|
+
)
|
91
|
+
@click.option(
|
92
|
+
"--log-level",
|
93
|
+
"-l",
|
94
|
+
default="",
|
95
|
+
type=click.Choice(["debug", "info", "warning", "error", "critical", ""]),
|
96
|
+
help="Log level",
|
97
|
+
)
|
98
|
+
@click.option(
|
99
|
+
"--start",
|
100
|
+
is_flag=True,
|
101
|
+
default=False,
|
102
|
+
help="Start in the background",
|
103
|
+
)
|
104
|
+
@click.option(
|
105
|
+
"--stop",
|
106
|
+
is_flag=True,
|
107
|
+
default=False,
|
108
|
+
help="Stop the background process",
|
109
|
+
)
|
110
|
+
def cli(ctx, port, hostname, log_level, start, stop):
|
111
|
+
"""Start local development"""
|
112
|
+
|
113
|
+
if ctx.invoked_subcommand:
|
114
|
+
return
|
115
|
+
|
116
|
+
if start and stop:
|
117
|
+
raise click.UsageError(
|
118
|
+
"You cannot use both --start and --stop at the same time."
|
119
|
+
)
|
120
|
+
|
121
|
+
os.environ["PLAIN_DEV_SERVICES_AUTO"] = "false"
|
122
|
+
|
123
|
+
dev = DevProcess()
|
124
|
+
|
125
|
+
if stop:
|
126
|
+
if ServicesProcess.running_pid():
|
127
|
+
ServicesProcess().stop_process()
|
128
|
+
click.secho("Services stopped.", fg="green")
|
129
|
+
|
130
|
+
if not dev.running_pid():
|
131
|
+
click.secho("No development server running.", fg="yellow")
|
132
|
+
return
|
133
|
+
|
134
|
+
dev.stop_process()
|
135
|
+
click.secho("Development server stopped.", fg="green")
|
136
|
+
return
|
137
|
+
|
138
|
+
if running_pid := dev.running_pid():
|
139
|
+
click.secho(f"`plain dev` already running (pid={running_pid})", fg="yellow")
|
140
|
+
sys.exit(1)
|
141
|
+
|
142
|
+
if start:
|
143
|
+
args = [sys.executable, "-m", "plain", "dev"]
|
144
|
+
if port:
|
145
|
+
args.extend(["--port", port])
|
146
|
+
if hostname:
|
147
|
+
args.extend(["--hostname", hostname])
|
148
|
+
if log_level:
|
149
|
+
args.extend(["--log-level", log_level])
|
150
|
+
|
151
|
+
result = subprocess.Popen(
|
152
|
+
args=args,
|
153
|
+
start_new_session=True,
|
154
|
+
stdout=subprocess.DEVNULL,
|
155
|
+
stderr=subprocess.DEVNULL,
|
156
|
+
)
|
157
|
+
click.secho(
|
158
|
+
f"Development server started in the background (pid={result.pid}).",
|
159
|
+
fg="green",
|
160
|
+
)
|
161
|
+
return
|
162
|
+
|
163
|
+
dev.setup(port=port, hostname=hostname, log_level=log_level)
|
164
|
+
returncode = dev.run()
|
165
|
+
if returncode:
|
166
|
+
sys.exit(returncode)
|
167
|
+
|
168
|
+
|
169
|
+
@cli.command()
|
170
|
+
def debug():
|
171
|
+
"""Connect to the remote debugger"""
|
172
|
+
|
173
|
+
def _connect():
|
174
|
+
if subprocess.run(["which", "nc"], capture_output=True).returncode == 0:
|
175
|
+
return subprocess.run(["nc", "-C", "localhost", "4444"])
|
176
|
+
else:
|
177
|
+
raise OSError("nc not found")
|
178
|
+
|
179
|
+
result = _connect()
|
180
|
+
|
181
|
+
# Try again once without a message
|
182
|
+
if result.returncode == 1:
|
183
|
+
time.sleep(1)
|
184
|
+
result = _connect()
|
185
|
+
|
186
|
+
# Keep trying...
|
187
|
+
while result.returncode == 1:
|
188
|
+
click.secho(
|
189
|
+
"Failed to connect. Make sure remote pdb is ready. Retrying...", fg="red"
|
190
|
+
)
|
191
|
+
result = _connect()
|
192
|
+
time.sleep(1)
|
193
|
+
|
194
|
+
|
195
|
+
@cli.command()
|
196
|
+
@click.option("--start", is_flag=True, help="Start in the background")
|
197
|
+
@click.option("--stop", is_flag=True, help="Stop the background process")
|
198
|
+
def services(start, stop):
|
199
|
+
"""Start additional services defined in pyproject.toml"""
|
200
|
+
|
201
|
+
if start and stop:
|
202
|
+
raise click.UsageError(
|
203
|
+
"You cannot use both --start and --stop at the same time."
|
204
|
+
)
|
205
|
+
|
206
|
+
if stop:
|
207
|
+
if not ServicesProcess.running_pid():
|
208
|
+
click.secho("No services running.", fg="yellow")
|
209
|
+
return
|
210
|
+
ServicesProcess().stop_process()
|
211
|
+
click.secho("Services stopped.", fg="green")
|
212
|
+
return
|
213
|
+
|
214
|
+
if running_pid := ServicesProcess.running_pid():
|
215
|
+
click.secho(f"Services already running (pid={running_pid})", fg="yellow")
|
216
|
+
sys.exit(1)
|
217
|
+
|
218
|
+
if start:
|
219
|
+
result = subprocess.Popen(
|
220
|
+
args=[sys.executable, "-m", "plain", "dev", "services"],
|
221
|
+
start_new_session=True,
|
222
|
+
stdout=subprocess.DEVNULL,
|
223
|
+
stderr=subprocess.DEVNULL,
|
224
|
+
)
|
225
|
+
click.secho(
|
226
|
+
f"Services started in the background (pid={result.pid}).", fg="green"
|
227
|
+
)
|
228
|
+
return
|
229
|
+
|
230
|
+
ServicesProcess().run()
|
231
|
+
|
232
|
+
|
233
|
+
@cli.command()
|
234
|
+
@click.option("--follow", "-f", is_flag=True, help="Follow log output")
|
235
|
+
@click.option("--pid", type=int, help="PID to show logs for")
|
236
|
+
@click.option("--path", is_flag=True, help="Output log file path")
|
237
|
+
@click.option("--services", is_flag=True, help="Show logs for services")
|
238
|
+
def logs(follow, pid, path, services):
|
239
|
+
"""Show logs from recent plain dev runs."""
|
240
|
+
|
241
|
+
if services:
|
242
|
+
log_dir = PLAIN_TEMP_PATH / "dev" / "logs" / "services"
|
243
|
+
else:
|
244
|
+
log_dir = PLAIN_TEMP_PATH / "dev" / "logs" / "run"
|
245
|
+
|
246
|
+
if pid:
|
247
|
+
log_path = log_dir / f"{pid}.log"
|
248
|
+
if not log_path.exists():
|
249
|
+
click.secho(f"No log found for pid {pid}", fg="red")
|
250
|
+
return
|
251
|
+
else:
|
252
|
+
logs = sorted(log_dir.glob("*.log"), key=lambda p: p.stat().st_mtime)
|
253
|
+
if not logs:
|
254
|
+
click.secho("No logs found", fg="yellow")
|
255
|
+
return
|
256
|
+
log_path = logs[-1]
|
257
|
+
|
258
|
+
if path:
|
259
|
+
click.echo(str(log_path))
|
260
|
+
return
|
261
|
+
|
262
|
+
if follow:
|
263
|
+
subprocess.run(["tail", "-f", str(log_path)])
|
264
|
+
else:
|
265
|
+
with log_path.open() as f:
|
266
|
+
click.echo(f.read())
|
267
|
+
|
268
|
+
|
269
|
+
@cli.command()
|
270
|
+
@click.option(
|
271
|
+
"--list", "-l", "show_list", is_flag=True, help="List available entrypoints"
|
272
|
+
)
|
273
|
+
@click.argument("entrypoint", required=False)
|
274
|
+
def entrypoint(show_list, entrypoint):
|
275
|
+
"""Entrypoints registered under plain.dev"""
|
276
|
+
if not show_list and not entrypoint:
|
277
|
+
raise click.UsageError("Please provide an entrypoint name or use --list")
|
278
|
+
|
279
|
+
for entry_point in entry_points().select(group=ENTRYPOINT_GROUP):
|
280
|
+
if show_list:
|
281
|
+
click.echo(entry_point.name)
|
282
|
+
elif entrypoint == entry_point.name:
|
283
|
+
entry_point.load()()
|
@@ -96,8 +96,7 @@ def cli(packages, repo, reset, all_packages):
|
|
96
96
|
elif package.startswith("plainx-"):
|
97
97
|
plainx_packages.append(str(repo))
|
98
98
|
else:
|
99
|
-
click.
|
100
|
-
sys.exit(2)
|
99
|
+
raise click.UsageError(f"Unknown package {package}")
|
101
100
|
|
102
101
|
if plain_packages:
|
103
102
|
result = subprocess.run(["uv", "add", "--editable", "--dev"] + plain_packages)
|