wasm-cli 0.14.0__py3-none-any.whl → 0.14.2__py3-none-any.whl
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/__init__.py +1 -1
- wasm/cli/commands/backup.py +18 -10
- wasm/cli/commands/store.py +20 -4
- wasm/cli/commands/webapp.py +71 -13
- wasm/cli/parser.py +27 -3
- wasm/completions/_wasm +31 -7
- wasm/completions/wasm.bash +25 -1
- wasm/completions/wasm.fish +24 -1
- wasm/core/__init__.py +2 -0
- wasm/core/logger.py +2 -0
- wasm/core/store.py +26 -0
- wasm/core/utils.py +18 -3
- wasm/deployers/__init__.py +2 -0
- wasm/deployers/base.py +3 -3
- wasm/deployers/helpers/__init__.py +4 -0
- wasm/deployers/helpers/turbo.py +337 -0
- wasm/deployers/helpers/workspace.py +423 -0
- wasm/deployers/monorepo.py +1243 -0
- wasm/deployers/registry.py +2 -0
- wasm/managers/backup_manager.py +108 -14
- wasm/managers/service_manager.py +91 -45
- wasm/templates/nginx/monorepo.conf.j2 +144 -0
- wasm/web/api/apps.py +6 -4
- wasm/web/api/backups.py +37 -17
- wasm/web/api/services.py +31 -25
- wasm/web/websockets/router.py +3 -1
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/METADATA +1 -1
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/RECORD +33 -29
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/WHEEL +1 -1
- {wasm_cli-0.14.0.data → wasm_cli-0.14.2.data}/data/share/man/man1/wasm.1 +0 -0
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/entry_points.txt +0 -0
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/licenses/LICENSE +0 -0
- {wasm_cli-0.14.0.dist-info → wasm_cli-0.14.2.dist-info}/top_level.txt +0 -0
wasm/__init__.py
CHANGED
wasm/cli/commands/backup.py
CHANGED
|
@@ -105,6 +105,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
105
105
|
include_env = not getattr(args, "no_env", False)
|
|
106
106
|
include_node_modules = getattr(args, "include_node_modules", False)
|
|
107
107
|
include_build = getattr(args, "include_build", False)
|
|
108
|
+
include_databases = getattr(args, "include_databases", False)
|
|
108
109
|
tags = getattr(args, "tags", None)
|
|
109
110
|
|
|
110
111
|
if not domain:
|
|
@@ -127,6 +128,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
127
128
|
include_env=include_env,
|
|
128
129
|
include_node_modules=include_node_modules,
|
|
129
130
|
include_build=include_build,
|
|
131
|
+
include_databases=include_databases,
|
|
130
132
|
tags=tag_list,
|
|
131
133
|
)
|
|
132
134
|
|
|
@@ -135,7 +137,13 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
135
137
|
logger.info(f" Size: {metadata.size_human}")
|
|
136
138
|
if metadata.git_commit:
|
|
137
139
|
logger.info(f" Commit: {metadata.git_commit} ({metadata.git_branch})")
|
|
138
|
-
|
|
140
|
+
if metadata.database_backups:
|
|
141
|
+
db_count = len(metadata.database_backups)
|
|
142
|
+
logger.info(f" Databases: {db_count} backed up")
|
|
143
|
+
for db_info in metadata.database_backups:
|
|
144
|
+
size_mb = db_info.get("size_bytes", 0) / (1024 * 1024)
|
|
145
|
+
logger.info(f" - {db_info['engine']}/{db_info['name']} ({size_mb:.1f} MB)")
|
|
146
|
+
|
|
139
147
|
return 0
|
|
140
148
|
|
|
141
149
|
except BackupError as e:
|
|
@@ -195,7 +203,7 @@ def _backup_list(args: Namespace, verbose: bool) -> int:
|
|
|
195
203
|
by_domain[backup.domain].append(backup)
|
|
196
204
|
|
|
197
205
|
for dom, dom_backups in by_domain.items():
|
|
198
|
-
logger.info(f"\n
|
|
206
|
+
logger.info(f"\n[{dom}]")
|
|
199
207
|
_print_backup_table(dom_backups, logger, indent=True)
|
|
200
208
|
|
|
201
209
|
return 0
|
|
@@ -226,7 +234,7 @@ def _print_backup_table(backups, logger, indent: bool = False):
|
|
|
226
234
|
desc_str = f" - {backup.description}"
|
|
227
235
|
|
|
228
236
|
logger.info(
|
|
229
|
-
f"{prefix}
|
|
237
|
+
f"{prefix}- {backup.id}: {backup.size_human}, "
|
|
230
238
|
f"{backup.age}{commit_str}{tags_str}{desc_str}"
|
|
231
239
|
)
|
|
232
240
|
|
|
@@ -359,17 +367,17 @@ def _backup_verify(args: Namespace, verbose: bool) -> int:
|
|
|
359
367
|
|
|
360
368
|
if result["valid"]:
|
|
361
369
|
logger.success("Backup is valid")
|
|
362
|
-
if result.get("
|
|
363
|
-
logger.info("
|
|
364
|
-
if result.get("
|
|
365
|
-
logger.info(f"
|
|
370
|
+
if result.get("checksum_ok"):
|
|
371
|
+
logger.info(" [OK] Checksum verified")
|
|
372
|
+
if result.get("files_ok"):
|
|
373
|
+
logger.info(f" [OK] Archive valid ({result.get('file_count', '?')} files)")
|
|
366
374
|
else:
|
|
367
375
|
logger.error("Backup is invalid")
|
|
368
376
|
for err in result["errors"]:
|
|
369
|
-
logger.error(f"
|
|
370
|
-
|
|
377
|
+
logger.error(f" [ERROR] {err}")
|
|
378
|
+
|
|
371
379
|
for warn in result.get("warnings", []):
|
|
372
|
-
logger.warning(f"
|
|
380
|
+
logger.warning(f" [WARN] {warn}")
|
|
373
381
|
|
|
374
382
|
return 0 if result["valid"] else 1
|
|
375
383
|
|
wasm/cli/commands/store.py
CHANGED
|
@@ -313,13 +313,29 @@ def _store_import(args: Namespace, verbose: bool) -> int:
|
|
|
313
313
|
else:
|
|
314
314
|
logger.step(2, 3, "No Apache sites found")
|
|
315
315
|
|
|
316
|
-
# 3. Import from systemd services (wasm-*
|
|
316
|
+
# 3. Import from systemd services (both legacy wasm-* and new format)
|
|
317
317
|
logger.step(3, 3, "Scanning systemd services")
|
|
318
318
|
if SYSTEMD_DIR.exists():
|
|
319
|
-
|
|
319
|
+
# Find all potential WASM service files
|
|
320
|
+
service_files = list(SYSTEMD_DIR.glob("wasm-*.service"))
|
|
321
|
+
# Also check for services matching domain pattern (new format)
|
|
322
|
+
for sf in SYSTEMD_DIR.glob("*.service"):
|
|
323
|
+
name = sf.stem
|
|
324
|
+
# Skip if already found as wasm-* or if it's a system service
|
|
325
|
+
if name.startswith("wasm-") or not "-" in name:
|
|
326
|
+
continue
|
|
327
|
+
# Check if it looks like a domain-based name (has hyphen, no @ or other special chars)
|
|
328
|
+
if "@" not in name and name.count("-") >= 1:
|
|
329
|
+
service_files.append(sf)
|
|
330
|
+
|
|
331
|
+
for service_file in service_files:
|
|
320
332
|
# Extract app name from service file name
|
|
321
|
-
service_name = service_file.stem
|
|
322
|
-
|
|
333
|
+
service_name = service_file.stem
|
|
334
|
+
# Handle both legacy (wasm-example-com) and new format (example-com)
|
|
335
|
+
if service_name.startswith("wasm-"):
|
|
336
|
+
app_name = service_name[5:] # Remove wasm- prefix
|
|
337
|
+
else:
|
|
338
|
+
app_name = service_name
|
|
323
339
|
|
|
324
340
|
if store.get_service(app_name):
|
|
325
341
|
continue
|
wasm/cli/commands/webapp.py
CHANGED
|
@@ -202,10 +202,14 @@ def _handle_create(args: Namespace) -> int:
|
|
|
202
202
|
logger.key_value("Package Manager", package_manager)
|
|
203
203
|
logger.key_value("SSL", "Yes" if not args.no_ssl else "No")
|
|
204
204
|
logger.blank()
|
|
205
|
-
|
|
205
|
+
|
|
206
|
+
# Handle monorepo deployments specially
|
|
207
|
+
if app_type == "monorepo":
|
|
208
|
+
return _handle_monorepo_create(args, domain, env_vars, logger)
|
|
209
|
+
|
|
206
210
|
# Get deployer
|
|
207
211
|
deployer = get_deployer(app_type, verbose=args.verbose)
|
|
208
|
-
|
|
212
|
+
|
|
209
213
|
# Configure deployer
|
|
210
214
|
deployer.configure(
|
|
211
215
|
domain=domain,
|
|
@@ -217,10 +221,51 @@ def _handle_create(args: Namespace) -> int:
|
|
|
217
221
|
env_vars=env_vars,
|
|
218
222
|
package_manager=package_manager,
|
|
219
223
|
)
|
|
220
|
-
|
|
224
|
+
|
|
221
225
|
# Run deployment
|
|
222
226
|
deployer.deploy()
|
|
223
|
-
|
|
227
|
+
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _handle_monorepo_create(args: Namespace, domain: str, env_vars: Dict, logger: Logger) -> int:
|
|
232
|
+
"""Handle monorepo deployment specially."""
|
|
233
|
+
from wasm.deployers.monorepo import MonorepoDeployer
|
|
234
|
+
|
|
235
|
+
# Parse subdomain overrides
|
|
236
|
+
subdomain_overrides = {}
|
|
237
|
+
if getattr(args, "subdomains", None):
|
|
238
|
+
for mapping in args.subdomains:
|
|
239
|
+
if ":" in mapping:
|
|
240
|
+
app_name, subdomain = mapping.split(":", 1)
|
|
241
|
+
subdomain_overrides[app_name] = subdomain
|
|
242
|
+
else:
|
|
243
|
+
logger.warning(f"Invalid subdomain mapping: {mapping} (expected app:subdomain)")
|
|
244
|
+
|
|
245
|
+
# Get workspace filter
|
|
246
|
+
workspace_filter = getattr(args, "workspaces", None)
|
|
247
|
+
|
|
248
|
+
# Get skip_database flag
|
|
249
|
+
skip_database = getattr(args, "no_database", False)
|
|
250
|
+
|
|
251
|
+
# Create and configure deployer
|
|
252
|
+
deployer = MonorepoDeployer(verbose=args.verbose)
|
|
253
|
+
|
|
254
|
+
deployer.configure(
|
|
255
|
+
domain=domain,
|
|
256
|
+
source=args.source,
|
|
257
|
+
webserver=args.webserver,
|
|
258
|
+
ssl=not args.no_ssl,
|
|
259
|
+
branch=args.branch,
|
|
260
|
+
env_vars=env_vars,
|
|
261
|
+
subdomain_overrides=subdomain_overrides,
|
|
262
|
+
workspace_filter=workspace_filter,
|
|
263
|
+
skip_database=skip_database,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Run deployment
|
|
267
|
+
deployer.deploy()
|
|
268
|
+
|
|
224
269
|
return 0
|
|
225
270
|
|
|
226
271
|
|
|
@@ -457,7 +502,7 @@ def _handle_start(args: Namespace) -> int:
|
|
|
457
502
|
def _handle_update(args: Namespace) -> int:
|
|
458
503
|
"""
|
|
459
504
|
Handle webapp update command with zero-downtime strategy.
|
|
460
|
-
|
|
505
|
+
|
|
461
506
|
Strategy:
|
|
462
507
|
1. Create pre-update backup (for rollback)
|
|
463
508
|
2. Pull/fetch new code
|
|
@@ -468,11 +513,24 @@ def _handle_update(args: Namespace) -> int:
|
|
|
468
513
|
"""
|
|
469
514
|
logger = Logger(verbose=args.verbose)
|
|
470
515
|
config = Config()
|
|
471
|
-
|
|
516
|
+
|
|
517
|
+
from wasm.core.store import get_store
|
|
518
|
+
store = get_store()
|
|
519
|
+
|
|
472
520
|
domain = validate_domain(args.domain)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
521
|
+
|
|
522
|
+
# Consultar BD primero para obtener el app_path real
|
|
523
|
+
app = store.get_app(domain)
|
|
524
|
+
|
|
525
|
+
if app and app.app_path:
|
|
526
|
+
# Usar el path almacenado en la BD (soporta apps legacy con prefijo wasm-)
|
|
527
|
+
app_path = Path(app.app_path)
|
|
528
|
+
app_name = app_path.name
|
|
529
|
+
else:
|
|
530
|
+
# Fallback para apps no registradas en BD
|
|
531
|
+
app_name = domain_to_app_name(domain)
|
|
532
|
+
app_path = config.apps_directory / app_name
|
|
533
|
+
|
|
476
534
|
if not app_path.exists():
|
|
477
535
|
raise WASMError(f"Application not found: {domain}")
|
|
478
536
|
|
|
@@ -639,7 +697,7 @@ def _handle_delete(args: Namespace) -> int:
|
|
|
639
697
|
try:
|
|
640
698
|
status = service_manager.status(app_name)
|
|
641
699
|
if status.get("exists"):
|
|
642
|
-
logger.key_value("Stop and remove service",
|
|
700
|
+
logger.key_value("Stop and remove service", app_name)
|
|
643
701
|
except Exception:
|
|
644
702
|
pass
|
|
645
703
|
|
|
@@ -734,9 +792,9 @@ def _handle_logs(args: Namespace) -> int:
|
|
|
734
792
|
domain = validate_domain(args.domain)
|
|
735
793
|
app_name = domain_to_app_name(domain)
|
|
736
794
|
|
|
737
|
-
# Get service name (
|
|
738
|
-
service_name =
|
|
739
|
-
|
|
795
|
+
# Get resolved service name (handles both legacy wasm-* and new format)
|
|
796
|
+
service_name = service_manager._resolve_service_name(app_name)
|
|
797
|
+
|
|
740
798
|
if args.follow:
|
|
741
799
|
# Use journalctl directly for follow mode
|
|
742
800
|
import subprocess
|
wasm/cli/parser.py
CHANGED
|
@@ -36,7 +36,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
36
36
|
CLI tool for deploying and managing web applications on Linux servers.
|
|
37
37
|
Automates: Git clone, build, systemd, Nginx/Apache, SSL (Let's Encrypt).
|
|
38
38
|
|
|
39
|
-
Supported app types: nextjs, nodejs, vite, python, static
|
|
39
|
+
Supported app types: nextjs, nodejs, vite, python, static, monorepo
|
|
40
40
|
|
|
41
41
|
QUICK START:
|
|
42
42
|
wasm setup init Initialize directories (first time, requires sudo)
|
|
@@ -179,7 +179,7 @@ def _add_webapp_commands(subparsers) -> None:
|
|
|
179
179
|
)
|
|
180
180
|
create.add_argument(
|
|
181
181
|
"--type", "-t",
|
|
182
|
-
choices=["nextjs", "nodejs", "vite", "python", "static", "auto"],
|
|
182
|
+
choices=["nextjs", "nodejs", "vite", "python", "static", "monorepo", "auto"],
|
|
183
183
|
default="auto",
|
|
184
184
|
help="Application type (default: auto-detect)",
|
|
185
185
|
)
|
|
@@ -213,7 +213,26 @@ def _add_webapp_commands(subparsers) -> None:
|
|
|
213
213
|
default="auto",
|
|
214
214
|
help="Package manager to use (default: auto-detect)",
|
|
215
215
|
)
|
|
216
|
-
|
|
216
|
+
|
|
217
|
+
# Monorepo-specific options
|
|
218
|
+
create.add_argument(
|
|
219
|
+
"--subdomains",
|
|
220
|
+
nargs="+",
|
|
221
|
+
metavar="APP:SUBDOMAIN",
|
|
222
|
+
help="Subdomain mapping for monorepo apps (e.g., erp-backend:api web-gateway:app)",
|
|
223
|
+
)
|
|
224
|
+
create.add_argument(
|
|
225
|
+
"--workspaces",
|
|
226
|
+
nargs="+",
|
|
227
|
+
metavar="NAME",
|
|
228
|
+
help="Specific workspace apps to deploy (default: all)",
|
|
229
|
+
)
|
|
230
|
+
create.add_argument(
|
|
231
|
+
"--no-database",
|
|
232
|
+
action="store_true",
|
|
233
|
+
help="Skip database provisioning for monorepo",
|
|
234
|
+
)
|
|
235
|
+
|
|
217
236
|
# list (alias: ls)
|
|
218
237
|
subparsers.add_parser(
|
|
219
238
|
"list",
|
|
@@ -908,6 +927,11 @@ def _add_backup_parser(subparsers) -> None:
|
|
|
908
927
|
action="store_true",
|
|
909
928
|
help="Include build artifacts (.next, dist, build)",
|
|
910
929
|
)
|
|
930
|
+
create.add_argument(
|
|
931
|
+
"--include-databases", "--include-db",
|
|
932
|
+
action="store_true",
|
|
933
|
+
help="Include associated database dumps in backup",
|
|
934
|
+
)
|
|
911
935
|
create.add_argument(
|
|
912
936
|
"--tags", "-t",
|
|
913
937
|
help="Comma-separated tags for the backup",
|
wasm/completions/_wasm
CHANGED
|
@@ -14,14 +14,38 @@
|
|
|
14
14
|
|
|
15
15
|
# Helper functions
|
|
16
16
|
_wasm_apps() {
|
|
17
|
-
local apps_dir="/var/www/apps"
|
|
18
17
|
local apps=()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
|
|
19
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
20
|
+
local db_path="/var/lib/wasm/wasm.db"
|
|
21
|
+
if [[ ! -f "$db_path" ]]; then
|
|
22
|
+
db_path="$HOME/.local/share/wasm/wasm.db"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
|
|
26
|
+
apps=(${(f)"$(sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null)"})
|
|
27
|
+
else
|
|
28
|
+
# Fallback: list app directories and convert names to domains
|
|
29
|
+
local apps_dir="/var/www/apps"
|
|
30
|
+
if [[ -d "$apps_dir" ]]; then
|
|
31
|
+
for app in "$apps_dir"/*/; do
|
|
32
|
+
if [[ -d "$app" ]]; then
|
|
33
|
+
local name
|
|
34
|
+
name=$(basename "$app")
|
|
35
|
+
# Convert directory name to domain format
|
|
36
|
+
# Legacy: wasm-example-com -> example.com
|
|
37
|
+
# New: example-com -> example.com
|
|
38
|
+
if [[ "$name" == wasm-* ]]; then
|
|
39
|
+
apps+=("${${name#wasm-}//\-/.}")
|
|
40
|
+
elif [[ "$name" == *-* ]]; then
|
|
41
|
+
# New format: convert hyphens to dots
|
|
42
|
+
apps+=("${name//\-/.}")
|
|
43
|
+
else
|
|
44
|
+
apps+=("$name")
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
done 2>/dev/null
|
|
48
|
+
fi
|
|
25
49
|
fi
|
|
26
50
|
_describe -t apps 'deployed applications' apps
|
|
27
51
|
}
|
wasm/completions/wasm.bash
CHANGED
|
@@ -16,11 +16,35 @@
|
|
|
16
16
|
|
|
17
17
|
# Helper function to get list of deployed apps (domains)
|
|
18
18
|
_wasm_get_apps() {
|
|
19
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
20
|
+
local db_path="/var/lib/wasm/wasm.db"
|
|
21
|
+
if [[ ! -f "$db_path" ]]; then
|
|
22
|
+
db_path="$HOME/.local/share/wasm/wasm.db"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
|
|
26
|
+
sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Fallback: list app directories and convert names to domains
|
|
19
31
|
local apps_dir="/var/www/apps"
|
|
20
32
|
if [[ -d "$apps_dir" ]]; then
|
|
21
33
|
for app in "$apps_dir"/*/; do
|
|
22
34
|
if [[ -d "$app" ]]; then
|
|
23
|
-
|
|
35
|
+
local name
|
|
36
|
+
name=$(basename "$app")
|
|
37
|
+
# Convert directory name to domain format
|
|
38
|
+
# Legacy: wasm-example-com -> example.com
|
|
39
|
+
# New: example-com -> example.com
|
|
40
|
+
if [[ "$name" == wasm-* ]]; then
|
|
41
|
+
echo "${name#wasm-}" | tr '-' '.'
|
|
42
|
+
elif [[ "$name" == *-* ]]; then
|
|
43
|
+
# New format: convert hyphens to dots
|
|
44
|
+
echo "$name" | tr '-' '.'
|
|
45
|
+
else
|
|
46
|
+
echo "$name"
|
|
47
|
+
fi
|
|
24
48
|
fi
|
|
25
49
|
done 2>/dev/null
|
|
26
50
|
fi
|
wasm/completions/wasm.fish
CHANGED
|
@@ -14,11 +14,34 @@ complete -c wasm -f
|
|
|
14
14
|
|
|
15
15
|
# Helper functions
|
|
16
16
|
function __wasm_get_apps
|
|
17
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
18
|
+
set -l db_path "/var/lib/wasm/wasm.db"
|
|
19
|
+
if not test -f "$db_path"
|
|
20
|
+
set db_path "$HOME/.local/share/wasm/wasm.db"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if test -f "$db_path"; and command -v sqlite3 >/dev/null
|
|
24
|
+
sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Fallback: list app directories and convert names to domains
|
|
17
29
|
set -l apps_dir "/var/www/apps"
|
|
18
30
|
if test -d "$apps_dir"
|
|
19
31
|
for app in $apps_dir/*/
|
|
20
32
|
if test -d "$app"
|
|
21
|
-
basename "$app"
|
|
33
|
+
set -l name (basename "$app")
|
|
34
|
+
# Convert directory name to domain format
|
|
35
|
+
# Legacy: wasm-example-com -> example.com
|
|
36
|
+
# New: example-com -> example.com
|
|
37
|
+
if string match -q 'wasm-*' "$name"
|
|
38
|
+
string replace 'wasm-' '' "$name" | string replace -a '-' '.'
|
|
39
|
+
else if string match -q '*-*' "$name"
|
|
40
|
+
# New format: convert hyphens to dots
|
|
41
|
+
string replace -a '-' '.' "$name"
|
|
42
|
+
else
|
|
43
|
+
echo "$name"
|
|
44
|
+
end
|
|
22
45
|
end
|
|
23
46
|
end 2>/dev/null
|
|
24
47
|
end
|
wasm/core/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from wasm.core.store import (
|
|
|
11
11
|
Service,
|
|
12
12
|
Database,
|
|
13
13
|
DatabaseUser,
|
|
14
|
+
MonorepoWorkspace,
|
|
14
15
|
AppType,
|
|
15
16
|
AppStatus,
|
|
16
17
|
WebServer,
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
|
28
29
|
"Service",
|
|
29
30
|
"Database",
|
|
30
31
|
"DatabaseUser",
|
|
32
|
+
"MonorepoWorkspace",
|
|
31
33
|
"AppType",
|
|
32
34
|
"AppStatus",
|
|
33
35
|
"WebServer",
|
wasm/core/logger.py
CHANGED
wasm/core/store.py
CHANGED
|
@@ -35,6 +35,7 @@ class AppType(str, Enum):
|
|
|
35
35
|
PYTHON = "python"
|
|
36
36
|
VITE = "vite"
|
|
37
37
|
STATIC = "static"
|
|
38
|
+
MONOREPO = "monorepo"
|
|
38
39
|
UNKNOWN = "unknown"
|
|
39
40
|
|
|
40
41
|
|
|
@@ -211,6 +212,31 @@ class DatabaseUser:
|
|
|
211
212
|
return cls(**dict(row))
|
|
212
213
|
|
|
213
214
|
|
|
215
|
+
@dataclass
|
|
216
|
+
class MonorepoWorkspace:
|
|
217
|
+
"""
|
|
218
|
+
Configuration for a workspace app within a monorepo.
|
|
219
|
+
|
|
220
|
+
Used by MonorepoDeployer to track individual apps in a Turborepo/pnpm
|
|
221
|
+
workspace monorepo.
|
|
222
|
+
"""
|
|
223
|
+
name: str = ""
|
|
224
|
+
path: str = ""
|
|
225
|
+
app_type: str = AppType.UNKNOWN.value
|
|
226
|
+
subdomain: str = ""
|
|
227
|
+
port: int = 3000
|
|
228
|
+
start_command: Optional[str] = None
|
|
229
|
+
health_check: str = "/"
|
|
230
|
+
env_vars: Dict[str, str] = field(default_factory=dict)
|
|
231
|
+
|
|
232
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
233
|
+
"""Convert to dictionary."""
|
|
234
|
+
d = asdict(self)
|
|
235
|
+
if isinstance(d.get('env_vars'), dict):
|
|
236
|
+
d['env_vars'] = json.dumps(d['env_vars'])
|
|
237
|
+
return d
|
|
238
|
+
|
|
239
|
+
|
|
214
240
|
# Schema version for migrations
|
|
215
241
|
SCHEMA_VERSION = 1
|
|
216
242
|
|
wasm/core/utils.py
CHANGED
|
@@ -219,12 +219,27 @@ def sanitize_name(name: str) -> str:
|
|
|
219
219
|
def domain_to_app_name(domain: str) -> str:
|
|
220
220
|
"""
|
|
221
221
|
Convert a domain to an application name.
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
Args:
|
|
224
224
|
domain: Domain name (e.g., "myapp.example.com").
|
|
225
|
-
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Application name (e.g., "myapp-example-com").
|
|
228
|
+
"""
|
|
229
|
+
return sanitize_name(domain)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def legacy_app_name(domain: str) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Get legacy app name format (with wasm- prefix).
|
|
235
|
+
|
|
236
|
+
Used for backwards compatibility with apps created before v0.14.1.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
domain: Domain name (e.g., "myapp.example.com").
|
|
240
|
+
|
|
226
241
|
Returns:
|
|
227
|
-
|
|
242
|
+
Legacy application name (e.g., "wasm-myapp-example-com").
|
|
228
243
|
"""
|
|
229
244
|
return f"wasm-{sanitize_name(domain)}"
|
|
230
245
|
|
wasm/deployers/__init__.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from wasm.deployers.base import BaseDeployer
|
|
4
4
|
from wasm.deployers.registry import DeployerRegistry, get_deployer, detect_app_type
|
|
5
|
+
from wasm.deployers.monorepo import MonorepoDeployer
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"BaseDeployer",
|
|
8
9
|
"DeployerRegistry",
|
|
9
10
|
"get_deployer",
|
|
10
11
|
"detect_app_type",
|
|
12
|
+
"MonorepoDeployer",
|
|
11
13
|
]
|
wasm/deployers/base.py
CHANGED
|
@@ -899,9 +899,9 @@ class BaseDeployer(ABC):
|
|
|
899
899
|
app = self.store.get_app(self.domain)
|
|
900
900
|
app_id = app.id if app else None
|
|
901
901
|
|
|
902
|
-
# Service name
|
|
902
|
+
# Service name (no prefix for new format)
|
|
903
903
|
service_name = self.app_name
|
|
904
|
-
service_file = SYSTEMD_DIR / f"
|
|
904
|
+
service_file = SYSTEMD_DIR / f"{self.app_name}.service"
|
|
905
905
|
|
|
906
906
|
existing_service = self.store.get_service(service_name)
|
|
907
907
|
|
|
@@ -1142,7 +1142,7 @@ class BaseDeployer(ABC):
|
|
|
1142
1142
|
# Basic info
|
|
1143
1143
|
protocol = "https" if ssl_obtained else "http"
|
|
1144
1144
|
self.logger.key_value("URL", f"{protocol}://{self.domain}")
|
|
1145
|
-
self.logger.key_value("Service",
|
|
1145
|
+
self.logger.key_value("Service", self.app_name)
|
|
1146
1146
|
self.logger.key_value("Port", str(self.port))
|
|
1147
1147
|
self.logger.key_value("App Path", str(self.app_path))
|
|
1148
1148
|
|
|
@@ -12,9 +12,13 @@ to improve maintainability and testability.
|
|
|
12
12
|
from wasm.deployers.helpers.package_manager import PackageManagerHelper
|
|
13
13
|
from wasm.deployers.helpers.path_resolver import PathResolver
|
|
14
14
|
from wasm.deployers.helpers.prisma import PrismaHelper
|
|
15
|
+
from wasm.deployers.helpers.workspace import WorkspaceHelper
|
|
16
|
+
from wasm.deployers.helpers.turbo import TurboHelper
|
|
15
17
|
|
|
16
18
|
__all__ = [
|
|
17
19
|
"PackageManagerHelper",
|
|
18
20
|
"PathResolver",
|
|
19
21
|
"PrismaHelper",
|
|
22
|
+
"WorkspaceHelper",
|
|
23
|
+
"TurboHelper",
|
|
20
24
|
]
|