caffeinated-whale-cli 3.3.0__tar.gz → 3.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-3.3.0 → caffeinated_whale_cli-3.4.0}/PKG-INFO +10 -1
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/README.md +9 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/pyproject.toml +1 -1
- caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/apps.py +11 -6
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/axi.py +182 -22
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/console.html +8 -3
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/init.py +12 -6
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm.py +10 -2
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_bench.py +5 -2
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_site.py +5 -2
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/run.py +20 -8
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/serve.py +31 -2
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/stop.py +8 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/update.py +15 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/apps.py +84 -65
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/bench_ops.py +159 -1
- caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/core/bench_read.py +393 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/docker.py +33 -5
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/exec_stream.py +24 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/init.py +3 -5
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/inspect.py +311 -149
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/resolvers.py +39 -18
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm.py +30 -3
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/setup.py +24 -5
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/start.py +75 -1
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/stop.py +50 -2
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/update.py +38 -33
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_labels.py +9 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_sites.py +42 -38
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/cache.py +35 -9
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +1 -0
- caffeinated_whale_cli-3.3.0/src/caffeinated_whale_cli/__init__.py +0 -1
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/LICENSE +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/setup.cfg +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/__main__.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/setup.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/cred_bridge.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/doctor.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_bench.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/scale.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/main.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/cred_daemon.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/shared_home.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/signals.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caffeinated-whale-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.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
|
|
@@ -626,6 +626,11 @@ supervisor rather than stopping any container. Sibling benches keep serving, the
|
|
|
626
626
|
containers stay up, and `cwcli start --bench` brings it back. Stopping a bench
|
|
627
627
|
that is already stopped is a success, not an error.
|
|
628
628
|
|
|
629
|
+
A whole-instance stop takes the database down last and gives it up to 60 seconds
|
|
630
|
+
to finish its shutdown. If the database exits non-zero (137 means it was killed
|
|
631
|
+
before it finished shutting down), `stop` says so and exits non-zero instead of
|
|
632
|
+
reporting a clean stop.
|
|
633
|
+
|
|
629
634
|
**Example:**
|
|
630
635
|
|
|
631
636
|
```bash
|
|
@@ -2579,8 +2584,12 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2579
2584
|
|
|
2580
2585
|
These are agent-only for now: there is no human `cwcli migrate`, `cwcli run-tests`, or `cwcli build` yet. Use `cwcli run <project> --site <site> migrate` for the human equivalent.
|
|
2581
2586
|
|
|
2587
|
+
`cwcli axi setup-wizard <project> <site> [--country <country>] [--currency <currency>] [--timezone <timezone>]` headlessly completes a site's Frappe setup wizard. A fresh site - whether created by `cwcli init`/`cwcli axi init` without `--complete-setup`, or by a bare `bench new-site` outside cwcli entirely - is left in the setup-wizard state: `frappe.is_setup_complete()` is `false` and the `desktop:home_page` default stays `setup-wizard`, so a non-System-Manager desk renders with no navbar. This runs the same whitelisted RPC a person finishing the wizard in a browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`, called via `bench execute ... --kwargs` with the args dict the wizard UI would have submitted (System Settings' country/currency/timezone/language; omitted flags fall back to `United States`/`USD`/`UTC`/`English`). It is **idempotent by Frappe's own design**: `setup_complete` checks `frappe.is_setup_complete()` first and no-ops under an advisory lock when the site is already set up, so re-running it is always safe - the report's `already_complete` field says whether it found the site already done. No user is created: `email`/`full_name`/`password` are deliberately left out of the args dict, so the `Administrator` account `cwcli init`'s `--admin-password` already provisioned is left untouched. Like the other bench-scoped verbs it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start`.
|
|
2588
|
+
|
|
2582
2589
|
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. Running several `axi init` calls in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1); an invalid `--uid` (0, negative, or non-numeric) is rejected before any container work. `--uid` behaves as it does on `cwcli init` (including the root-host fallback, whose one-time warning is folded into the report's `warnings`). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2583
2590
|
|
|
2591
|
+
`--complete-setup` runs the new site's Frappe setup wizard headlessly right after creation - the same operation `cwcli axi setup-wizard` performs standalone, sharing its exact implementation - so a fresh site does not land in the unfinished setup-wizard state a non-System-Manager desk renders with no navbar in. `--country`/`--currency`/`--timezone` tune it (require `--complete-setup`; each has a sensible default when omitted) and are usage errors on their own. Its outcome rides as a `setup_wizard` block inside the SAME `InitReport` document, not a second one; a failure there is never exit `0` even though the bench and site above it were already created successfully - the `setup_wizard` block still carries what happened.
|
|
2592
|
+
|
|
2584
2593
|
`cwcli axi rm <project> --yes` permanently removes an instance: its containers, named volumes (databases, sites, and files), own Docker Compose network, and local project directory.
|
|
2585
2594
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
2586
2595
|
The verified per-bench backup is taken and checked before anything is destroyed, and a backup that cannot be created and verified **aborts the removal with everything still intact**, so data is never destroyed without a confirmed copy on the host.
|
|
@@ -586,6 +586,11 @@ supervisor rather than stopping any container. Sibling benches keep serving, the
|
|
|
586
586
|
containers stay up, and `cwcli start --bench` brings it back. Stopping a bench
|
|
587
587
|
that is already stopped is a success, not an error.
|
|
588
588
|
|
|
589
|
+
A whole-instance stop takes the database down last and gives it up to 60 seconds
|
|
590
|
+
to finish its shutdown. If the database exits non-zero (137 means it was killed
|
|
591
|
+
before it finished shutting down), `stop` says so and exits non-zero instead of
|
|
592
|
+
reporting a clean stop.
|
|
593
|
+
|
|
589
594
|
**Example:**
|
|
590
595
|
|
|
591
596
|
```bash
|
|
@@ -2539,8 +2544,12 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2539
2544
|
|
|
2540
2545
|
These are agent-only for now: there is no human `cwcli migrate`, `cwcli run-tests`, or `cwcli build` yet. Use `cwcli run <project> --site <site> migrate` for the human equivalent.
|
|
2541
2546
|
|
|
2547
|
+
`cwcli axi setup-wizard <project> <site> [--country <country>] [--currency <currency>] [--timezone <timezone>]` headlessly completes a site's Frappe setup wizard. A fresh site - whether created by `cwcli init`/`cwcli axi init` without `--complete-setup`, or by a bare `bench new-site` outside cwcli entirely - is left in the setup-wizard state: `frappe.is_setup_complete()` is `false` and the `desktop:home_page` default stays `setup-wizard`, so a non-System-Manager desk renders with no navbar. This runs the same whitelisted RPC a person finishing the wizard in a browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`, called via `bench execute ... --kwargs` with the args dict the wizard UI would have submitted (System Settings' country/currency/timezone/language; omitted flags fall back to `United States`/`USD`/`UTC`/`English`). It is **idempotent by Frappe's own design**: `setup_complete` checks `frappe.is_setup_complete()` first and no-ops under an advisory lock when the site is already set up, so re-running it is always safe - the report's `already_complete` field says whether it found the site already done. No user is created: `email`/`full_name`/`password` are deliberately left out of the args dict, so the `Administrator` account `cwcli init`'s `--admin-password` already provisioned is left untouched. Like the other bench-scoped verbs it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start`.
|
|
2548
|
+
|
|
2542
2549
|
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. Running several `axi init` calls in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1); an invalid `--uid` (0, negative, or non-numeric) is rejected before any container work. `--uid` behaves as it does on `cwcli init` (including the root-host fallback, whose one-time warning is folded into the report's `warnings`). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2543
2550
|
|
|
2551
|
+
`--complete-setup` runs the new site's Frappe setup wizard headlessly right after creation - the same operation `cwcli axi setup-wizard` performs standalone, sharing its exact implementation - so a fresh site does not land in the unfinished setup-wizard state a non-System-Manager desk renders with no navbar in. `--country`/`--currency`/`--timezone` tune it (require `--complete-setup`; each has a sensible default when omitted) and are usage errors on their own. Its outcome rides as a `setup_wizard` block inside the SAME `InitReport` document, not a second one; a failure there is never exit `0` even though the bench and site above it were already created successfully - the `setup_wizard` block still carries what happened.
|
|
2552
|
+
|
|
2544
2553
|
`cwcli axi rm <project> --yes` permanently removes an instance: its containers, named volumes (databases, sites, and files), own Docker Compose network, and local project directory.
|
|
2545
2554
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
2546
2555
|
The verified per-bench backup is taken and checked before anything is destroyed, and a backup that cannot be created and verified **aborts the removal with everything still intact**, so data is never destroyed without a confirmed copy on the host.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.4.0"
|
|
@@ -135,18 +135,23 @@ def _make_renderer(*, json_output, verbose):
|
|
|
135
135
|
return on_event
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def _refresh_cache(project_name, verbose):
|
|
138
|
+
def _refresh_cache(project_name, verbose, bench_path):
|
|
139
139
|
"""Refresh the cache through the existing writer so where/open/inspect stop lying.
|
|
140
140
|
|
|
141
|
-
Uses ``cache.recache_project``
|
|
141
|
+
Uses ``cache.recache_project`` scoped to the mutated bench (it persists via
|
|
142
142
|
``cache_project_data`` -> ``_redact_config_for_cache``), never a direct write.
|
|
143
143
|
Degrades to a warning; the mutation itself already succeeded.
|
|
144
144
|
"""
|
|
145
|
-
|
|
145
|
+
warnings: list = []
|
|
146
|
+
if not cache.recache_project(
|
|
147
|
+
project_name, verbose=verbose, bench_path=bench_path, warnings=warnings
|
|
148
|
+
):
|
|
146
149
|
stderr_console.print(
|
|
147
150
|
"[yellow]Warning:[/yellow] app mutation succeeded but refreshing the cache failed; "
|
|
148
151
|
"run 'cwcli inspect --update' to refresh."
|
|
149
152
|
)
|
|
153
|
+
for warning in warnings:
|
|
154
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
def _report_and_exit(report, json_output, *, success_msg):
|
|
@@ -416,7 +421,7 @@ def install_apps(
|
|
|
416
421
|
# Refresh the cache if any step succeeded: a fetch changes available apps, an
|
|
417
422
|
# install changes a site's installed apps - both make the cache stale.
|
|
418
423
|
if any(r.ok for r in report.results):
|
|
419
|
-
_refresh_cache(project_name, verbose)
|
|
424
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
420
425
|
|
|
421
426
|
# The banner must match what actually happened: only claim "installed" when an
|
|
422
427
|
# install-app step ran (not for --fetch-only, a bench with no sites, or an
|
|
@@ -516,7 +521,7 @@ def uninstall_apps(
|
|
|
516
521
|
report = result.data
|
|
517
522
|
assert report is not None # OK/WARNING always carries a report
|
|
518
523
|
if any(r.ok for r in report.results):
|
|
519
|
-
_refresh_cache(project_name, verbose)
|
|
524
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
520
525
|
|
|
521
526
|
_report_and_exit(report, json_output, success_msg="App(s) uninstalled.")
|
|
522
527
|
|
|
@@ -585,7 +590,7 @@ def checkout_app(
|
|
|
585
590
|
# A checkout changes the app's git state (and possibly its reported version), so
|
|
586
591
|
# refresh the cache whenever any git step ran, matching install/update.
|
|
587
592
|
if any(r.ok for r in report.results):
|
|
588
|
-
_refresh_cache(project_name, verbose)
|
|
593
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
589
594
|
|
|
590
595
|
_report_and_exit(report, json_output, success_msg=f"Checked out {ref} into {app}.")
|
|
591
596
|
|
|
@@ -877,7 +877,8 @@ def axi_stop(
|
|
|
877
877
|
|
|
878
878
|
assert result.data is not None # OK always carries a StopOutcome
|
|
879
879
|
emit_result(result.data, warnings=result.warnings)
|
|
880
|
-
|
|
880
|
+
# A database killed mid-shutdown is a failed stop, not a successful one.
|
|
881
|
+
raise typer.Exit(1 if result.data.db_clean_shutdown is False else 0)
|
|
881
882
|
|
|
882
883
|
|
|
883
884
|
def _axi_stop_bench(project: str, bench: str) -> None:
|
|
@@ -925,7 +926,7 @@ def axi_start(
|
|
|
925
926
|
auto-resolves conflicting Frappe projects); a multi-bench project with no
|
|
926
927
|
``--bench`` is a usage error naming ``--bench``.
|
|
927
928
|
"""
|
|
928
|
-
_axi_resolve_port_conflicts(project, yes)
|
|
929
|
+
stop_warnings = _axi_resolve_port_conflicts(project, yes)
|
|
929
930
|
|
|
930
931
|
try:
|
|
931
932
|
result = core_start.start(project, bench=bench, autorestart=autorestart)
|
|
@@ -939,22 +940,23 @@ def axi_start(
|
|
|
939
940
|
raise typer.Exit(2)
|
|
940
941
|
|
|
941
942
|
assert result.data is not None # OK/WARNING always carries a StartOutcome
|
|
942
|
-
emit_result(result.data, warnings=result.warnings)
|
|
943
|
+
emit_result(result.data, warnings=[*stop_warnings, *result.warnings])
|
|
943
944
|
raise typer.Exit(0 if result.status in (CoreStatus.OK, CoreStatus.WARNING) else 1)
|
|
944
945
|
|
|
945
946
|
|
|
946
|
-
def _axi_resolve_port_conflicts(project: str, yes: bool) ->
|
|
947
|
+
def _axi_resolve_port_conflicts(project: str, yes: bool) -> list[Message]:
|
|
947
948
|
"""Never-prompt host-side port pre-step for ``axi start`` (D6).
|
|
948
949
|
|
|
949
950
|
Only meaningful when the container is not already up (a running instance owns
|
|
950
951
|
its ports). A non-Frappe holder cannot be auto-resolved -> ``CONFLICT``; a
|
|
951
952
|
Frappe holder is a ``CONFLICT`` naming ``--yes`` unless ``--yes`` was passed,
|
|
952
|
-
in which case the conflicting Frappe projects are stopped (stdout stays TOON)
|
|
953
|
+
in which case the conflicting Frappe projects are stopped (stdout stays TOON)
|
|
954
|
+
and their stop warnings are returned for the start document.
|
|
953
955
|
"""
|
|
954
956
|
from .start import _frappe_running, detect_port_conflicts
|
|
955
957
|
|
|
956
958
|
if _frappe_running(project):
|
|
957
|
-
return
|
|
959
|
+
return []
|
|
958
960
|
conflicting, non_frappe = detect_port_conflicts(project)
|
|
959
961
|
if non_frappe:
|
|
960
962
|
emit_axi_error(
|
|
@@ -985,9 +987,10 @@ def _axi_resolve_port_conflicts(project: str, yes: bool) -> None:
|
|
|
985
987
|
# contract. A project that vanished or stopped in that window is fine here
|
|
986
988
|
# (its ports are free either way) - the recheck below is what decides.
|
|
987
989
|
|
|
990
|
+
stop_warnings: list[Message] = []
|
|
988
991
|
for proj in conflicting:
|
|
989
992
|
try:
|
|
990
|
-
core_stop.stop(proj)
|
|
993
|
+
stop_warnings.extend(core_stop.stop(proj).warnings)
|
|
991
994
|
except CwcliError as e:
|
|
992
995
|
if e.kind is not ErrorKind.NOT_FOUND:
|
|
993
996
|
raise
|
|
@@ -1008,6 +1011,8 @@ def _axi_resolve_port_conflicts(project: str, yes: bool) -> None:
|
|
|
1008
1011
|
)
|
|
1009
1012
|
)
|
|
1010
1013
|
raise typer.Exit(exit_for(ErrorKind.CONFLICT))
|
|
1014
|
+
return stop_warnings
|
|
1015
|
+
return []
|
|
1011
1016
|
|
|
1012
1017
|
|
|
1013
1018
|
# ---------------------------------------------------------------------------- status
|
|
@@ -1700,7 +1705,10 @@ def axi_apps_checkout(
|
|
|
1700
1705
|
# epilogue gated on a condition already in the returned report. A failed recache
|
|
1701
1706
|
# is a stderr warning, NOT a non-zero exit: the checkout itself landed, and
|
|
1702
1707
|
# failing here would make an agent retry a mutation that already succeeded.
|
|
1703
|
-
|
|
1708
|
+
warnings = list(result.warnings)
|
|
1709
|
+
if any(r.ok for r in report.results) and not cache.recache_project(
|
|
1710
|
+
project, bench_path=report.bench_path, warnings=warnings
|
|
1711
|
+
):
|
|
1704
1712
|
print(
|
|
1705
1713
|
f"Warning: checkout completed, but re-caching '{project}' failed; "
|
|
1706
1714
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -1708,7 +1716,7 @@ def axi_apps_checkout(
|
|
|
1708
1716
|
flush=True,
|
|
1709
1717
|
)
|
|
1710
1718
|
|
|
1711
|
-
emit_result(report, warnings=
|
|
1719
|
+
emit_result(report, warnings=warnings)
|
|
1712
1720
|
# The exit code reads report.ok, NOT result.status: a failed step is a
|
|
1713
1721
|
# WARNING-shaped envelope, and WARNING maps to exit 0 everywhere else, so a
|
|
1714
1722
|
# status-driven code would report success for a checkout git refused.
|
|
@@ -1834,7 +1842,10 @@ def axi_apps_install(
|
|
|
1834
1842
|
# the cache whenever any step landed, matching the human verb. A failed recache
|
|
1835
1843
|
# is a stderr warning, NOT a non-zero exit: the install itself landed, and
|
|
1836
1844
|
# failing here would make an agent retry a mutation that already succeeded.
|
|
1837
|
-
|
|
1845
|
+
warnings = list(result.warnings)
|
|
1846
|
+
if any(r.ok for r in report.results) and not cache.recache_project(
|
|
1847
|
+
project, bench_path=report.bench_path, warnings=warnings
|
|
1848
|
+
):
|
|
1838
1849
|
print(
|
|
1839
1850
|
f"Warning: install completed, but re-caching '{project}' failed; "
|
|
1840
1851
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -1842,7 +1853,7 @@ def axi_apps_install(
|
|
|
1842
1853
|
flush=True,
|
|
1843
1854
|
)
|
|
1844
1855
|
|
|
1845
|
-
emit_result(report, warnings=
|
|
1856
|
+
emit_result(report, warnings=warnings)
|
|
1846
1857
|
# The exit code reads report.ok, NOT result.status: a failed step is a
|
|
1847
1858
|
# WARNING-shaped envelope, and WARNING maps to exit 0 everywhere else, so a
|
|
1848
1859
|
# status-driven code would report success for an install that failed.
|
|
@@ -2091,6 +2102,68 @@ def axi_build(
|
|
|
2091
2102
|
raise typer.Exit(0 if report.ok else 1)
|
|
2092
2103
|
|
|
2093
2104
|
|
|
2105
|
+
@app.command("setup-wizard")
|
|
2106
|
+
def axi_setup_wizard(
|
|
2107
|
+
project: str = typer.Argument(..., help="The Docker Compose project name."),
|
|
2108
|
+
site: str = typer.Argument(..., help="The site whose setup wizard to complete."),
|
|
2109
|
+
country: str = typer.Option(
|
|
2110
|
+
None, "--country", help="Setup wizard country (default: United States)."
|
|
2111
|
+
),
|
|
2112
|
+
currency: str = typer.Option(None, "--currency", help="Setup wizard currency (default: USD)."),
|
|
2113
|
+
timezone: str = typer.Option(None, "--timezone", help="Setup wizard timezone (default: UTC)."),
|
|
2114
|
+
bench: str = typer.Option(None, "--bench", help="Which bench: numeric index or label."),
|
|
2115
|
+
) -> None:
|
|
2116
|
+
"""Headlessly complete an EXISTING site's Frappe setup wizard; emit the report as TOON.
|
|
2117
|
+
|
|
2118
|
+
The standalone twin of `axi init --complete-setup`, sharing its exact
|
|
2119
|
+
implementation (`core.bench_ops.complete_setup_wizard`) - for a site that was
|
|
2120
|
+
created before this existed, or by anything other than cwcli (a bare `bench
|
|
2121
|
+
new-site`), and is stuck in the setup-wizard state: `desktop:home_page` stays
|
|
2122
|
+
`setup-wizard` and a non-System-Manager desk renders with no navbar.
|
|
2123
|
+
|
|
2124
|
+
Runs the SAME whitelisted Frappe RPC a human finishing the wizard in a
|
|
2125
|
+
browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`
|
|
2126
|
+
- via `bench execute ... --kwargs`, with the args dict the wizard UI would
|
|
2127
|
+
have submitted (System Settings' country/currency/timezone/language).
|
|
2128
|
+
|
|
2129
|
+
IDEMPOTENT: Frappe's own `setup_complete` checks `frappe.is_setup_complete()`
|
|
2130
|
+
first and no-ops (under an advisory lock) when the site is already set up, so
|
|
2131
|
+
re-running this against an already-completed site is always safe. The report's
|
|
2132
|
+
`already_complete` field says whether this call found it already done.
|
|
2133
|
+
|
|
2134
|
+
No user is created: `email`/`full_name`/`password` are deliberately left out
|
|
2135
|
+
of the args dict, so the Administrator account cwcli's own `init` already
|
|
2136
|
+
provisioned is left untouched.
|
|
2137
|
+
|
|
2138
|
+
NO --yes and no auto-start: a stopped project is a usage error naming
|
|
2139
|
+
`cwcli start`.
|
|
2140
|
+
"""
|
|
2141
|
+
try:
|
|
2142
|
+
result = core_bench_ops.complete_setup_wizard(
|
|
2143
|
+
project,
|
|
2144
|
+
site=site,
|
|
2145
|
+
bench=bench,
|
|
2146
|
+
country=country,
|
|
2147
|
+
currency=currency,
|
|
2148
|
+
timezone=timezone,
|
|
2149
|
+
auto_start=False,
|
|
2150
|
+
on_event=_bench_op_narrate,
|
|
2151
|
+
)
|
|
2152
|
+
except CwcliError as error:
|
|
2153
|
+
emit_axi_error(error)
|
|
2154
|
+
raise typer.Exit(exit_for(error.kind)) from None
|
|
2155
|
+
|
|
2156
|
+
if result.status is CoreStatus.NEEDS_CHOICE:
|
|
2157
|
+
assert result.choice is not None # NEEDS_CHOICE always carries a Choice
|
|
2158
|
+
emit_axi_choice_as_usage_error(result.choice)
|
|
2159
|
+
raise typer.Exit(2)
|
|
2160
|
+
|
|
2161
|
+
assert result.data is not None # OK/WARNING always carries a SetupWizardReport
|
|
2162
|
+
report = result.data
|
|
2163
|
+
emit_result(report, warnings=result.warnings)
|
|
2164
|
+
raise typer.Exit(0 if report.ok else 1)
|
|
2165
|
+
|
|
2166
|
+
|
|
2094
2167
|
# ---------------------------------------------------------------------------- config
|
|
2095
2168
|
|
|
2096
2169
|
|
|
@@ -2272,6 +2345,25 @@ def axi_init(
|
|
|
2272
2345
|
"so that case already falls back to a safe default automatically (reported in `warnings`); "
|
|
2273
2346
|
"pass --uid to choose a specific uid instead. Must be a positive, non-root integer.",
|
|
2274
2347
|
),
|
|
2348
|
+
complete_setup: bool = typer.Option(
|
|
2349
|
+
False,
|
|
2350
|
+
"--complete-setup",
|
|
2351
|
+
help="After the site is created, headlessly complete its Frappe setup wizard (same "
|
|
2352
|
+
"effect as finishing it in a browser) so 'desktop:home_page' leaves 'setup-wizard' and "
|
|
2353
|
+
"a non-System-Manager desk renders with its navbar. Without this flag a fresh site is "
|
|
2354
|
+
"left exactly as before: in the unfinished setup-wizard state.",
|
|
2355
|
+
),
|
|
2356
|
+
setup_country: str = typer.Option(
|
|
2357
|
+
None,
|
|
2358
|
+
"--country",
|
|
2359
|
+
help="Setup wizard country (default: United States). Requires --complete-setup.",
|
|
2360
|
+
),
|
|
2361
|
+
setup_currency: str = typer.Option(
|
|
2362
|
+
None, "--currency", help="Setup wizard currency (default: USD). Requires --complete-setup."
|
|
2363
|
+
),
|
|
2364
|
+
setup_timezone: str = typer.Option(
|
|
2365
|
+
None, "--timezone", help="Setup wizard timezone (default: UTC). Requires --complete-setup."
|
|
2366
|
+
),
|
|
2275
2367
|
) -> None:
|
|
2276
2368
|
"""Provision a new instance, bench, and site; emit the report as TOON (never prompts).
|
|
2277
2369
|
|
|
@@ -2304,7 +2396,23 @@ def axi_init(
|
|
|
2304
2396
|
--port (exit 1); containers that do not come up point at `cwcli status` /
|
|
2305
2397
|
`cwcli logs` (exit 1). There is no --auto-start (compose `cwcli axi start`
|
|
2306
2398
|
then re-run) and no --verbose (stdout is always TOON).
|
|
2399
|
+
|
|
2400
|
+
--complete-setup runs the site's Frappe setup wizard headlessly right after
|
|
2401
|
+
creation (--country/--currency/--timezone tune it; omitted ones get sensible
|
|
2402
|
+
defaults), and its outcome rides as a `setup_wizard` block inside this SAME
|
|
2403
|
+
document - see `cwcli axi setup-wizard --help` for the standalone verb that
|
|
2404
|
+
completes an EXISTING site's wizard, which shares this exact implementation.
|
|
2307
2405
|
"""
|
|
2406
|
+
if (setup_country or setup_currency or setup_timezone) and not complete_setup:
|
|
2407
|
+
emit_axi_error(
|
|
2408
|
+
CwcliError(
|
|
2409
|
+
ErrorKind.USAGE,
|
|
2410
|
+
"init.setup_flags_without_complete_setup",
|
|
2411
|
+
"--country/--currency/--timezone require --complete-setup.",
|
|
2412
|
+
)
|
|
2413
|
+
)
|
|
2414
|
+
raise typer.Exit(exit_for(ErrorKind.USAGE))
|
|
2415
|
+
|
|
2308
2416
|
resolved_admin = admin_password or os.environ.get("CWCLI_ADMIN_PASSWORD")
|
|
2309
2417
|
if not resolved_admin:
|
|
2310
2418
|
emit_axi_error(
|
|
@@ -2319,11 +2427,17 @@ def axi_init(
|
|
|
2319
2427
|
raise typer.Exit(exit_for(ErrorKind.USAGE))
|
|
2320
2428
|
db_root = db_root_password or os.environ.get("CWCLI_DB_ROOT_PASSWORD") or "123"
|
|
2321
2429
|
|
|
2322
|
-
# Fail-fast on a bad site name or --uid BEFORE stage 1 (the human
|
|
2323
|
-
# ordering): the core re-validates in init_bench, but that runs only
|
|
2324
|
-
# compose has brought real containers up, which a usage error must never
|
|
2325
|
-
# behind.
|
|
2430
|
+
# Fail-fast on a bad project/site name or --uid BEFORE stage 1 (the human
|
|
2431
|
+
# init's ordering): the core re-validates in init_bench, but that runs only
|
|
2432
|
+
# after compose has brought real containers up, which a usage error must never
|
|
2433
|
+
# leave behind. Normalizing the project name here (Docker Compose lowercases it,
|
|
2434
|
+
# and validate_project_slug does the same) is load-bearing, not cosmetic: the
|
|
2435
|
+
# post-creation steps below (dev-services start, recache, setup wizard) look the
|
|
2436
|
+
# container up by this name, so a mixed-case argument would resolve nothing and
|
|
2437
|
+
# they would fail "project not found" - exactly what the human init avoids by
|
|
2438
|
+
# threading the normalized name everywhere.
|
|
2326
2439
|
try:
|
|
2440
|
+
project = core_init.validate_project_slug(project)
|
|
2327
2441
|
site = core_init.validate_new_site_name(site)
|
|
2328
2442
|
core_init.check_uid_override(uid)
|
|
2329
2443
|
except CwcliError as error:
|
|
@@ -2415,7 +2529,8 @@ def axi_init(
|
|
|
2415
2529
|
assert bench_result.data is not None # OK/WARNING always carries an InitReport
|
|
2416
2530
|
report = bench_result.data
|
|
2417
2531
|
|
|
2418
|
-
|
|
2532
|
+
recache_warnings: list = []
|
|
2533
|
+
if not cache.recache_project(project, bench_path=report.bench_path, warnings=recache_warnings):
|
|
2419
2534
|
print(
|
|
2420
2535
|
"Warning: bench created, but caching its bench path failed; "
|
|
2421
2536
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2439,8 +2554,51 @@ def axi_init(
|
|
|
2439
2554
|
if warning.code in ("start.web_not_ready", "start.no_host_port"):
|
|
2440
2555
|
print(f"Warning: {warning.text}", file=sys.stderr, flush=True)
|
|
2441
2556
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2557
|
+
setup_wizard_payload: dict | None = None
|
|
2558
|
+
setup_wizard_ok = True
|
|
2559
|
+
if complete_setup:
|
|
2560
|
+
print("Completing the setup wizard...", file=sys.stderr, flush=True)
|
|
2561
|
+
try:
|
|
2562
|
+
setup_result = core_bench_ops.complete_setup_wizard(
|
|
2563
|
+
project,
|
|
2564
|
+
site=report.site_name,
|
|
2565
|
+
bench_path=report.bench_path,
|
|
2566
|
+
country=setup_country,
|
|
2567
|
+
currency=setup_currency,
|
|
2568
|
+
timezone=setup_timezone,
|
|
2569
|
+
auto_start=False,
|
|
2570
|
+
on_event=_bench_op_narrate,
|
|
2571
|
+
)
|
|
2572
|
+
except CwcliError as error:
|
|
2573
|
+
setup_wizard_ok = False
|
|
2574
|
+
setup_wizard_payload = {"ok": False, "error": error.message}
|
|
2575
|
+
else:
|
|
2576
|
+
if setup_result.status is CoreStatus.NEEDS_CHOICE:
|
|
2577
|
+
# The container stopped in the window between bench creation and
|
|
2578
|
+
# here; the bench+site above are already real, so this degrades to
|
|
2579
|
+
# a reported failure rather than aborting before the InitReport
|
|
2580
|
+
# an agent needs is ever printed.
|
|
2581
|
+
setup_wizard_ok = False
|
|
2582
|
+
setup_wizard_payload = {
|
|
2583
|
+
"ok": False,
|
|
2584
|
+
"error": f"the frappe container for '{project}' is not running",
|
|
2585
|
+
}
|
|
2586
|
+
else:
|
|
2587
|
+
assert setup_result.data is not None # OK/WARNING always carries a report
|
|
2588
|
+
setup_wizard_payload = asdict(setup_result.data)
|
|
2589
|
+
setup_wizard_ok = setup_result.data.ok
|
|
2590
|
+
|
|
2591
|
+
# ONE document: the setup_wizard outcome, when requested, is a block inside
|
|
2592
|
+
# the SAME encode() call rather than a second emitted document.
|
|
2593
|
+
payload = asdict(bench_result.data)
|
|
2594
|
+
if setup_wizard_payload is not None:
|
|
2595
|
+
payload["setup_wizard"] = setup_wizard_payload
|
|
2596
|
+
typer.echo(toon.encode(payload, warnings=[*bench_result.warnings, *recache_warnings]))
|
|
2597
|
+
|
|
2598
|
+
# Never exit 0 for a failed explicit request: --complete-setup was asked for,
|
|
2599
|
+
# so its own failure must be visible in the exit code, not just the payload.
|
|
2600
|
+
overall_ok = bench_result.status in (CoreStatus.OK, CoreStatus.WARNING) and setup_wizard_ok
|
|
2601
|
+
raise typer.Exit(0 if overall_ok else 1)
|
|
2444
2602
|
|
|
2445
2603
|
|
|
2446
2604
|
# -------------------------------------------------------------------------------- rm
|
|
@@ -2711,7 +2869,8 @@ def axi_rm_site(
|
|
|
2711
2869
|
# A failed recache is a stderr warning, NOT a non-zero exit: the drop
|
|
2712
2870
|
# already happened, and failing here would make an agent retry a mutation
|
|
2713
2871
|
# that already succeeded.
|
|
2714
|
-
|
|
2872
|
+
warnings = list(result.warnings)
|
|
2873
|
+
if not cache.recache_project(project, bench_path=outcome.bench_path, warnings=warnings):
|
|
2715
2874
|
print(
|
|
2716
2875
|
f"Warning: site dropped, but re-caching '{project}' failed; "
|
|
2717
2876
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2719,7 +2878,7 @@ def axi_rm_site(
|
|
|
2719
2878
|
flush=True,
|
|
2720
2879
|
)
|
|
2721
2880
|
|
|
2722
|
-
emit_result(outcome, warnings=
|
|
2881
|
+
emit_result(outcome, warnings=warnings)
|
|
2723
2882
|
# ok, NOT result.status: a drop with an unsafe archive is a WARNING-shaped
|
|
2724
2883
|
# envelope, which maps to exit 0 everywhere else and would hide leftover
|
|
2725
2884
|
# in-container credentials behind a green exit code.
|
|
@@ -2827,7 +2986,8 @@ def axi_rm_bench(
|
|
|
2827
2986
|
# A failed recache is a stderr warning, NOT a non-zero exit: the removal
|
|
2828
2987
|
# already happened, and failing here would make an agent retry a bench that
|
|
2829
2988
|
# is already gone.
|
|
2830
|
-
|
|
2989
|
+
warnings = list(result.warnings)
|
|
2990
|
+
if not cache.recache_project(project, bench_path=outcome.bench_path, warnings=warnings):
|
|
2831
2991
|
print(
|
|
2832
2992
|
f"Warning: bench removed, but re-caching '{project}' failed; "
|
|
2833
2993
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2835,7 +2995,7 @@ def axi_rm_bench(
|
|
|
2835
2995
|
flush=True,
|
|
2836
2996
|
)
|
|
2837
2997
|
|
|
2838
|
-
emit_result(outcome, warnings=
|
|
2998
|
+
emit_result(outcome, warnings=warnings)
|
|
2839
2999
|
# ok, NOT result.status: a partial removal (a site's backup trapped in the
|
|
2840
3000
|
# container) is a WARNING-shaped envelope, which maps to exit 0 everywhere
|
|
2841
3001
|
# else and would report a green exit for an incomplete removal.
|
|
@@ -1982,7 +1982,7 @@ dialog input {
|
|
|
1982
1982
|
? (pendingAction === action ? "working" : "another action is running")
|
|
1983
1983
|
: null;
|
|
1984
1984
|
const processDisabledReason = disabledReason("restart_process") || (!process ? "select process" : null);
|
|
1985
|
-
const message = lastAction ? banner(lastAction.ok ? "success" : "danger", null, lastAction.text, lastAction.hint) : "";
|
|
1985
|
+
const message = lastAction ? banner(lastAction.tone || (lastAction.ok ? "success" : "danger"), null, lastAction.text, lastAction.hint) : "";
|
|
1986
1986
|
el("rail-body").innerHTML = `<div>
|
|
1987
1987
|
<div class="target"><div class="target-name">${esc(targetName)}</div><div class="target-meta">${esc(targetMeta)}</div></div>
|
|
1988
1988
|
<div class="action-group">
|
|
@@ -2072,10 +2072,14 @@ dialog input {
|
|
|
2072
2072
|
}
|
|
2073
2073
|
const choice = body.choice && body.choice.options ? " Options: " + body.choice.options.map(o => o.label || o.value).join(", ") : "";
|
|
2074
2074
|
const failure = new Error((error.message || "action failed") + choice);
|
|
2075
|
-
|
|
2075
|
+
const warning = (body.warnings || []).map(w => w.text || w.code || "").filter(Boolean).join(" ");
|
|
2076
|
+
failure.hint = [error.hint, warning].filter((value, index, values) => value && value !== failure.message && values.indexOf(value) === index).join(" ");
|
|
2076
2077
|
throw failure;
|
|
2077
2078
|
}
|
|
2078
|
-
|
|
2079
|
+
const warning = (body.warnings || []).map(w => w.text || w.code || "").filter(Boolean).join(" ");
|
|
2080
|
+
lastAction = warning
|
|
2081
|
+
? {ok: true, tone: "warn", text: `${action.replaceAll("_", " ")} completed with warnings for ${inst.project}`, hint: warning}
|
|
2082
|
+
: {ok: true, text: `${action.replaceAll("_", " ")} accepted for ${inst.project}`};
|
|
2079
2083
|
addEvent("fast", `ACTION ${action.replaceAll("_", " ")} ${inst.project}`);
|
|
2080
2084
|
if (action === "set_label" || action === "checkout_app") {
|
|
2081
2085
|
// The bench label / app registry just changed; the cached detail no
|
|
@@ -2090,6 +2094,7 @@ dialog input {
|
|
|
2090
2094
|
pendingAction = null;
|
|
2091
2095
|
renderRail();
|
|
2092
2096
|
el("action-status").textContent = lastAction ? lastAction.text : "";
|
|
2097
|
+
if (lastAction && lastAction.hint) el("action-status").textContent += `. ${lastAction.hint}`;
|
|
2093
2098
|
}
|
|
2094
2099
|
}
|
|
2095
2100
|
|
|
@@ -328,11 +328,12 @@ def _resolve_frappe_branch(frappe_branch: str | None, version: str | None) -> st
|
|
|
328
328
|
return core_init.DEFAULT_FRAPPE_BRANCH
|
|
329
329
|
|
|
330
330
|
|
|
331
|
-
def _refresh_cache(project: str, verbose: bool) -> None:
|
|
332
|
-
"""
|
|
331
|
+
def _refresh_cache(project: str, verbose: bool, bench_path: str) -> None:
|
|
332
|
+
"""Cache the bench right after it is created (or reused).
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
Only this bench is re-read and spliced into the project's cache; a failed
|
|
335
|
+
recache clears the project's cache instead (a stale entry would be worse
|
|
336
|
+
than none). Every bench-resolving verb
|
|
336
337
|
(``status``, ``run``, ``apps``, ...) falls back to a hardcoded default
|
|
337
338
|
bench path when the cache is empty, which only happens to match a bench
|
|
338
339
|
built under the DEFAULT ``--bench-parent`` - a custom parent's very first
|
|
@@ -340,11 +341,16 @@ def _refresh_cache(project: str, verbose: bool) -> None:
|
|
|
340
341
|
``apps.py``'s post-mutation ``_refresh_cache``: degrades to a warning,
|
|
341
342
|
never fails init (the bench itself already succeeded).
|
|
342
343
|
"""
|
|
343
|
-
|
|
344
|
+
warnings: list = []
|
|
345
|
+
if not cache.recache_project(
|
|
346
|
+
project, verbose=verbose, bench_path=bench_path, warnings=warnings
|
|
347
|
+
):
|
|
344
348
|
stderr_console.print(
|
|
345
349
|
"[yellow]Warning:[/yellow] bench created, but caching its bench path failed; "
|
|
346
350
|
"run 'cwcli inspect --update' to refresh."
|
|
347
351
|
)
|
|
352
|
+
for warning in warnings:
|
|
353
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
348
354
|
|
|
349
355
|
|
|
350
356
|
def _bench_web_url(project: str, bench_path: str, site: str) -> str | None:
|
|
@@ -794,7 +800,7 @@ def init(
|
|
|
794
800
|
)
|
|
795
801
|
console.print(f"[dim]Bench path: {report.bench_path}[/dim]")
|
|
796
802
|
|
|
797
|
-
_refresh_cache(project, verbose)
|
|
803
|
+
_refresh_cache(project, verbose, report.bench_path)
|
|
798
804
|
|
|
799
805
|
# Auto-start the bench dev services (default) so init leaves a running dev
|
|
800
806
|
# environment. Containers are already up (stage 1 brought them up), so no
|
{caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm.py
RENAMED
|
@@ -318,12 +318,15 @@ def _stop_after_transient_start(project_name: str) -> None:
|
|
|
318
318
|
with stderr_console.status(
|
|
319
319
|
f"[bold yellow]Stopping '{project_name}'...[/bold yellow]", spinner="dots"
|
|
320
320
|
):
|
|
321
|
-
core_stop.stop(project_name)
|
|
321
|
+
result = core_stop.stop(project_name)
|
|
322
322
|
except Exception as e:
|
|
323
323
|
stderr_console.print(
|
|
324
324
|
f"[yellow]Warning:[/yellow] Could not stop '{project_name}' after aborting; it may "
|
|
325
325
|
f"still be running: {e}"
|
|
326
326
|
)
|
|
327
|
+
return
|
|
328
|
+
for warning in result.warnings:
|
|
329
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
327
330
|
|
|
328
331
|
|
|
329
332
|
def _recover_trailing_flags(
|
|
@@ -555,10 +558,15 @@ def rm(
|
|
|
555
558
|
if actual_verbose:
|
|
556
559
|
stderr_console.print(f"[dim]VERBOSE: Re-caching '{project}'...[/dim]")
|
|
557
560
|
|
|
558
|
-
|
|
561
|
+
recache_warnings: list = []
|
|
562
|
+
if not cache.recache_project(
|
|
563
|
+
project, verbose=actual_verbose, warnings=recache_warnings
|
|
564
|
+
):
|
|
559
565
|
stderr_console.print(
|
|
560
566
|
f"[yellow]Warning:[/yellow] Could not recache '{project}'. Backup may be incomplete."
|
|
561
567
|
)
|
|
568
|
+
for warning in recache_warnings:
|
|
569
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
562
570
|
|
|
563
571
|
# Confirmation prompt (unless --yes flag is used)
|
|
564
572
|
if not yes:
|
|
@@ -154,13 +154,16 @@ def rm_bench(
|
|
|
154
154
|
# Removing a bench changes the instance's bench set, so refresh the cache.
|
|
155
155
|
# A failed recache is a warning, not a non-zero exit: the removal already
|
|
156
156
|
# happened, and failing here would make a retry act on a bench that is gone.
|
|
157
|
-
|
|
157
|
+
recache_warnings: list = []
|
|
158
|
+
if not cache.recache_project(
|
|
159
|
+
project_name, verbose=verbose, bench_path=outcome.bench_path, warnings=recache_warnings
|
|
160
|
+
):
|
|
158
161
|
stderr_console.print(
|
|
159
162
|
f"[yellow]Warning:[/yellow] bench removed, but re-caching '{project_name}' failed; "
|
|
160
163
|
"run 'cwcli inspect --update' to refresh."
|
|
161
164
|
)
|
|
162
165
|
|
|
163
|
-
for warning in result.warnings:
|
|
166
|
+
for warning in [*result.warnings, *recache_warnings]:
|
|
164
167
|
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
165
168
|
|
|
166
169
|
if outcome.dir_removed:
|
|
@@ -166,13 +166,16 @@ def rm_site(
|
|
|
166
166
|
outcome = result.data
|
|
167
167
|
assert outcome is not None # OK/WARNING always carries a DropSiteOutcome
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
recache_warnings: list = []
|
|
170
|
+
if not cache.recache_project(
|
|
171
|
+
project_name, verbose=verbose, bench_path=outcome.bench_path, warnings=recache_warnings
|
|
172
|
+
):
|
|
170
173
|
stderr_console.print(
|
|
171
174
|
f"[yellow]Warning:[/yellow] site dropped, but re-caching '{project_name}' failed; "
|
|
172
175
|
"run 'cwcli inspect --update' to refresh."
|
|
173
176
|
)
|
|
174
177
|
|
|
175
|
-
for warning in result.warnings:
|
|
178
|
+
for warning in [*result.warnings, *recache_warnings]:
|
|
176
179
|
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
177
180
|
|
|
178
181
|
console.print(f"[bold green]✓[/bold green] Site '{outcome.site}' dropped.")
|