plain.dev 0.32.1__tar.gz → 0.33.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.
Files changed (35) hide show
  1. {plain_dev-0.32.1 → plain_dev-0.33.1}/PKG-INFO +17 -3
  2. plain_dev-0.33.1/plain/dev/CHANGELOG.md +50 -0
  3. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/README.md +16 -1
  4. plain_dev-0.33.1/plain/dev/cli.py +288 -0
  5. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/contribute/cli.py +1 -2
  6. plain_dev-0.32.1/plain/dev/cli.py → plain_dev-0.33.1/plain/dev/core.py +53 -200
  7. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/printer.py +31 -1
  8. plain_dev-0.33.1/plain/dev/precommit/cli.py +113 -0
  9. plain_dev-0.33.1/plain/dev/process.py +127 -0
  10. plain_dev-0.33.1/plain/dev/services.py +48 -0
  11. {plain_dev-0.32.1 → plain_dev-0.33.1}/pyproject.toml +1 -2
  12. plain_dev-0.32.1/plain/dev/CHANGELOG.md +0 -24
  13. plain_dev-0.32.1/plain/dev/dev_pid.py +0 -36
  14. plain_dev-0.32.1/plain/dev/precommit/cli.py +0 -116
  15. plain_dev-0.32.1/plain/dev/services.py +0 -106
  16. {plain_dev-0.32.1 → plain_dev-0.33.1}/.gitignore +0 -0
  17. {plain_dev-0.32.1 → plain_dev-0.33.1}/LICENSE +0 -0
  18. {plain_dev-0.32.1 → plain_dev-0.33.1}/README.md +0 -0
  19. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/__init__.py +0 -0
  20. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/contribute/README.md +0 -0
  21. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/contribute/__init__.py +0 -0
  22. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/debug.py +0 -0
  23. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/default_settings.py +0 -0
  24. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/entrypoints.py +0 -0
  25. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/gunicorn_logging.json +0 -0
  26. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/mkcert.py +0 -0
  27. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/pdb.py +0 -0
  28. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/__init__.py +0 -0
  29. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/color.py +0 -0
  30. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/compat.py +0 -0
  31. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/manager.py +0 -0
  32. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/poncho/process.py +0 -0
  33. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/precommit/__init__.py +0 -0
  34. {plain_dev-0.32.1 → plain_dev-0.33.1}/plain/dev/utils.py +0 -0
  35. {plain_dev-0.32.1 → plain_dev-0.33.1}/tests/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.dev
3
- Version: 0.32.1
3
+ Version: 0.33.1
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,50 @@
1
+ # plain-dev changelog
2
+
3
+ ## [0.33.1](https://github.com/dropseed/plain/releases/plain-dev@0.33.1) (2025-07-18)
4
+
5
+ ### What's changed
6
+
7
+ - Dev services are no longer automatically started when running in CI environments unless explicitly enabled with `PLAIN_DEV_SERVICES_AUTO=true` ([b8452bae74](https://github.com/dropseed/plain/commit/b8452bae74))
8
+ - The `plain dev logs` command now skips automatic service startup to avoid conflicts ([ff65428bca](https://github.com/dropseed/plain/commit/ff65428bca))
9
+
10
+ ### Upgrade instructions
11
+
12
+ - No changes required
13
+
14
+ ## [0.33.0](https://github.com/dropseed/plain/releases/plain-dev@0.33.0) (2025-07-18)
15
+
16
+ ### What's changed
17
+
18
+ - 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)).
19
+ - 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)).
20
+ - 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)).
21
+ - 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)).
22
+ - Improved CLI error handling by using `click.UsageError` instead of manual error printing and `sys.exit()` ([88f06c5184](https://github.com/dropseed/plain/commit/88f06c5184)).
23
+ - 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)).
24
+
25
+ ### Upgrade instructions
26
+
27
+ - No changes required
28
+
29
+ ## [0.32.1](https://github.com/dropseed/plain/releases/plain-dev@0.32.1) (2025-06-27)
30
+
31
+ ### What's changed
32
+
33
+ - 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)).
34
+
35
+ ### Upgrade instructions
36
+
37
+ - No changes required.
38
+
39
+ ## [0.32.0](https://github.com/dropseed/plain/releases/plain-dev@0.32.0) (2025-06-23)
40
+
41
+ ### What's changed
42
+
43
+ - `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)).
44
+ - 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)).
45
+ - 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)).
46
+ - `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)).
47
+
48
+ ### Upgrade instructions
49
+
50
+ - 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,288 @@
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
+ # Check if we're in CI and auto-start is not explicitly enabled
28
+ if os.environ.get("CI") and os.environ.get("PLAIN_DEV_SERVICES_AUTO") is None:
29
+ return
30
+
31
+ if os.environ.get("PLAIN_DEV_SERVICES_AUTO", "true") not in [
32
+ "1",
33
+ "true",
34
+ "yes",
35
+ ]:
36
+ return
37
+
38
+ # Don't do anything if it looks like a "services" command is being run explicitly
39
+ if "dev" in sys.argv:
40
+ if "logs" in sys.argv or "services" in sys.argv or "--stop" in sys.argv:
41
+ return
42
+
43
+ if not ServicesProcess.get_services(APP_PATH.parent):
44
+ return
45
+
46
+ if ServicesProcess.running_pid():
47
+ return
48
+
49
+ click.secho(
50
+ "Starting background dev services (terminate with `plain dev --stop`)...",
51
+ dim=True,
52
+ )
53
+
54
+ subprocess.Popen(
55
+ [sys.executable, "-m", "plain", "dev", "services", "--start"],
56
+ start_new_session=True,
57
+ stdout=subprocess.DEVNULL,
58
+ stderr=subprocess.DEVNULL,
59
+ )
60
+
61
+ time.sleep(0.5) # Give it a moment to start
62
+
63
+ # If it's already dead, show the output and quit
64
+ if not ServicesProcess.running_pid():
65
+ click.secho(
66
+ "Failed to start dev services. Here are the logs:",
67
+ fg="red",
68
+ )
69
+ subprocess.run(
70
+ ["plain", "dev", "logs", "--services"],
71
+ check=False,
72
+ )
73
+ sys.exit(1)
74
+
75
+
76
+ @register_cli("dev")
77
+ @click.group(cls=DevGroup, invoke_without_command=True)
78
+ @click.pass_context
79
+ @click.option(
80
+ "--port",
81
+ "-p",
82
+ default="",
83
+ type=str,
84
+ help=(
85
+ "Port to run the web server on. "
86
+ "If omitted, tries 8443 and picks the next free port."
87
+ ),
88
+ )
89
+ @click.option(
90
+ "--hostname",
91
+ "-h",
92
+ default=None,
93
+ type=str,
94
+ help="Hostname to run the web server on",
95
+ )
96
+ @click.option(
97
+ "--log-level",
98
+ "-l",
99
+ default="",
100
+ type=click.Choice(["debug", "info", "warning", "error", "critical", ""]),
101
+ help="Log level",
102
+ )
103
+ @click.option(
104
+ "--start",
105
+ is_flag=True,
106
+ default=False,
107
+ help="Start in the background",
108
+ )
109
+ @click.option(
110
+ "--stop",
111
+ is_flag=True,
112
+ default=False,
113
+ help="Stop the background process",
114
+ )
115
+ def cli(ctx, port, hostname, log_level, start, stop):
116
+ """Start local development"""
117
+
118
+ if ctx.invoked_subcommand:
119
+ return
120
+
121
+ if start and stop:
122
+ raise click.UsageError(
123
+ "You cannot use both --start and --stop at the same time."
124
+ )
125
+
126
+ os.environ["PLAIN_DEV_SERVICES_AUTO"] = "false"
127
+
128
+ dev = DevProcess()
129
+
130
+ if stop:
131
+ if ServicesProcess.running_pid():
132
+ ServicesProcess().stop_process()
133
+ click.secho("Services stopped.", fg="green")
134
+
135
+ if not dev.running_pid():
136
+ click.secho("No development server running.", fg="yellow")
137
+ return
138
+
139
+ dev.stop_process()
140
+ click.secho("Development server stopped.", fg="green")
141
+ return
142
+
143
+ if running_pid := dev.running_pid():
144
+ click.secho(f"`plain dev` already running (pid={running_pid})", fg="yellow")
145
+ sys.exit(1)
146
+
147
+ if start:
148
+ args = [sys.executable, "-m", "plain", "dev"]
149
+ if port:
150
+ args.extend(["--port", port])
151
+ if hostname:
152
+ args.extend(["--hostname", hostname])
153
+ if log_level:
154
+ args.extend(["--log-level", log_level])
155
+
156
+ result = subprocess.Popen(
157
+ args=args,
158
+ start_new_session=True,
159
+ stdout=subprocess.DEVNULL,
160
+ stderr=subprocess.DEVNULL,
161
+ )
162
+ click.secho(
163
+ f"Development server started in the background (pid={result.pid}).",
164
+ fg="green",
165
+ )
166
+ return
167
+
168
+ dev.setup(port=port, hostname=hostname, log_level=log_level)
169
+ returncode = dev.run()
170
+ if returncode:
171
+ sys.exit(returncode)
172
+
173
+
174
+ @cli.command()
175
+ def debug():
176
+ """Connect to the remote debugger"""
177
+
178
+ def _connect():
179
+ if subprocess.run(["which", "nc"], capture_output=True).returncode == 0:
180
+ return subprocess.run(["nc", "-C", "localhost", "4444"])
181
+ else:
182
+ raise OSError("nc not found")
183
+
184
+ result = _connect()
185
+
186
+ # Try again once without a message
187
+ if result.returncode == 1:
188
+ time.sleep(1)
189
+ result = _connect()
190
+
191
+ # Keep trying...
192
+ while result.returncode == 1:
193
+ click.secho(
194
+ "Failed to connect. Make sure remote pdb is ready. Retrying...", fg="red"
195
+ )
196
+ result = _connect()
197
+ time.sleep(1)
198
+
199
+
200
+ @cli.command()
201
+ @click.option("--start", is_flag=True, help="Start in the background")
202
+ @click.option("--stop", is_flag=True, help="Stop the background process")
203
+ def services(start, stop):
204
+ """Start additional services defined in pyproject.toml"""
205
+
206
+ if start and stop:
207
+ raise click.UsageError(
208
+ "You cannot use both --start and --stop at the same time."
209
+ )
210
+
211
+ if stop:
212
+ if not ServicesProcess.running_pid():
213
+ click.secho("No services running.", fg="yellow")
214
+ return
215
+ ServicesProcess().stop_process()
216
+ click.secho("Services stopped.", fg="green")
217
+ return
218
+
219
+ if running_pid := ServicesProcess.running_pid():
220
+ click.secho(f"Services already running (pid={running_pid})", fg="yellow")
221
+ sys.exit(1)
222
+
223
+ if start:
224
+ result = subprocess.Popen(
225
+ args=[sys.executable, "-m", "plain", "dev", "services"],
226
+ start_new_session=True,
227
+ stdout=subprocess.DEVNULL,
228
+ stderr=subprocess.DEVNULL,
229
+ )
230
+ click.secho(
231
+ f"Services started in the background (pid={result.pid}).", fg="green"
232
+ )
233
+ return
234
+
235
+ ServicesProcess().run()
236
+
237
+
238
+ @cli.command()
239
+ @click.option("--follow", "-f", is_flag=True, help="Follow log output")
240
+ @click.option("--pid", type=int, help="PID to show logs for")
241
+ @click.option("--path", is_flag=True, help="Output log file path")
242
+ @click.option("--services", is_flag=True, help="Show logs for services")
243
+ def logs(follow, pid, path, services):
244
+ """Show logs from recent plain dev runs."""
245
+
246
+ if services:
247
+ log_dir = PLAIN_TEMP_PATH / "dev" / "logs" / "services"
248
+ else:
249
+ log_dir = PLAIN_TEMP_PATH / "dev" / "logs" / "run"
250
+
251
+ if pid:
252
+ log_path = log_dir / f"{pid}.log"
253
+ if not log_path.exists():
254
+ click.secho(f"No log found for pid {pid}", fg="red")
255
+ return
256
+ else:
257
+ logs = sorted(log_dir.glob("*.log"), key=lambda p: p.stat().st_mtime)
258
+ if not logs:
259
+ click.secho("No logs found", fg="yellow")
260
+ return
261
+ log_path = logs[-1]
262
+
263
+ if path:
264
+ click.echo(str(log_path))
265
+ return
266
+
267
+ if follow:
268
+ subprocess.run(["tail", "-f", str(log_path)])
269
+ else:
270
+ with log_path.open() as f:
271
+ click.echo(f.read())
272
+
273
+
274
+ @cli.command()
275
+ @click.option(
276
+ "--list", "-l", "show_list", is_flag=True, help="List available entrypoints"
277
+ )
278
+ @click.argument("entrypoint", required=False)
279
+ def entrypoint(show_list, entrypoint):
280
+ """Entrypoints registered under plain.dev"""
281
+ if not show_list and not entrypoint:
282
+ raise click.UsageError("Please provide an entrypoint name or use --list")
283
+
284
+ for entry_point in entry_points().select(group=ENTRYPOINT_GROUP):
285
+ if show_list:
286
+ click.echo(entry_point.name)
287
+ elif entrypoint == entry_point.name:
288
+ 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.secho(f"Unknown package {package}", fg="red")
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)