wasm-cli 1.0.1__tar.gz → 1.0.2__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.
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/MANIFEST.in +1 -0
- {wasm_cli-1.0.1/src/wasm_cli.egg-info → wasm_cli-1.0.2}/PKG-INFO +1 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/man/wasm.1 +1 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/pyproject.toml +3 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/setup.py +1 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/__init__.py +1 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/interactive.py +72 -70
- wasm_cli-1.0.2/src/wasm/cli/prompts.py +182 -0
- wasm_cli-1.0.2/src/wasm/completions/README.md +16 -0
- wasm_cli-1.0.2/src/wasm/completions/_wasm +40 -0
- wasm_cli-1.0.2/src/wasm/completions/wasm.bash +28 -0
- wasm_cli-1.0.2/src/wasm/completions/wasm.fish +17 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/env_manager.py +10 -27
- {wasm_cli-1.0.1 → wasm_cli-1.0.2/src/wasm_cli.egg-info}/PKG-INFO +1 -1
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/SOURCES.txt +5 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/LICENSE +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/README.md +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/MONITOR.md +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/OBS_SETUP.md +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/assets/logo.png +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/assets/logo_bg.png +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/superpowers/specs/2026-08-12-wasm-panel-design-direction.md +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/superpowers/specs/2026-08-12-wasm-v1-refactor-design.md +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/setup.cfg +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/__main__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/app.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/backup.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/cert.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/config.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/db.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/env.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/health.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/monitor.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/service.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/setup.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/site.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/store.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/version.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/web.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/webapp.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/config.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/dependencies.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/exceptions.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/fs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/logger.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/runner.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/store.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/update_checker.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/utils.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/auto.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/base.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/docker_compose.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/health.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/nginx_config.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/package_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/path_resolver.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/preflight.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/prisma.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/registration.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/summary.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/turbo.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/workspace.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/interface.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/monorepo.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/nextjs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/nodejs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/pipeline.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/python.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/registry.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/static.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/vite.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/main.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/apache_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/backup_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/backup_scheduler.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/base_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/cert_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/base.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/mongodb.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/mysql.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/postgres.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/redis.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/registry.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/nginx_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/service_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/source_manager.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/webserver.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/email_notifier.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/metrics.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/models.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/observation_store.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/process_monitor.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/signals.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/apache/static.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/advanced.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/static.conf.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/_escape.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/app.service.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/backup-service.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/domain.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/environment.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/names.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/port.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/source.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/ssh.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/apps.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/auth.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/backups.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/certs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/config.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/databases.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/deps.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/jobs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/monitor.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/router.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/services.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/sites.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/system.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/auth.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/jobs.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/server.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/app.css +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/logo.png +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/logo.webp +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/panel.js +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/alpine.min.js +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-500-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-600-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/htmx.min.js +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/uplot.min.css +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/uplot.min.js +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/xterm.css +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/xterm.js +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/_macros.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/base.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/fragments/machine.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/login.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/activity.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/app.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/backups.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/certificates.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/dashboard.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/missing.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/resources.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/settings.html +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/context.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/machine.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/rendering.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/resources.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/router.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/websockets/__init__.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/websockets/router.py +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/entry_points.txt +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/requires.txt +0 -0
- {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/top_level.txt +0 -0
|
@@ -10,6 +10,7 @@ include pyproject.toml
|
|
|
10
10
|
# Include source code (handled by setuptools, but explicit is better)
|
|
11
11
|
recursive-include src *.py
|
|
12
12
|
recursive-include src/wasm/templates *.j2
|
|
13
|
+
recursive-include src/wasm/completions *
|
|
13
14
|
recursive-include src/wasm/web/static *
|
|
14
15
|
recursive-include src/wasm/web/templates *.html
|
|
15
16
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wasm-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Web App System Management - Deploy and manage web applications on Linux servers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "WASM-NCSAL" }
|
|
@@ -108,6 +108,8 @@ include = ["wasm*"]
|
|
|
108
108
|
wasm = [
|
|
109
109
|
# nginx, apache and systemd unit templates.
|
|
110
110
|
"templates/**/*.j2",
|
|
111
|
+
# Committed, not generated: see src/wasm/completions/README.md.
|
|
112
|
+
"completions/*",
|
|
111
113
|
# The panel: its pages, its stylesheet and every third-party asset it
|
|
112
114
|
# serves. All of it has to ship, because the panel renders server-side and
|
|
113
115
|
# fetches nothing from the network.
|
|
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="wasm-cli",
|
|
12
|
-
version="1.0.
|
|
12
|
+
version="1.0.2",
|
|
13
13
|
description="Web App System Management - Deploy and manage web applications on Linux servers",
|
|
14
14
|
author="Yago López Prado",
|
|
15
15
|
author_email="yago.lopez.adeje@gmail.com",
|
|
@@ -15,7 +15,7 @@ from importlib.metadata import version as _installed_version
|
|
|
15
15
|
#: as an OBS build directory. The single source of truth is the ``version``
|
|
16
16
|
#: field of pyproject.toml; ``scripts/release.py`` keeps this literal and the
|
|
17
17
|
#: distribution packaging files in step with it.
|
|
18
|
-
_FALLBACK_VERSION = "1.0.
|
|
18
|
+
_FALLBACK_VERSION = "1.0.2"
|
|
19
19
|
|
|
20
20
|
try:
|
|
21
21
|
__version__ = _installed_version("wasm-cli")
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Interactive mode
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
import inquirer
|
|
7
|
-
from inquirer.themes import GreenPassion
|
|
2
|
+
Interactive mode.
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
Prompts come from :mod:`wasm.cli.prompts`, which is built on questionary. The
|
|
5
|
+
previous implementation used inquirer, which is not packaged in Debian or
|
|
6
|
+
Ubuntu, so interactive mode never worked on the distributions most WASM users
|
|
7
|
+
run.
|
|
8
|
+
"""
|
|
12
9
|
|
|
10
|
+
from wasm.cli import prompts
|
|
13
11
|
from wasm.core.exceptions import WASMError
|
|
14
12
|
from wasm.core.logger import Logger
|
|
15
13
|
from wasm.validators.domain import check_domain
|
|
@@ -19,7 +17,7 @@ from wasm.validators.source import is_valid_source
|
|
|
19
17
|
|
|
20
18
|
class InteractiveMode:
|
|
21
19
|
"""
|
|
22
|
-
Interactive mode handler
|
|
20
|
+
Interactive mode handler.
|
|
23
21
|
"""
|
|
24
22
|
|
|
25
23
|
def __init__(self, verbose: bool = False):
|
|
@@ -32,10 +30,14 @@ class InteractiveMode:
|
|
|
32
30
|
self.verbose = verbose
|
|
33
31
|
self.logger = Logger(verbose=verbose)
|
|
34
32
|
|
|
35
|
-
if not
|
|
33
|
+
if not prompts.AVAILABLE:
|
|
36
34
|
raise WASMError(
|
|
37
|
-
"Interactive mode
|
|
38
|
-
|
|
35
|
+
"Interactive mode needs questionary, which is missing",
|
|
36
|
+
details=(
|
|
37
|
+
"It is a hard dependency, so this means a broken install.\n"
|
|
38
|
+
" pip install --force-reinstall wasm-cli\n"
|
|
39
|
+
" or, on a distribution package: apt install python3-questionary"
|
|
40
|
+
),
|
|
39
41
|
)
|
|
40
42
|
|
|
41
43
|
def run(self) -> int:
|
|
@@ -73,7 +75,7 @@ class InteractiveMode:
|
|
|
73
75
|
def _prompt_action_type(self) -> str:
|
|
74
76
|
"""Prompt for action type."""
|
|
75
77
|
questions = [
|
|
76
|
-
|
|
78
|
+
prompts.List(
|
|
77
79
|
"action_type",
|
|
78
80
|
message="What would you like to do?",
|
|
79
81
|
choices=[
|
|
@@ -85,13 +87,13 @@ class InteractiveMode:
|
|
|
85
87
|
),
|
|
86
88
|
]
|
|
87
89
|
|
|
88
|
-
answers =
|
|
90
|
+
answers = prompts.prompt(questions)
|
|
89
91
|
return answers["action_type"] if answers else None
|
|
90
92
|
|
|
91
93
|
def _webapp_flow(self) -> int:
|
|
92
94
|
"""Handle webapp interactive flow."""
|
|
93
95
|
questions = [
|
|
94
|
-
|
|
96
|
+
prompts.List(
|
|
95
97
|
"action",
|
|
96
98
|
message="What action would you like to perform?",
|
|
97
99
|
choices=[
|
|
@@ -108,7 +110,7 @@ class InteractiveMode:
|
|
|
108
110
|
),
|
|
109
111
|
]
|
|
110
112
|
|
|
111
|
-
answers =
|
|
113
|
+
answers = prompts.prompt(questions)
|
|
112
114
|
if not answers:
|
|
113
115
|
return 0
|
|
114
116
|
|
|
@@ -155,7 +157,7 @@ class InteractiveMode:
|
|
|
155
157
|
def _webapp_create(self) -> int:
|
|
156
158
|
"""Handle webapp create flow."""
|
|
157
159
|
questions = [
|
|
158
|
-
|
|
160
|
+
prompts.List(
|
|
159
161
|
"type",
|
|
160
162
|
message="Select application type",
|
|
161
163
|
choices=[
|
|
@@ -167,23 +169,23 @@ class InteractiveMode:
|
|
|
167
169
|
("🔍 Auto-detect", "auto"),
|
|
168
170
|
],
|
|
169
171
|
),
|
|
170
|
-
|
|
172
|
+
prompts.Text(
|
|
171
173
|
"domain",
|
|
172
174
|
message="Enter target domain",
|
|
173
175
|
validate=lambda _, x: check_domain(x) or "Invalid domain name",
|
|
174
176
|
),
|
|
175
|
-
|
|
177
|
+
prompts.Text(
|
|
176
178
|
"source",
|
|
177
179
|
message="Enter source (Git URL or path)",
|
|
178
180
|
validate=lambda _, x: is_valid_source(x) or "Invalid source",
|
|
179
181
|
),
|
|
180
|
-
|
|
182
|
+
prompts.Text(
|
|
181
183
|
"port",
|
|
182
184
|
message="Application port (leave empty for auto)",
|
|
183
185
|
default="",
|
|
184
186
|
validate=lambda _, x: x == "" or check_port(x) or "Invalid port",
|
|
185
187
|
),
|
|
186
|
-
|
|
188
|
+
prompts.List(
|
|
187
189
|
"webserver",
|
|
188
190
|
message="Select web server",
|
|
189
191
|
choices=[
|
|
@@ -192,24 +194,24 @@ class InteractiveMode:
|
|
|
192
194
|
],
|
|
193
195
|
default="nginx",
|
|
194
196
|
),
|
|
195
|
-
|
|
197
|
+
prompts.Text(
|
|
196
198
|
"branch",
|
|
197
199
|
message="Git branch (leave empty for default)",
|
|
198
200
|
default="",
|
|
199
201
|
),
|
|
200
|
-
|
|
202
|
+
prompts.Confirm(
|
|
201
203
|
"ssl",
|
|
202
204
|
message="Configure SSL certificate?",
|
|
203
205
|
default=True,
|
|
204
206
|
),
|
|
205
|
-
|
|
207
|
+
prompts.Text(
|
|
206
208
|
"env_file",
|
|
207
209
|
message="Path to environment file (leave empty to skip)",
|
|
208
210
|
default="",
|
|
209
211
|
),
|
|
210
212
|
]
|
|
211
213
|
|
|
212
|
-
answers =
|
|
214
|
+
answers = prompts.prompt(questions)
|
|
213
215
|
if not answers:
|
|
214
216
|
return 0
|
|
215
217
|
|
|
@@ -219,13 +221,13 @@ class InteractiveMode:
|
|
|
219
221
|
include_www = False
|
|
220
222
|
if answers["ssl"] and should_include_www(answers["domain"]):
|
|
221
223
|
www_questions = [
|
|
222
|
-
|
|
224
|
+
prompts.Confirm(
|
|
223
225
|
"include_www",
|
|
224
226
|
message=f"Include www.{answers['domain']} in certificate and web server config?",
|
|
225
227
|
default=True,
|
|
226
228
|
),
|
|
227
229
|
]
|
|
228
|
-
www_answers =
|
|
230
|
+
www_answers = prompts.prompt(www_questions)
|
|
229
231
|
include_www = www_answers.get("include_www", True) if www_answers else False
|
|
230
232
|
|
|
231
233
|
# Build arguments
|
|
@@ -252,20 +254,20 @@ class InteractiveMode:
|
|
|
252
254
|
def _webapp_logs(self, domain: str) -> int:
|
|
253
255
|
"""Handle webapp logs flow with interactive prompts."""
|
|
254
256
|
questions = [
|
|
255
|
-
|
|
257
|
+
prompts.Text(
|
|
256
258
|
"lines",
|
|
257
259
|
message="Number of log lines to show",
|
|
258
260
|
default="50",
|
|
259
261
|
validate=lambda _, x: (x.isdigit() and int(x) > 0) or "Must be a positive number",
|
|
260
262
|
),
|
|
261
|
-
|
|
263
|
+
prompts.Confirm(
|
|
262
264
|
"follow",
|
|
263
265
|
message="Follow log output in real time?",
|
|
264
266
|
default=False,
|
|
265
267
|
),
|
|
266
268
|
]
|
|
267
269
|
|
|
268
|
-
answers =
|
|
270
|
+
answers = prompts.prompt(questions)
|
|
269
271
|
if not answers:
|
|
270
272
|
return 0
|
|
271
273
|
|
|
@@ -279,14 +281,14 @@ class InteractiveMode:
|
|
|
279
281
|
def _webapp_update(self, domain: str) -> int:
|
|
280
282
|
"""Handle webapp update flow with interactive prompts."""
|
|
281
283
|
questions = [
|
|
282
|
-
|
|
284
|
+
prompts.Text(
|
|
283
285
|
"branch",
|
|
284
286
|
message="Git branch to update from (leave empty for default)",
|
|
285
287
|
default="",
|
|
286
288
|
),
|
|
287
289
|
]
|
|
288
290
|
|
|
289
|
-
answers =
|
|
291
|
+
answers = prompts.prompt(questions)
|
|
290
292
|
if not answers:
|
|
291
293
|
return 0
|
|
292
294
|
|
|
@@ -299,7 +301,7 @@ class InteractiveMode:
|
|
|
299
301
|
def _site_flow(self) -> int:
|
|
300
302
|
"""Handle site interactive flow."""
|
|
301
303
|
questions = [
|
|
302
|
-
|
|
304
|
+
prompts.List(
|
|
303
305
|
"action",
|
|
304
306
|
message="What action would you like to perform?",
|
|
305
307
|
choices=[
|
|
@@ -313,7 +315,7 @@ class InteractiveMode:
|
|
|
313
315
|
),
|
|
314
316
|
]
|
|
315
317
|
|
|
316
|
-
answers =
|
|
318
|
+
answers = prompts.prompt(questions)
|
|
317
319
|
if not answers:
|
|
318
320
|
return 0
|
|
319
321
|
|
|
@@ -334,12 +336,12 @@ class InteractiveMode:
|
|
|
334
336
|
def _site_create(self) -> int:
|
|
335
337
|
"""Handle site create flow."""
|
|
336
338
|
questions = [
|
|
337
|
-
|
|
339
|
+
prompts.Text(
|
|
338
340
|
"domain",
|
|
339
341
|
message="Enter domain name",
|
|
340
342
|
validate=lambda _, x: check_domain(x) or "Invalid domain name",
|
|
341
343
|
),
|
|
342
|
-
|
|
344
|
+
prompts.List(
|
|
343
345
|
"webserver",
|
|
344
346
|
message="Select web server",
|
|
345
347
|
choices=[
|
|
@@ -347,7 +349,7 @@ class InteractiveMode:
|
|
|
347
349
|
("Apache", "apache"),
|
|
348
350
|
],
|
|
349
351
|
),
|
|
350
|
-
|
|
352
|
+
prompts.List(
|
|
351
353
|
"template",
|
|
352
354
|
message="Select configuration template",
|
|
353
355
|
choices=[
|
|
@@ -355,20 +357,20 @@ class InteractiveMode:
|
|
|
355
357
|
("Static Site", "static"),
|
|
356
358
|
],
|
|
357
359
|
),
|
|
358
|
-
|
|
360
|
+
prompts.Text(
|
|
359
361
|
"port",
|
|
360
362
|
message="Backend port (for proxy)",
|
|
361
363
|
default="3000",
|
|
362
364
|
validate=lambda _, x: check_port(x) or "Invalid port",
|
|
363
365
|
),
|
|
364
|
-
|
|
366
|
+
prompts.Confirm(
|
|
365
367
|
"ssl",
|
|
366
368
|
message="Configure SSL certificate?",
|
|
367
369
|
default=True,
|
|
368
370
|
),
|
|
369
371
|
]
|
|
370
372
|
|
|
371
|
-
answers =
|
|
373
|
+
answers = prompts.prompt(questions)
|
|
372
374
|
if not answers:
|
|
373
375
|
return 0
|
|
374
376
|
|
|
@@ -378,13 +380,13 @@ class InteractiveMode:
|
|
|
378
380
|
include_www = False
|
|
379
381
|
if answers["ssl"] and should_include_www(answers["domain"]):
|
|
380
382
|
www_questions = [
|
|
381
|
-
|
|
383
|
+
prompts.Confirm(
|
|
382
384
|
"include_www",
|
|
383
385
|
message=f"Include www.{answers['domain']} in certificate and web server config?",
|
|
384
386
|
default=True,
|
|
385
387
|
),
|
|
386
388
|
]
|
|
387
|
-
www_answers =
|
|
389
|
+
www_answers = prompts.prompt(www_questions)
|
|
388
390
|
include_www = www_answers.get("include_www", True) if www_answers else False
|
|
389
391
|
|
|
390
392
|
from argparse import Namespace
|
|
@@ -407,7 +409,7 @@ class InteractiveMode:
|
|
|
407
409
|
def _service_flow(self) -> int:
|
|
408
410
|
"""Handle service interactive flow."""
|
|
409
411
|
questions = [
|
|
410
|
-
|
|
412
|
+
prompts.List(
|
|
411
413
|
"action",
|
|
412
414
|
message="What action would you like to perform?",
|
|
413
415
|
choices=[
|
|
@@ -423,7 +425,7 @@ class InteractiveMode:
|
|
|
423
425
|
),
|
|
424
426
|
]
|
|
425
427
|
|
|
426
|
-
answers =
|
|
428
|
+
answers = prompts.prompt(questions)
|
|
427
429
|
if not answers:
|
|
428
430
|
return 0
|
|
429
431
|
|
|
@@ -447,34 +449,34 @@ class InteractiveMode:
|
|
|
447
449
|
def _service_create(self) -> int:
|
|
448
450
|
"""Handle service create flow."""
|
|
449
451
|
questions = [
|
|
450
|
-
|
|
452
|
+
prompts.Text(
|
|
451
453
|
"name",
|
|
452
454
|
message="Enter service name",
|
|
453
455
|
validate=lambda _, x: len(x) > 0 or "Name required",
|
|
454
456
|
),
|
|
455
|
-
|
|
457
|
+
prompts.Text(
|
|
456
458
|
"command",
|
|
457
459
|
message="Enter command to run",
|
|
458
460
|
validate=lambda _, x: len(x) > 0 or "Command required",
|
|
459
461
|
),
|
|
460
|
-
|
|
462
|
+
prompts.Text(
|
|
461
463
|
"directory",
|
|
462
464
|
message="Enter working directory",
|
|
463
465
|
validate=lambda _, x: len(x) > 0 or "Directory required",
|
|
464
466
|
),
|
|
465
|
-
|
|
467
|
+
prompts.Text(
|
|
466
468
|
"user",
|
|
467
469
|
message="User to run as",
|
|
468
470
|
default="www-data",
|
|
469
471
|
),
|
|
470
|
-
|
|
472
|
+
prompts.Text(
|
|
471
473
|
"description",
|
|
472
474
|
message="Service description",
|
|
473
475
|
default="",
|
|
474
476
|
),
|
|
475
477
|
]
|
|
476
478
|
|
|
477
|
-
answers =
|
|
479
|
+
answers = prompts.prompt(questions)
|
|
478
480
|
if not answers:
|
|
479
481
|
return 0
|
|
480
482
|
|
|
@@ -497,20 +499,20 @@ class InteractiveMode:
|
|
|
497
499
|
def _service_logs(self, name: str) -> int:
|
|
498
500
|
"""Handle service logs flow with interactive prompts."""
|
|
499
501
|
questions = [
|
|
500
|
-
|
|
502
|
+
prompts.Text(
|
|
501
503
|
"lines",
|
|
502
504
|
message="Number of log lines to show",
|
|
503
505
|
default="50",
|
|
504
506
|
validate=lambda _, x: (x.isdigit() and int(x) > 0) or "Must be a positive number",
|
|
505
507
|
),
|
|
506
|
-
|
|
508
|
+
prompts.Confirm(
|
|
507
509
|
"follow",
|
|
508
510
|
message="Follow log output in real time?",
|
|
509
511
|
default=False,
|
|
510
512
|
),
|
|
511
513
|
]
|
|
512
514
|
|
|
513
|
-
answers =
|
|
515
|
+
answers = prompts.prompt(questions)
|
|
514
516
|
if not answers:
|
|
515
517
|
return 0
|
|
516
518
|
|
|
@@ -525,7 +527,7 @@ class InteractiveMode:
|
|
|
525
527
|
def _cert_flow(self) -> int:
|
|
526
528
|
"""Handle cert interactive flow."""
|
|
527
529
|
questions = [
|
|
528
|
-
|
|
530
|
+
prompts.List(
|
|
529
531
|
"action",
|
|
530
532
|
message="What action would you like to perform?",
|
|
531
533
|
choices=[
|
|
@@ -539,7 +541,7 @@ class InteractiveMode:
|
|
|
539
541
|
),
|
|
540
542
|
]
|
|
541
543
|
|
|
542
|
-
answers =
|
|
544
|
+
answers = prompts.prompt(questions)
|
|
543
545
|
if not answers:
|
|
544
546
|
return 0
|
|
545
547
|
|
|
@@ -564,22 +566,22 @@ class InteractiveMode:
|
|
|
564
566
|
def _cert_create(self) -> int:
|
|
565
567
|
"""Handle cert create flow."""
|
|
566
568
|
questions = [
|
|
567
|
-
|
|
569
|
+
prompts.Text(
|
|
568
570
|
"domain",
|
|
569
571
|
message="Enter primary domain",
|
|
570
572
|
validate=lambda _, x: check_domain(x) or "Invalid domain",
|
|
571
573
|
),
|
|
572
|
-
|
|
574
|
+
prompts.Text(
|
|
573
575
|
"additional",
|
|
574
576
|
message="Additional domains (comma separated, or leave empty)",
|
|
575
577
|
default="",
|
|
576
578
|
),
|
|
577
|
-
|
|
579
|
+
prompts.Text(
|
|
578
580
|
"email",
|
|
579
581
|
message="Email for registration (leave empty for default)",
|
|
580
582
|
default="",
|
|
581
583
|
),
|
|
582
|
-
|
|
584
|
+
prompts.List(
|
|
583
585
|
"method",
|
|
584
586
|
message="Certificate obtention method",
|
|
585
587
|
choices=[
|
|
@@ -589,14 +591,14 @@ class InteractiveMode:
|
|
|
589
591
|
("Webroot", "webroot"),
|
|
590
592
|
],
|
|
591
593
|
),
|
|
592
|
-
|
|
594
|
+
prompts.Confirm(
|
|
593
595
|
"dry_run",
|
|
594
596
|
message="Dry run (test without obtaining)?",
|
|
595
597
|
default=False,
|
|
596
598
|
),
|
|
597
599
|
]
|
|
598
600
|
|
|
599
|
-
answers =
|
|
601
|
+
answers = prompts.prompt(questions)
|
|
600
602
|
if not answers:
|
|
601
603
|
return 0
|
|
602
604
|
|
|
@@ -631,7 +633,7 @@ class InteractiveMode:
|
|
|
631
633
|
def _cert_renew(self) -> int:
|
|
632
634
|
"""Handle cert renew flow."""
|
|
633
635
|
questions = [
|
|
634
|
-
|
|
636
|
+
prompts.List(
|
|
635
637
|
"scope",
|
|
636
638
|
message="What to renew?",
|
|
637
639
|
choices=[
|
|
@@ -639,19 +641,19 @@ class InteractiveMode:
|
|
|
639
641
|
("Specific certificate", "specific"),
|
|
640
642
|
],
|
|
641
643
|
),
|
|
642
|
-
|
|
644
|
+
prompts.Confirm(
|
|
643
645
|
"force",
|
|
644
646
|
message="Force renewal?",
|
|
645
647
|
default=False,
|
|
646
648
|
),
|
|
647
|
-
|
|
649
|
+
prompts.Confirm(
|
|
648
650
|
"dry_run",
|
|
649
651
|
message="Dry run?",
|
|
650
652
|
default=False,
|
|
651
653
|
),
|
|
652
654
|
]
|
|
653
655
|
|
|
654
|
-
answers =
|
|
656
|
+
answers = prompts.prompt(questions)
|
|
655
657
|
if not answers:
|
|
656
658
|
return 0
|
|
657
659
|
|
|
@@ -676,25 +678,25 @@ class InteractiveMode:
|
|
|
676
678
|
def _prompt_domain(self, message: str) -> str:
|
|
677
679
|
"""Prompt for a domain name."""
|
|
678
680
|
questions = [
|
|
679
|
-
|
|
681
|
+
prompts.Text(
|
|
680
682
|
"domain",
|
|
681
683
|
message=message,
|
|
682
684
|
validate=lambda _, x: check_domain(x) or "Invalid domain",
|
|
683
685
|
),
|
|
684
686
|
]
|
|
685
|
-
answers =
|
|
687
|
+
answers = prompts.prompt(questions)
|
|
686
688
|
return answers["domain"] if answers else None
|
|
687
689
|
|
|
688
690
|
def _prompt_text(self, message: str, default: str = "") -> str:
|
|
689
691
|
"""Prompt for text input."""
|
|
690
692
|
questions = [
|
|
691
|
-
|
|
693
|
+
prompts.Text(
|
|
692
694
|
"value",
|
|
693
695
|
message=message,
|
|
694
696
|
default=default,
|
|
695
697
|
),
|
|
696
698
|
]
|
|
697
|
-
answers =
|
|
699
|
+
answers = prompts.prompt(questions)
|
|
698
700
|
return answers["value"] if answers else default
|
|
699
701
|
|
|
700
702
|
def _run_command(self, resource: str, action: str, target: str | None = None, **kwargs) -> int:
|