caffeinated-whale-cli 2.3.0__tar.gz → 2.3.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.
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/PKG-INFO +8 -3
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/README.md +7 -2
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/pyproject.toml +1 -1
- caffeinated_whale_cli-2.3.1/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/axi.py +1 -1
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/init.py +13 -4
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/start.py +12 -2
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/init.py +67 -7
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/scale.py +12 -9
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/start.py +43 -2
- caffeinated_whale_cli-2.3.0/src/caffeinated_whale_cli/__init__.py +0 -1
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/LICENSE +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/setup.cfg +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/apps.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/console.html +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/rm.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/run.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/serve.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/stop.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/apps.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/bench_ops.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/docker.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/doctor.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/resolvers.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/rm.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/stop.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/main.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/cache.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caffeinated-whale-cli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: A CLI tool to help manage Frappe Docker instances.
|
|
5
5
|
Author-email: Christopher McKay <mckay.christopher73@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -320,15 +320,19 @@ Then open http://development.localhost:8000 (or `cwcli open my-project`).
|
|
|
320
320
|
If the dev services fail to start, init still exits successfully (the bench was already created) and prints a warning telling you to run `cwcli start` yourself.
|
|
321
321
|
The start itself waits for the web server to answer on the bench's own configured port before declaring success; if that times out, init reports the same "not running" output even though the containers and supervisor did launch, since the web isn't actually serving yet.
|
|
322
322
|
If the port cannot be read, start skips the wait and warns instead of probing another bench's port.
|
|
323
|
+
If the web server is running inside the container but its port is not published to the host, init prints a warning naming the missing host binding and advises recreating the instance.
|
|
323
324
|
|
|
324
325
|
**Port Conflict Handling:**
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
Init retries occupied ports five times, one second apart, to allow bindings from a just-removed instance to finish releasing.
|
|
328
|
+
Each retry is announced in both interactive and non-interactive runs.
|
|
329
|
+
If the ports remain occupied, init exits nonzero before creating the instance and names every conflicting port:
|
|
327
330
|
|
|
328
331
|
```
|
|
329
332
|
Error: The following ports are already in use: 8000-8005
|
|
330
333
|
|
|
331
|
-
Tip:
|
|
334
|
+
Tip: If an instance using these ports was just removed, they may still be releasing - wait a few seconds and retry.
|
|
335
|
+
Tip: Otherwise, use the --port flag to select a different starting port.
|
|
332
336
|
Example: cwcli init my-project --port 10000
|
|
333
337
|
```
|
|
334
338
|
|
|
@@ -498,6 +502,7 @@ cwcli start [OPTIONS] [PROJECT_NAME]...
|
|
|
498
502
|
- **Idempotent:** a re-run on an already-running bench is a clean no-op ("already running: N/N processes up"), never a second supervisor stack
|
|
499
503
|
- **Per-process supervision:** each Procfile process runs under supervisord, so one can be restarted or auto-healed without disturbing the others
|
|
500
504
|
- **Waits for the web server:** on a genuine launch, `start` blocks until the bench's **own** web port actually answers before reporting the bench as running, so a scripted `cwcli start && cwcli status` never catches a transient `degraded`. The port is read from that bench's `sites/common_site_config.json`, never assumed - waiting on a hardcoded `:8000` meant `--bench 1` watched bench 0's port and then warned that a healthy bench had not started. If the port cannot be read, the wait is **skipped** (`web_ready` stays unset) rather than spent on a guess. A timeout (60s) does not fail the start (the stack IS launched) - it prints a warning naming the real port and telling you to check `cwcli status` / `cwcli logs`
|
|
505
|
+
- **Host port verification:** on both a genuine launch and an idempotent no-op, `start` warns when the bench's web server has no live host port binding. The warning names the container port and advises recreating the instance with `cwcli init`
|
|
501
506
|
- **Port Conflict Detection:** Automatically checks if required ports are available
|
|
502
507
|
- **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
|
|
503
508
|
- **Process Identification:** Shows which processes are using ports (cross-platform)
|
|
@@ -280,15 +280,19 @@ Then open http://development.localhost:8000 (or `cwcli open my-project`).
|
|
|
280
280
|
If the dev services fail to start, init still exits successfully (the bench was already created) and prints a warning telling you to run `cwcli start` yourself.
|
|
281
281
|
The start itself waits for the web server to answer on the bench's own configured port before declaring success; if that times out, init reports the same "not running" output even though the containers and supervisor did launch, since the web isn't actually serving yet.
|
|
282
282
|
If the port cannot be read, start skips the wait and warns instead of probing another bench's port.
|
|
283
|
+
If the web server is running inside the container but its port is not published to the host, init prints a warning naming the missing host binding and advises recreating the instance.
|
|
283
284
|
|
|
284
285
|
**Port Conflict Handling:**
|
|
285
286
|
|
|
286
|
-
|
|
287
|
+
Init retries occupied ports five times, one second apart, to allow bindings from a just-removed instance to finish releasing.
|
|
288
|
+
Each retry is announced in both interactive and non-interactive runs.
|
|
289
|
+
If the ports remain occupied, init exits nonzero before creating the instance and names every conflicting port:
|
|
287
290
|
|
|
288
291
|
```
|
|
289
292
|
Error: The following ports are already in use: 8000-8005
|
|
290
293
|
|
|
291
|
-
Tip:
|
|
294
|
+
Tip: If an instance using these ports was just removed, they may still be releasing - wait a few seconds and retry.
|
|
295
|
+
Tip: Otherwise, use the --port flag to select a different starting port.
|
|
292
296
|
Example: cwcli init my-project --port 10000
|
|
293
297
|
```
|
|
294
298
|
|
|
@@ -458,6 +462,7 @@ cwcli start [OPTIONS] [PROJECT_NAME]...
|
|
|
458
462
|
- **Idempotent:** a re-run on an already-running bench is a clean no-op ("already running: N/N processes up"), never a second supervisor stack
|
|
459
463
|
- **Per-process supervision:** each Procfile process runs under supervisord, so one can be restarted or auto-healed without disturbing the others
|
|
460
464
|
- **Waits for the web server:** on a genuine launch, `start` blocks until the bench's **own** web port actually answers before reporting the bench as running, so a scripted `cwcli start && cwcli status` never catches a transient `degraded`. The port is read from that bench's `sites/common_site_config.json`, never assumed - waiting on a hardcoded `:8000` meant `--bench 1` watched bench 0's port and then warned that a healthy bench had not started. If the port cannot be read, the wait is **skipped** (`web_ready` stays unset) rather than spent on a guess. A timeout (60s) does not fail the start (the stack IS launched) - it prints a warning naming the real port and telling you to check `cwcli status` / `cwcli logs`
|
|
465
|
+
- **Host port verification:** on both a genuine launch and an idempotent no-op, `start` warns when the bench's web server has no live host port binding. The warning names the container port and advises recreating the instance with `cwcli init`
|
|
461
466
|
- **Port Conflict Detection:** Automatically checks if required ports are available
|
|
462
467
|
- **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
|
|
463
468
|
- **Process Identification:** Shows which processes are using ports (cross-platform)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.1"
|
|
@@ -2336,7 +2336,7 @@ def axi_init(
|
|
|
2336
2336
|
)
|
|
2337
2337
|
else:
|
|
2338
2338
|
for warning in start_result.warnings:
|
|
2339
|
-
if warning.code
|
|
2339
|
+
if warning.code in ("start.web_not_ready", "start.no_host_port"):
|
|
2340
2340
|
print(f"Warning: {warning.text}", file=sys.stderr, flush=True)
|
|
2341
2341
|
|
|
2342
2342
|
emit_result(bench_result.data, warnings=bench_result.warnings)
|
|
@@ -173,6 +173,10 @@ class _InitRenderer:
|
|
|
173
173
|
stderr_console.print(f"[yellow]Warning: {event.text}[/yellow]")
|
|
174
174
|
elif code == "instance.already_running" and self.verbose:
|
|
175
175
|
stderr_console.print(f"[dim]{event.text}[/dim]")
|
|
176
|
+
elif code == "ports.retry":
|
|
177
|
+
# Unmissable in both rendering modes: a caller waiting on this run
|
|
178
|
+
# needs to know it's retrying, not stuck.
|
|
179
|
+
stderr_console.print(f"[yellow]{event.text}[/yellow]")
|
|
176
180
|
|
|
177
181
|
def _on_trace(self, event) -> None:
|
|
178
182
|
if not self.verbose:
|
|
@@ -191,7 +195,11 @@ def _render_error_exit(e: CwcliError, project_name: str, *, verbose: bool = Fals
|
|
|
191
195
|
stderr_console.print(f"[bold red]Error:[/bold red] {e.message}")
|
|
192
196
|
if e.code == "ports.in_use":
|
|
193
197
|
stderr_console.print(
|
|
194
|
-
"\n[yellow]Tip:[/yellow]
|
|
198
|
+
"\n[yellow]Tip:[/yellow] If an instance using these ports was just removed, "
|
|
199
|
+
"they may still be releasing - wait a few seconds and retry."
|
|
200
|
+
)
|
|
201
|
+
stderr_console.print(
|
|
202
|
+
"[yellow]Tip:[/yellow] Otherwise, use the [cyan]--port[/cyan] flag to select "
|
|
195
203
|
"a different starting port."
|
|
196
204
|
)
|
|
197
205
|
stderr_console.print(f"[dim]Example: cwcli init {project_name} --port 10000[/dim]")
|
|
@@ -376,10 +384,11 @@ def _start_services(project: str, bench_path: str) -> tuple[bool, bool | None]:
|
|
|
376
384
|
)
|
|
377
385
|
return False, None
|
|
378
386
|
# core.start now blocks until the web server binds this bench's assigned port, so "running" is
|
|
379
|
-
# honest by the time we return. If it timed out,
|
|
380
|
-
# user isn't told the web is up
|
|
387
|
+
# honest by the time we return. If it timed out, or the port never got published
|
|
388
|
+
# to the host at all, surface the warning so the user isn't told the web is up
|
|
389
|
+
# when it isn't reachable.
|
|
381
390
|
for warning in result.warnings:
|
|
382
|
-
if warning.code
|
|
391
|
+
if warning.code in ("start.web_not_ready", "start.no_host_port"):
|
|
383
392
|
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
384
393
|
running = result.status is not Status.NEEDS_CHOICE
|
|
385
394
|
web_ready = result.data.web_ready if running and result.data is not None else None
|
|
@@ -322,7 +322,12 @@ def _start_project(
|
|
|
322
322
|
# be visible here too, since restart's whole-stack path and the auto-start
|
|
323
323
|
# path (ensure_containers_running) both funnel through this helper.
|
|
324
324
|
for warning in result.warnings:
|
|
325
|
-
if warning.code in (
|
|
325
|
+
if warning.code in (
|
|
326
|
+
"start.uid_align_failed",
|
|
327
|
+
"bench.default_used",
|
|
328
|
+
"start.web_not_ready",
|
|
329
|
+
"start.no_host_port",
|
|
330
|
+
):
|
|
326
331
|
stderr_console.print(f"[yellow]Warning: {warning.text}[/yellow]")
|
|
327
332
|
elif verbose:
|
|
328
333
|
stderr_console.print(f"[dim]{warning.text}[/dim]")
|
|
@@ -522,7 +527,12 @@ def _run_start(
|
|
|
522
527
|
# init.uid_align_failed precedent for the uid remap failure): both signal the
|
|
523
528
|
# bench workspace may not behave as expected, not just verbose diagnostics.
|
|
524
529
|
for warning in result.warnings:
|
|
525
|
-
if warning.code in (
|
|
530
|
+
if warning.code in (
|
|
531
|
+
"start.uid_align_failed",
|
|
532
|
+
"bench.default_used",
|
|
533
|
+
"start.web_not_ready",
|
|
534
|
+
"start.no_host_port",
|
|
535
|
+
):
|
|
526
536
|
stderr_console.print(f"[yellow]Warning: {warning.text}[/yellow]")
|
|
527
537
|
elif verbose:
|
|
528
538
|
stderr_console.print(f"[dim]{warning.text}[/dim]")
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/init.py
RENAMED
|
@@ -472,6 +472,43 @@ def _project_containers_running(project_name: str) -> bool:
|
|
|
472
472
|
|
|
473
473
|
_EXPECTED_COMPOSE_SERVICES = frozenset({"frappe", "mariadb", "redis-cache", "redis-queue"})
|
|
474
474
|
|
|
475
|
+
# A just-removed instance's own port bindings can take a moment to fully
|
|
476
|
+
# release (Docker's docker-proxy teardown lag, observed in the field to clear
|
|
477
|
+
# within seconds), so a rapid remove-then-reinit (a CI purge-and-recreate
|
|
478
|
+
# pattern) can see those SAME ports as still "in use" right after the old
|
|
479
|
+
# instance is gone. 5 attempts a second apart absorbs that ordinary teardown
|
|
480
|
+
# window before concluding the conflict is real and persistent.
|
|
481
|
+
_PORT_CHECK_ATTEMPTS = 5
|
|
482
|
+
_PORT_CHECK_RETRY_DELAY = 1.0
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _ports_still_in_use(ports: list[int], *, emit: OnEvent) -> list[int]:
|
|
486
|
+
"""Check host ports, retrying briefly before giving up.
|
|
487
|
+
|
|
488
|
+
Never silently treats a conflict as "no conflict" - it only delays the
|
|
489
|
+
verdict to absorb a transient teardown window. A port still in use after
|
|
490
|
+
every attempt is reported exactly as before: loudly, deterministically,
|
|
491
|
+
regardless of interactivity.
|
|
492
|
+
"""
|
|
493
|
+
ports_in_use: list[int] = []
|
|
494
|
+
for attempt in range(_PORT_CHECK_ATTEMPTS):
|
|
495
|
+
port_status = check_ports_in_use(ports)
|
|
496
|
+
ports_in_use = [p for p, in_use in port_status.items() if in_use]
|
|
497
|
+
if not ports_in_use:
|
|
498
|
+
return []
|
|
499
|
+
if attempt < _PORT_CHECK_ATTEMPTS - 1:
|
|
500
|
+
emit(
|
|
501
|
+
InitNotice(
|
|
502
|
+
code="ports.retry",
|
|
503
|
+
text=(
|
|
504
|
+
f"Port(s) {format_port_list(ports_in_use)} still in use "
|
|
505
|
+
"(may be a just-removed instance releasing them); retrying..."
|
|
506
|
+
),
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
time.sleep(_PORT_CHECK_RETRY_DELAY)
|
|
510
|
+
return ports_in_use
|
|
511
|
+
|
|
475
512
|
|
|
476
513
|
def _running_compose_services(project_name: str) -> set[str]:
|
|
477
514
|
"""Return this project's running compose service names."""
|
|
@@ -532,14 +569,17 @@ def init_instance(
|
|
|
532
569
|
if not auto_start and not _project_containers_running(project_name):
|
|
533
570
|
web_ports = list(range(port, port + 6))
|
|
534
571
|
socketio_ports = list(range(port + 1000, port + 1006))
|
|
535
|
-
|
|
536
|
-
ports_in_use = [p for p, in_use in port_status.items() if in_use]
|
|
572
|
+
ports_in_use = _ports_still_in_use(web_ports + socketio_ports, emit=emit)
|
|
537
573
|
if ports_in_use:
|
|
538
574
|
raise CwcliError(
|
|
539
575
|
ErrorKind.CONFLICT,
|
|
540
576
|
"ports.in_use",
|
|
541
577
|
f"The following ports are already in use: {format_port_list(ports_in_use)}",
|
|
542
|
-
hint=
|
|
578
|
+
hint=(
|
|
579
|
+
"If an instance using these ports was just removed, they may still be "
|
|
580
|
+
"releasing - wait a few seconds and retry. Otherwise, use --port to "
|
|
581
|
+
"select a different starting port."
|
|
582
|
+
),
|
|
543
583
|
)
|
|
544
584
|
|
|
545
585
|
emit(InitStepStart(phase="project_dir", message="Creating project directory"))
|
|
@@ -581,11 +621,11 @@ def init_instance(
|
|
|
581
621
|
)
|
|
582
622
|
)
|
|
583
623
|
content = compose_path.read_text()
|
|
584
|
-
|
|
624
|
+
web_mapping = f"{port}-{port + 5}:8000-8005"
|
|
625
|
+
content = content.replace("8000-8005:8000-8005", web_mapping)
|
|
585
626
|
socketio_start = port + 1000
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
)
|
|
627
|
+
socketio_mapping = f"{socketio_start}-{socketio_start + 5}:9000-9005"
|
|
628
|
+
content = content.replace("9000-9005:9000-9005", socketio_mapping)
|
|
589
629
|
# The upstream devcontainer template sets `working_dir: /workspace/development`
|
|
590
630
|
# - a path cwcli never creates (its bench lives at /workspace/frappe-bench).
|
|
591
631
|
# /workspace is a bind mount to CWCLI_HOME/projects/<name>/, so on `compose up`
|
|
@@ -617,6 +657,26 @@ def init_instance(
|
|
|
617
657
|
(project_dir / "data").mkdir(parents=True, exist_ok=True)
|
|
618
658
|
content = content.replace("- ..:/workspace:cached", f"- ../data:{bench_parent_path}:cached")
|
|
619
659
|
content = content.replace("working_dir: /workspace", f"working_dir: {bench_parent_path}")
|
|
660
|
+
# frappe_docker removed the devcontainer template's ports block in favor
|
|
661
|
+
# of editor-managed forwarding. cwcli runs the compose project directly,
|
|
662
|
+
# so it must add the host publications when the downloaded template omits
|
|
663
|
+
# both mappings. Without them Docker starts a healthy but host-unreachable
|
|
664
|
+
# instance and every later string replacement remains a silent no-op.
|
|
665
|
+
if web_mapping not in content and socketio_mapping not in content:
|
|
666
|
+
working_dir = f" working_dir: {bench_parent_path}"
|
|
667
|
+
published_ports = (
|
|
668
|
+
f'{working_dir}\n ports:\n - "{web_mapping}"\n - "{socketio_mapping}"'
|
|
669
|
+
)
|
|
670
|
+
content = content.replace(working_dir, published_ports, 1)
|
|
671
|
+
if web_mapping not in content or socketio_mapping not in content:
|
|
672
|
+
compose_path.unlink()
|
|
673
|
+
raise CwcliError(
|
|
674
|
+
ErrorKind.PRECONDITION,
|
|
675
|
+
"compose.ports_missing",
|
|
676
|
+
"The downloaded Docker Compose template could not be configured "
|
|
677
|
+
"with the required host port mappings.",
|
|
678
|
+
hint="Retry init. If the error persists, report the upstream compose change.",
|
|
679
|
+
)
|
|
620
680
|
compose_path.write_text(content)
|
|
621
681
|
|
|
622
682
|
compose_base = ["docker", "compose", "-p", project_name, "-f", "docker-compose.yml"]
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/scale.py
RENAMED
|
@@ -73,16 +73,17 @@ _WEB_CONTAINER_BASE = 8000
|
|
|
73
73
|
_SOCKETIO_CONTAINER_BASE = 9000
|
|
74
74
|
|
|
75
75
|
# The published ``ports:`` lines cwcli writes at init look like
|
|
76
|
-
# ``- 16000-16005:8000-8005`` (web) and ``- 17000-17005:9000-9005`` (socketio)
|
|
76
|
+
# ``- 16000-16005:8000-8005`` (web) and ``- 17000-17005:9000-9005`` (socketio),
|
|
77
|
+
# optionally quoted as YAML strings.
|
|
77
78
|
# Capture the host low bound and the container high bound so we know the current
|
|
78
79
|
# published count and the host base to preserve when widening.
|
|
79
80
|
_WEB_PORTS_RE = re.compile(
|
|
80
|
-
rf"(?m)^(?P<prefix>\s*-\s*)(?P<host_lo>\d+)-(?P<host_hi>\d+):"
|
|
81
|
-
rf"{_WEB_CONTAINER_BASE}-(?P<cont_hi>\d+)\s*$"
|
|
81
|
+
rf"(?m)^(?P<prefix>\s*-\s*)(?P<quote>['\"]?)(?P<host_lo>\d+)-(?P<host_hi>\d+):"
|
|
82
|
+
rf"{_WEB_CONTAINER_BASE}-(?P<cont_hi>\d+)(?P=quote)\s*$"
|
|
82
83
|
)
|
|
83
84
|
_SOCKETIO_PORTS_RE = re.compile(
|
|
84
|
-
rf"(?m)^(?P<prefix>\s*-\s*)(?P<host_lo>\d+)-(?P<host_hi>\d+):"
|
|
85
|
-
rf"{_SOCKETIO_CONTAINER_BASE}-(?P<cont_hi>\d+)\s*$"
|
|
85
|
+
rf"(?m)^(?P<prefix>\s*-\s*)(?P<quote>['\"]?)(?P<host_lo>\d+)-(?P<host_hi>\d+):"
|
|
86
|
+
rf"{_SOCKETIO_CONTAINER_BASE}-(?P<cont_hi>\d+)(?P=quote)\s*$"
|
|
86
87
|
)
|
|
87
88
|
|
|
88
89
|
|
|
@@ -218,15 +219,17 @@ def _widen_ports_block(compose_text: str, published: _PublishedRange, count: int
|
|
|
218
219
|
sio_hi_cont = _SOCKETIO_CONTAINER_BASE + count - 1
|
|
219
220
|
|
|
220
221
|
def web_repl(match: re.Match) -> str:
|
|
222
|
+
quote = match.group("quote")
|
|
221
223
|
return (
|
|
222
|
-
f"{match.group('prefix')}{published.web_base}-{web_hi_host}:"
|
|
223
|
-
f"{_WEB_CONTAINER_BASE}-{web_hi_cont}"
|
|
224
|
+
f"{match.group('prefix')}{quote}{published.web_base}-{web_hi_host}:"
|
|
225
|
+
f"{_WEB_CONTAINER_BASE}-{web_hi_cont}{quote}"
|
|
224
226
|
)
|
|
225
227
|
|
|
226
228
|
def sio_repl(match: re.Match) -> str:
|
|
229
|
+
quote = match.group("quote")
|
|
227
230
|
return (
|
|
228
|
-
f"{match.group('prefix')}{published.socketio_base}-{sio_hi_host}:"
|
|
229
|
-
f"{_SOCKETIO_CONTAINER_BASE}-{sio_hi_cont}"
|
|
231
|
+
f"{match.group('prefix')}{quote}{published.socketio_base}-{sio_hi_host}:"
|
|
232
|
+
f"{_SOCKETIO_CONTAINER_BASE}-{sio_hi_cont}{quote}"
|
|
230
233
|
)
|
|
231
234
|
|
|
232
235
|
compose_text = _WEB_PORTS_RE.sub(web_repl, compose_text, count=1)
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/start.py
RENAMED
|
@@ -107,6 +107,7 @@ def start(
|
|
|
107
107
|
for container in containers:
|
|
108
108
|
if container.status != "running":
|
|
109
109
|
container.start()
|
|
110
|
+
container.reload()
|
|
110
111
|
|
|
111
112
|
frappe_container = next(
|
|
112
113
|
(c for c in containers if c.labels.get("com.docker.compose.service") == "frappe"),
|
|
@@ -159,6 +160,18 @@ def start(
|
|
|
159
160
|
snapshot = supervision.discover_stack(frappe_container, resolved_path)
|
|
160
161
|
if snapshot.supervisor_up:
|
|
161
162
|
if not restart:
|
|
163
|
+
idle_processes = _launched_processes(frappe_container, resolved_path, snapshot)
|
|
164
|
+
idle_warnings = list(warnings)
|
|
165
|
+
if "web" in {p.label for p in idle_processes}:
|
|
166
|
+
idle_ports = resolvers.resolve_assigned_ports(
|
|
167
|
+
frappe_container, [resolved_path], fill_defaults=False
|
|
168
|
+
).get(resolved_path)
|
|
169
|
+
if idle_ports is not None:
|
|
170
|
+
port_warning = _no_host_port_warning(
|
|
171
|
+
frappe_container, project_name, resolved_path, idle_ports
|
|
172
|
+
)
|
|
173
|
+
if port_warning is not None:
|
|
174
|
+
idle_warnings.append(port_warning)
|
|
162
175
|
return Result(
|
|
163
176
|
status=Status.OK,
|
|
164
177
|
data=StartOutcome(
|
|
@@ -168,9 +181,9 @@ def start(
|
|
|
168
181
|
supervisor=supervision.SUPERVISOR,
|
|
169
182
|
log_path=log_path,
|
|
170
183
|
already_running=True,
|
|
171
|
-
processes=
|
|
184
|
+
processes=idle_processes,
|
|
172
185
|
),
|
|
173
|
-
warnings=
|
|
186
|
+
warnings=idle_warnings,
|
|
174
187
|
)
|
|
175
188
|
supervision.stop_supervisor(frappe_container, resolved_path)
|
|
176
189
|
|
|
@@ -218,6 +231,11 @@ def start(
|
|
|
218
231
|
f"on :{ports[0]} in time. Check 'cwcli status' / 'cwcli logs'.",
|
|
219
232
|
)
|
|
220
233
|
)
|
|
234
|
+
port_warning = _no_host_port_warning(
|
|
235
|
+
frappe_container, project_name, resolved_path, ports
|
|
236
|
+
)
|
|
237
|
+
if port_warning is not None:
|
|
238
|
+
warnings.append(port_warning)
|
|
221
239
|
|
|
222
240
|
return Result(
|
|
223
241
|
status=Status.OK,
|
|
@@ -235,6 +253,29 @@ def start(
|
|
|
235
253
|
)
|
|
236
254
|
|
|
237
255
|
|
|
256
|
+
def _no_host_port_warning(
|
|
257
|
+
frappe_container, project_name: str, resolved_path: str, ports: tuple[int, int]
|
|
258
|
+
) -> Message | None:
|
|
259
|
+
"""An unmissable warning when this bench's web server has no live HOST
|
|
260
|
+
publish - "running" must stop meaning "reachable". A bench whose container
|
|
261
|
+
was created (or recreated) without its port mapping otherwise reports
|
|
262
|
+
fully healthy here and is only ever discovered by a caller's HTTP request
|
|
263
|
+
failing. Silent when a host port IS published; ``ports`` is the bench's
|
|
264
|
+
OWN container port, already resolved by the caller.
|
|
265
|
+
"""
|
|
266
|
+
if (
|
|
267
|
+
resolvers.resolve_host_web_url(frappe_container, resolved_path, assigned_ports=ports)
|
|
268
|
+
is not None
|
|
269
|
+
):
|
|
270
|
+
return None
|
|
271
|
+
return Message(
|
|
272
|
+
"start.no_host_port",
|
|
273
|
+
f"'{project_name}' is running, but no host port is published for this bench's "
|
|
274
|
+
f"web server (container port {ports[0]}) - it is not reachable from outside the "
|
|
275
|
+
"container. Recreate the instance with 'cwcli init' to publish its ports.",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
238
279
|
def _launched_processes(frappe_container, bench_path, snapshot) -> list[ProcessLaunch]:
|
|
239
280
|
"""The launched set: every expected Procfile label, with its PID if already up.
|
|
240
281
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.3.0"
|
|
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
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/commands/rm.py
RENAMED
|
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
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/apps.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/backup.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/docker.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/doctor.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/errors.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/fleet.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/label.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/list.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/logs.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/open.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/rm.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/run.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/status.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/stop.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/unlock.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/update.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/url.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/core/where.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/main.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/cache.py
RENAMED
|
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
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/tips.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.3.1}/src/caffeinated_whale_cli/utils/toon.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|