caffeinated-whale-cli 2.3.0__tar.gz → 2.4.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.
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/PKG-INFO +12 -3
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/README.md +11 -2
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/pyproject.toml +1 -1
- caffeinated_whale_cli-2.4.0/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/axi.py +9 -1
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/init.py +24 -4
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/start.py +12 -2
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/init.py +113 -28
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/scale.py +12 -9
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/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.4.0}/LICENSE +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/setup.cfg +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/apps.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/console.html +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/rm.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/run.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/serve.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/stop.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/apps.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/bench_ops.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/docker.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/doctor.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/resolvers.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/rm.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/stop.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/update.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/main.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/cache.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
- {caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/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
|
+
Version: 2.4.0
|
|
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
|
|
@@ -201,6 +201,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
|
|
|
201
201
|
| `--bench-parent TEXT` | Directory inside container where bench is created (default: /workspace); also the container mount point for the persisted host `data/` directory, fixed for the life of the instance - changing it on a re-init errors, naming the mounted directory |
|
|
202
202
|
| `--frappe-branch TEXT` | Frappe branch or tag for bench init, e.g. `version-16` or `v16.26.3` (default: version-16). Mutually exclusive with `--version` |
|
|
203
203
|
| `--version TEXT` | Frappe version for bench init, resolved by shape: a bare major (`16` → `version-16` branch) or a full semantic version (`16.26.3` → `v16.26.3` tag). Malformed values are rejected with a non-zero exit. Mutually exclusive with `--frappe-branch` |
|
|
204
|
+
| `--frappe-url TEXT` | Custom Frappe repository URL to build from (a fork), passed to `bench init --frappe-path`. Omit to use the default `frappe/frappe` repo. Pair with `--frappe-branch` to check out the fork's branch. A private fork authenticates through the same credential bridge as `apps install`/`apps update` (host `gh`/`glab`, no token in the container); sign in on the host first |
|
|
204
205
|
| `--db-root-password TEXT` | MariaDB root password (default: 123) |
|
|
205
206
|
| `--admin-password TEXT` | Administrator password for the site, used verbatim. If omitted, a strong password is generated and printed once (interactive runs only); a non-interactive run must supply this flag |
|
|
206
207
|
| `--install-erpnext` | Install ERPNext application after initialization |
|
|
@@ -271,6 +272,9 @@ cwcli init my-project --version 16
|
|
|
271
272
|
# ...or a full semantic version -> vX.Y.Z tag
|
|
272
273
|
cwcli init my-project --version 16.26.3
|
|
273
274
|
|
|
275
|
+
# Build from a Frappe fork, checking out its branch
|
|
276
|
+
cwcli init my-project --frappe-url https://github.com/me/frappe --frappe-branch my-feature
|
|
277
|
+
|
|
274
278
|
# Full customization
|
|
275
279
|
cwcli init my-project \
|
|
276
280
|
--port 12000 \
|
|
@@ -320,15 +324,19 @@ Then open http://development.localhost:8000 (or `cwcli open my-project`).
|
|
|
320
324
|
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
325
|
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
326
|
If the port cannot be read, start skips the wait and warns instead of probing another bench's port.
|
|
327
|
+
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
328
|
|
|
324
329
|
**Port Conflict Handling:**
|
|
325
330
|
|
|
326
|
-
|
|
331
|
+
Init retries occupied ports five times, one second apart, to allow bindings from a just-removed instance to finish releasing.
|
|
332
|
+
Each retry is announced in both interactive and non-interactive runs.
|
|
333
|
+
If the ports remain occupied, init exits nonzero before creating the instance and names every conflicting port:
|
|
327
334
|
|
|
328
335
|
```
|
|
329
336
|
Error: The following ports are already in use: 8000-8005
|
|
330
337
|
|
|
331
|
-
Tip:
|
|
338
|
+
Tip: If an instance using these ports was just removed, they may still be releasing - wait a few seconds and retry.
|
|
339
|
+
Tip: Otherwise, use the --port flag to select a different starting port.
|
|
332
340
|
Example: cwcli init my-project --port 10000
|
|
333
341
|
```
|
|
334
342
|
|
|
@@ -498,6 +506,7 @@ cwcli start [OPTIONS] [PROJECT_NAME]...
|
|
|
498
506
|
- **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
507
|
- **Per-process supervision:** each Procfile process runs under supervisord, so one can be restarted or auto-healed without disturbing the others
|
|
500
508
|
- **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`
|
|
509
|
+
- **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
510
|
- **Port Conflict Detection:** Automatically checks if required ports are available
|
|
502
511
|
- **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
|
|
503
512
|
- **Process Identification:** Shows which processes are using ports (cross-platform)
|
|
@@ -161,6 +161,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
|
|
|
161
161
|
| `--bench-parent TEXT` | Directory inside container where bench is created (default: /workspace); also the container mount point for the persisted host `data/` directory, fixed for the life of the instance - changing it on a re-init errors, naming the mounted directory |
|
|
162
162
|
| `--frappe-branch TEXT` | Frappe branch or tag for bench init, e.g. `version-16` or `v16.26.3` (default: version-16). Mutually exclusive with `--version` |
|
|
163
163
|
| `--version TEXT` | Frappe version for bench init, resolved by shape: a bare major (`16` → `version-16` branch) or a full semantic version (`16.26.3` → `v16.26.3` tag). Malformed values are rejected with a non-zero exit. Mutually exclusive with `--frappe-branch` |
|
|
164
|
+
| `--frappe-url TEXT` | Custom Frappe repository URL to build from (a fork), passed to `bench init --frappe-path`. Omit to use the default `frappe/frappe` repo. Pair with `--frappe-branch` to check out the fork's branch. A private fork authenticates through the same credential bridge as `apps install`/`apps update` (host `gh`/`glab`, no token in the container); sign in on the host first |
|
|
164
165
|
| `--db-root-password TEXT` | MariaDB root password (default: 123) |
|
|
165
166
|
| `--admin-password TEXT` | Administrator password for the site, used verbatim. If omitted, a strong password is generated and printed once (interactive runs only); a non-interactive run must supply this flag |
|
|
166
167
|
| `--install-erpnext` | Install ERPNext application after initialization |
|
|
@@ -231,6 +232,9 @@ cwcli init my-project --version 16
|
|
|
231
232
|
# ...or a full semantic version -> vX.Y.Z tag
|
|
232
233
|
cwcli init my-project --version 16.26.3
|
|
233
234
|
|
|
235
|
+
# Build from a Frappe fork, checking out its branch
|
|
236
|
+
cwcli init my-project --frappe-url https://github.com/me/frappe --frappe-branch my-feature
|
|
237
|
+
|
|
234
238
|
# Full customization
|
|
235
239
|
cwcli init my-project \
|
|
236
240
|
--port 12000 \
|
|
@@ -280,15 +284,19 @@ Then open http://development.localhost:8000 (or `cwcli open my-project`).
|
|
|
280
284
|
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
285
|
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
286
|
If the port cannot be read, start skips the wait and warns instead of probing another bench's port.
|
|
287
|
+
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
288
|
|
|
284
289
|
**Port Conflict Handling:**
|
|
285
290
|
|
|
286
|
-
|
|
291
|
+
Init retries occupied ports five times, one second apart, to allow bindings from a just-removed instance to finish releasing.
|
|
292
|
+
Each retry is announced in both interactive and non-interactive runs.
|
|
293
|
+
If the ports remain occupied, init exits nonzero before creating the instance and names every conflicting port:
|
|
287
294
|
|
|
288
295
|
```
|
|
289
296
|
Error: The following ports are already in use: 8000-8005
|
|
290
297
|
|
|
291
|
-
Tip:
|
|
298
|
+
Tip: If an instance using these ports was just removed, they may still be releasing - wait a few seconds and retry.
|
|
299
|
+
Tip: Otherwise, use the --port flag to select a different starting port.
|
|
292
300
|
Example: cwcli init my-project --port 10000
|
|
293
301
|
```
|
|
294
302
|
|
|
@@ -458,6 +466,7 @@ cwcli start [OPTIONS] [PROJECT_NAME]...
|
|
|
458
466
|
- **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
467
|
- **Per-process supervision:** each Procfile process runs under supervisord, so one can be restarted or auto-healed without disturbing the others
|
|
460
468
|
- **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`
|
|
469
|
+
- **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
470
|
- **Port Conflict Detection:** Automatically checks if required ports are available
|
|
462
471
|
- **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
|
|
463
472
|
- **Process Identification:** Shows which processes are using ports (cross-platform)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.4.0"
|
|
@@ -2157,6 +2157,13 @@ def axi_init(
|
|
|
2157
2157
|
help="Frappe version resolved by shape: a bare major (16 -> version-16) or a full "
|
|
2158
2158
|
"semantic version (16.26.3 -> v16.26.3). Mutually exclusive with --frappe-branch.",
|
|
2159
2159
|
),
|
|
2160
|
+
frappe_url: str = typer.Option(
|
|
2161
|
+
None,
|
|
2162
|
+
"--frappe-url",
|
|
2163
|
+
help="Custom Frappe repository URL to build from (a fork), passed to 'bench init "
|
|
2164
|
+
"--frappe-path'. Omit for the default frappe/frappe repo. Pair with --frappe-branch "
|
|
2165
|
+
"to check out the fork's branch.",
|
|
2166
|
+
),
|
|
2160
2167
|
db_root_password: str = typer.Option(
|
|
2161
2168
|
None,
|
|
2162
2169
|
"--db-root-password",
|
|
@@ -2283,6 +2290,7 @@ def axi_init(
|
|
|
2283
2290
|
site_name=site,
|
|
2284
2291
|
bench_parent=bench_parent,
|
|
2285
2292
|
frappe_ref=frappe_ref,
|
|
2293
|
+
frappe_url=frappe_url,
|
|
2286
2294
|
db_root_password=db_root,
|
|
2287
2295
|
admin_password=resolved_admin,
|
|
2288
2296
|
reuse_bench=reuse_bench,
|
|
@@ -2336,7 +2344,7 @@ def axi_init(
|
|
|
2336
2344
|
)
|
|
2337
2345
|
else:
|
|
2338
2346
|
for warning in start_result.warnings:
|
|
2339
|
-
if warning.code
|
|
2347
|
+
if warning.code in ("start.web_not_ready", "start.no_host_port"):
|
|
2340
2348
|
print(f"Warning: {warning.text}", file=sys.stderr, flush=True)
|
|
2341
2349
|
|
|
2342
2350
|
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
|
|
@@ -478,6 +487,15 @@ def init(
|
|
|
478
487
|
"(16.26.3 -> v16.26.3 tag). Mutually exclusive with --frappe-branch."
|
|
479
488
|
),
|
|
480
489
|
),
|
|
490
|
+
frappe_url: str | None = typer.Option(
|
|
491
|
+
None,
|
|
492
|
+
"--frappe-url",
|
|
493
|
+
help=(
|
|
494
|
+
"Custom Frappe repository URL to build from (a fork), passed to "
|
|
495
|
+
"'bench init --frappe-path'. Omit to use the default frappe/frappe repo. "
|
|
496
|
+
"Pair with --frappe-branch to check out the fork's branch."
|
|
497
|
+
),
|
|
498
|
+
),
|
|
481
499
|
db_root_password: str = typer.Option(
|
|
482
500
|
"123",
|
|
483
501
|
"--db-root-password",
|
|
@@ -553,6 +571,7 @@ def init(
|
|
|
553
571
|
cwcli init my-project --version 16 --install-erpnext
|
|
554
572
|
cwcli init my-project --version 16.26.3
|
|
555
573
|
cwcli init my-project --frappe-branch version-16 --admin-password mypass
|
|
574
|
+
cwcli init my-project --frappe-url https://github.com/me/frappe --frappe-branch my-feature
|
|
556
575
|
"""
|
|
557
576
|
# Resolve the Frappe git ref first so a malformed --version fails fast,
|
|
558
577
|
# before any project dir / container work.
|
|
@@ -635,6 +654,7 @@ def init(
|
|
|
635
654
|
site_name=site,
|
|
636
655
|
bench_parent=bench_parent,
|
|
637
656
|
frappe_ref=frappe_branch,
|
|
657
|
+
frappe_url=frappe_url,
|
|
638
658
|
db_root_password=db_root_password,
|
|
639
659
|
admin_password=admin_password,
|
|
640
660
|
reuse_bench=reuse_answer,
|
|
@@ -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.4.0}/src/caffeinated_whale_cli/core/init.py
RENAMED
|
@@ -30,6 +30,7 @@ value the old path did not print.
|
|
|
30
30
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
|
+
import contextlib
|
|
33
34
|
import json
|
|
34
35
|
import re
|
|
35
36
|
import shlex
|
|
@@ -42,8 +43,8 @@ from pathlib import Path
|
|
|
42
43
|
|
|
43
44
|
from ..utils import config_utils, db_utils
|
|
44
45
|
from ..utils.port_utils import check_ports_in_use, format_port_list
|
|
46
|
+
from . import credbridge, resolvers
|
|
45
47
|
from . import docker as core_docker
|
|
46
|
-
from . import resolvers
|
|
47
48
|
from .envelope import Choice, Message, Result, Status
|
|
48
49
|
from .errors import CwcliError, ErrorKind
|
|
49
50
|
from .exec_stream import ExecChunk, ExecDone, exec_stream
|
|
@@ -472,6 +473,43 @@ def _project_containers_running(project_name: str) -> bool:
|
|
|
472
473
|
|
|
473
474
|
_EXPECTED_COMPOSE_SERVICES = frozenset({"frappe", "mariadb", "redis-cache", "redis-queue"})
|
|
474
475
|
|
|
476
|
+
# A just-removed instance's own port bindings can take a moment to fully
|
|
477
|
+
# release (Docker's docker-proxy teardown lag, observed in the field to clear
|
|
478
|
+
# within seconds), so a rapid remove-then-reinit (a CI purge-and-recreate
|
|
479
|
+
# pattern) can see those SAME ports as still "in use" right after the old
|
|
480
|
+
# instance is gone. 5 attempts a second apart absorbs that ordinary teardown
|
|
481
|
+
# window before concluding the conflict is real and persistent.
|
|
482
|
+
_PORT_CHECK_ATTEMPTS = 5
|
|
483
|
+
_PORT_CHECK_RETRY_DELAY = 1.0
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _ports_still_in_use(ports: list[int], *, emit: OnEvent) -> list[int]:
|
|
487
|
+
"""Check host ports, retrying briefly before giving up.
|
|
488
|
+
|
|
489
|
+
Never silently treats a conflict as "no conflict" - it only delays the
|
|
490
|
+
verdict to absorb a transient teardown window. A port still in use after
|
|
491
|
+
every attempt is reported exactly as before: loudly, deterministically,
|
|
492
|
+
regardless of interactivity.
|
|
493
|
+
"""
|
|
494
|
+
ports_in_use: list[int] = []
|
|
495
|
+
for attempt in range(_PORT_CHECK_ATTEMPTS):
|
|
496
|
+
port_status = check_ports_in_use(ports)
|
|
497
|
+
ports_in_use = [p for p, in_use in port_status.items() if in_use]
|
|
498
|
+
if not ports_in_use:
|
|
499
|
+
return []
|
|
500
|
+
if attempt < _PORT_CHECK_ATTEMPTS - 1:
|
|
501
|
+
emit(
|
|
502
|
+
InitNotice(
|
|
503
|
+
code="ports.retry",
|
|
504
|
+
text=(
|
|
505
|
+
f"Port(s) {format_port_list(ports_in_use)} still in use "
|
|
506
|
+
"(may be a just-removed instance releasing them); retrying..."
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
time.sleep(_PORT_CHECK_RETRY_DELAY)
|
|
511
|
+
return ports_in_use
|
|
512
|
+
|
|
475
513
|
|
|
476
514
|
def _running_compose_services(project_name: str) -> set[str]:
|
|
477
515
|
"""Return this project's running compose service names."""
|
|
@@ -532,14 +570,17 @@ def init_instance(
|
|
|
532
570
|
if not auto_start and not _project_containers_running(project_name):
|
|
533
571
|
web_ports = list(range(port, port + 6))
|
|
534
572
|
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]
|
|
573
|
+
ports_in_use = _ports_still_in_use(web_ports + socketio_ports, emit=emit)
|
|
537
574
|
if ports_in_use:
|
|
538
575
|
raise CwcliError(
|
|
539
576
|
ErrorKind.CONFLICT,
|
|
540
577
|
"ports.in_use",
|
|
541
578
|
f"The following ports are already in use: {format_port_list(ports_in_use)}",
|
|
542
|
-
hint=
|
|
579
|
+
hint=(
|
|
580
|
+
"If an instance using these ports was just removed, they may still be "
|
|
581
|
+
"releasing - wait a few seconds and retry. Otherwise, use --port to "
|
|
582
|
+
"select a different starting port."
|
|
583
|
+
),
|
|
543
584
|
)
|
|
544
585
|
|
|
545
586
|
emit(InitStepStart(phase="project_dir", message="Creating project directory"))
|
|
@@ -581,11 +622,11 @@ def init_instance(
|
|
|
581
622
|
)
|
|
582
623
|
)
|
|
583
624
|
content = compose_path.read_text()
|
|
584
|
-
|
|
625
|
+
web_mapping = f"{port}-{port + 5}:8000-8005"
|
|
626
|
+
content = content.replace("8000-8005:8000-8005", web_mapping)
|
|
585
627
|
socketio_start = port + 1000
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
)
|
|
628
|
+
socketio_mapping = f"{socketio_start}-{socketio_start + 5}:9000-9005"
|
|
629
|
+
content = content.replace("9000-9005:9000-9005", socketio_mapping)
|
|
589
630
|
# The upstream devcontainer template sets `working_dir: /workspace/development`
|
|
590
631
|
# - a path cwcli never creates (its bench lives at /workspace/frappe-bench).
|
|
591
632
|
# /workspace is a bind mount to CWCLI_HOME/projects/<name>/, so on `compose up`
|
|
@@ -617,6 +658,26 @@ def init_instance(
|
|
|
617
658
|
(project_dir / "data").mkdir(parents=True, exist_ok=True)
|
|
618
659
|
content = content.replace("- ..:/workspace:cached", f"- ../data:{bench_parent_path}:cached")
|
|
619
660
|
content = content.replace("working_dir: /workspace", f"working_dir: {bench_parent_path}")
|
|
661
|
+
# frappe_docker removed the devcontainer template's ports block in favor
|
|
662
|
+
# of editor-managed forwarding. cwcli runs the compose project directly,
|
|
663
|
+
# so it must add the host publications when the downloaded template omits
|
|
664
|
+
# both mappings. Without them Docker starts a healthy but host-unreachable
|
|
665
|
+
# instance and every later string replacement remains a silent no-op.
|
|
666
|
+
if web_mapping not in content and socketio_mapping not in content:
|
|
667
|
+
working_dir = f" working_dir: {bench_parent_path}"
|
|
668
|
+
published_ports = (
|
|
669
|
+
f'{working_dir}\n ports:\n - "{web_mapping}"\n - "{socketio_mapping}"'
|
|
670
|
+
)
|
|
671
|
+
content = content.replace(working_dir, published_ports, 1)
|
|
672
|
+
if web_mapping not in content or socketio_mapping not in content:
|
|
673
|
+
compose_path.unlink()
|
|
674
|
+
raise CwcliError(
|
|
675
|
+
ErrorKind.PRECONDITION,
|
|
676
|
+
"compose.ports_missing",
|
|
677
|
+
"The downloaded Docker Compose template could not be configured "
|
|
678
|
+
"with the required host port mappings.",
|
|
679
|
+
hint="Retry init. If the error persists, report the upstream compose change.",
|
|
680
|
+
)
|
|
620
681
|
compose_path.write_text(content)
|
|
621
682
|
|
|
622
683
|
compose_base = ["docker", "compose", "-p", project_name, "-f", "docker-compose.yml"]
|
|
@@ -806,6 +867,7 @@ def init_bench(
|
|
|
806
867
|
site_name: str,
|
|
807
868
|
bench_parent: str = "/workspace",
|
|
808
869
|
frappe_ref: str = DEFAULT_FRAPPE_BRANCH,
|
|
870
|
+
frappe_url: str | None = None,
|
|
809
871
|
db_root_password: str = "123",
|
|
810
872
|
admin_password: str,
|
|
811
873
|
reuse_bench: bool | None = None,
|
|
@@ -827,6 +889,16 @@ def init_bench(
|
|
|
827
889
|
it mirrors - the downloaded compose's hardcoded ``MYSQL_ROOT_PASSWORD:
|
|
828
890
|
123`` - lives in the compose file the core itself downloads; it is
|
|
829
891
|
shielded off the argv/echo, not randomized.
|
|
892
|
+
|
|
893
|
+
``frappe_url`` is the optional custom Frappe repository (a fork) passed
|
|
894
|
+
through to ``bench init --frappe-path``; ``None`` (the default) builds from
|
|
895
|
+
the upstream ``frappe/frappe`` repo exactly as before. ``frappe_ref`` is the
|
|
896
|
+
branch/tag within that repo, so a fork tracking a custom branch pairs the two
|
|
897
|
+
(``frappe_url=<fork>``, ``frappe_ref=<branch>``). When ``frappe_url`` is a
|
|
898
|
+
PRIVATE fork, the ``bench init`` clone is wrapped in the same
|
|
899
|
+
:func:`core.credbridge.credential_bridge` context ``apps`` install/update
|
|
900
|
+
use, so it authenticates through the host's ``gh``/``glab`` with no token
|
|
901
|
+
ever entering the container.
|
|
830
902
|
"""
|
|
831
903
|
emit = on_event or _noop
|
|
832
904
|
warnings: list[Message] = []
|
|
@@ -981,31 +1053,44 @@ def init_bench(
|
|
|
981
1053
|
else:
|
|
982
1054
|
emit(InitTrace(text="yarn installed successfully."))
|
|
983
1055
|
|
|
1056
|
+
bench_init_args = [
|
|
1057
|
+
"bench",
|
|
1058
|
+
"init",
|
|
1059
|
+
"--skip-redis-config-generation",
|
|
1060
|
+
"--frappe-branch",
|
|
1061
|
+
shlex.quote(frappe_ref),
|
|
1062
|
+
]
|
|
1063
|
+
# A custom Frappe repo (a fork) rides bench's own --frappe-path; frappe_ref
|
|
1064
|
+
# above is the branch/tag to check out within it. Omitted -> upstream repo.
|
|
1065
|
+
if frappe_url:
|
|
1066
|
+
bench_init_args += ["--frappe-path", shlex.quote(frappe_url)]
|
|
1067
|
+
bench_init_args += [shlex.quote(bench_name), "--verbose"]
|
|
984
1068
|
bench_init_cmd = _build_cd_command(
|
|
985
|
-
bench_parent_path,
|
|
986
|
-
nvm_prefix
|
|
987
|
-
+ env_prefix
|
|
988
|
-
+ " ".join(
|
|
989
|
-
[
|
|
990
|
-
"bench",
|
|
991
|
-
"init",
|
|
992
|
-
"--skip-redis-config-generation",
|
|
993
|
-
"--frappe-branch",
|
|
994
|
-
shlex.quote(frappe_ref),
|
|
995
|
-
shlex.quote(bench_name),
|
|
996
|
-
"--verbose",
|
|
997
|
-
]
|
|
998
|
-
),
|
|
1069
|
+
bench_parent_path, nvm_prefix + env_prefix + " ".join(bench_init_args)
|
|
999
1070
|
)
|
|
1000
1071
|
|
|
1001
1072
|
emit(InitStepStart(phase="bench_init", item=bench_name))
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1073
|
+
# A custom Frappe fork may be a PRIVATE repo, so bridge the container's
|
|
1074
|
+
# git back to the host's gh/glab for the clone `bench init` performs -
|
|
1075
|
+
# the same context apps install/update wrap their get-app fetch in
|
|
1076
|
+
# (see core.credbridge). The upstream default (frappe_url is None) is the
|
|
1077
|
+
# public frappe/frappe repo and needs no bridge, so that common path is
|
|
1078
|
+
# left untouched; the bridge is inert for a public fork too (git only
|
|
1079
|
+
# calls a credential helper on a 401). bench_full_path is under the
|
|
1080
|
+
# workspace mount, which is what credbridge resolves against.
|
|
1081
|
+
bridge: contextlib.AbstractContextManager[None] = (
|
|
1082
|
+
credbridge.credential_bridge(frappe_container, bench_full_path)
|
|
1083
|
+
if frappe_url
|
|
1084
|
+
else contextlib.nullcontext()
|
|
1008
1085
|
)
|
|
1086
|
+
with bridge:
|
|
1087
|
+
_run_exec(
|
|
1088
|
+
frappe_container,
|
|
1089
|
+
bench_init_cmd,
|
|
1090
|
+
phase="bench_init",
|
|
1091
|
+
emit=emit,
|
|
1092
|
+
collect=not stream_output,
|
|
1093
|
+
)
|
|
1009
1094
|
emit(InitStepEnd(phase="bench_init"))
|
|
1010
1095
|
bench_created = True
|
|
1011
1096
|
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/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.4.0}/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.4.0}/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.4.0}/src/caffeinated_whale_cli/core/apps.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/backup.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/docker.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/doctor.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/errors.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/fleet.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/label.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/list.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/logs.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/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.4.0}/src/caffeinated_whale_cli/core/rm.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/run.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/status.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/stop.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/unlock.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/update.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/url.py
RENAMED
|
File without changes
|
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/core/where.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/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.4.0}/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.4.0}/src/caffeinated_whale_cli/utils/tips.py
RENAMED
|
File without changes
|
{caffeinated_whale_cli-2.3.0 → caffeinated_whale_cli-2.4.0}/src/caffeinated_whale_cli/utils/toon.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|