wasm-cli 0.14.1__tar.gz → 0.14.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.
Files changed (146) hide show
  1. {wasm_cli-0.14.1/src/wasm_cli.egg-info → wasm_cli-0.14.2}/PKG-INFO +1 -1
  2. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/pyproject.toml +1 -1
  3. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/setup.py +1 -1
  4. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/__init__.py +1 -1
  5. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/webapp.py +67 -9
  6. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/parser.py +22 -3
  7. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/__init__.py +2 -0
  8. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/logger.py +2 -0
  9. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/store.py +26 -0
  10. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/__init__.py +2 -0
  11. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/helpers/__init__.py +4 -0
  12. wasm_cli-0.14.2/src/wasm/deployers/helpers/turbo.py +337 -0
  13. wasm_cli-0.14.2/src/wasm/deployers/helpers/workspace.py +423 -0
  14. wasm_cli-0.14.2/src/wasm/deployers/monorepo.py +1243 -0
  15. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/registry.py +2 -0
  16. wasm_cli-0.14.2/src/wasm/templates/nginx/monorepo.conf.j2 +144 -0
  17. {wasm_cli-0.14.1 → wasm_cli-0.14.2/src/wasm_cli.egg-info}/PKG-INFO +1 -1
  18. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm_cli.egg-info/SOURCES.txt +4 -0
  19. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/LICENSE +0 -0
  20. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/MANIFEST.in +0 -0
  21. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/README.md +0 -0
  22. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/docs/MONITOR.md +0 -0
  23. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/docs/OBS_SETUP.md +0 -0
  24. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/docs/assets/logo.png +0 -0
  25. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/docs/assets/logo_bg.png +0 -0
  26. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/man/wasm.1 +0 -0
  27. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/setup.cfg +0 -0
  28. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/__main__.py +0 -0
  29. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/__init__.py +0 -0
  30. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/__init__.py +0 -0
  31. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/backup.py +0 -0
  32. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/cert.py +0 -0
  33. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/config.py +0 -0
  34. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/db.py +0 -0
  35. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/health.py +0 -0
  36. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/monitor.py +0 -0
  37. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/service.py +0 -0
  38. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/setup.py +0 -0
  39. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/site.py +0 -0
  40. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/store.py +0 -0
  41. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/version.py +0 -0
  42. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/commands/web.py +0 -0
  43. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/cli/interactive.py +0 -0
  44. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/completions/__init__.py +0 -0
  45. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/completions/_wasm +0 -0
  46. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/completions/wasm.bash +0 -0
  47. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/completions/wasm.fish +0 -0
  48. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/config.py +0 -0
  49. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/dependencies.py +0 -0
  50. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/exceptions.py +0 -0
  51. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/update_checker.py +0 -0
  52. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/core/utils.py +0 -0
  53. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/base.py +0 -0
  54. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/helpers/package_manager.py +0 -0
  55. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/helpers/path_resolver.py +0 -0
  56. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/helpers/prisma.py +0 -0
  57. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/nextjs.py +0 -0
  58. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/nodejs.py +0 -0
  59. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/python.py +0 -0
  60. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/static.py +0 -0
  61. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/deployers/vite.py +0 -0
  62. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/main.py +0 -0
  63. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/__init__.py +0 -0
  64. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/apache_manager.py +0 -0
  65. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/backup_manager.py +0 -0
  66. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/base_manager.py +0 -0
  67. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/cert_manager.py +0 -0
  68. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/__init__.py +0 -0
  69. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/base.py +0 -0
  70. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/mongodb.py +0 -0
  71. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/mysql.py +0 -0
  72. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/postgres.py +0 -0
  73. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/redis.py +0 -0
  74. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/database/registry.py +0 -0
  75. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/nginx_manager.py +0 -0
  76. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/service_manager.py +0 -0
  77. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/managers/source_manager.py +0 -0
  78. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/monitor/__init__.py +0 -0
  79. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/monitor/ai_analyzer.py +0 -0
  80. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/monitor/email_notifier.py +0 -0
  81. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/monitor/process_monitor.py +0 -0
  82. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/monitor/threat_store.py +0 -0
  83. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/__init__.py +0 -0
  84. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
  85. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/apache/static.conf.j2 +0 -0
  86. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
  87. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/nginx/static.conf.j2 +0 -0
  88. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/templates/systemd/app.service.j2 +0 -0
  89. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/validators/__init__.py +0 -0
  90. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/validators/domain.py +0 -0
  91. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/validators/port.py +0 -0
  92. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/validators/source.py +0 -0
  93. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/validators/ssh.py +0 -0
  94. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/__init__.py +0 -0
  95. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/__init__.py +0 -0
  96. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/apps.py +0 -0
  97. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/auth.py +0 -0
  98. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/backups.py +0 -0
  99. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/certs.py +0 -0
  100. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/config.py +0 -0
  101. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/databases.py +0 -0
  102. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/jobs.py +0 -0
  103. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/monitor.py +0 -0
  104. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/router.py +0 -0
  105. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/services.py +0 -0
  106. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/sites.py +0 -0
  107. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/api/system.py +0 -0
  108. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/auth.py +0 -0
  109. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/jobs.py +0 -0
  110. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/server.py +0 -0
  111. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/css/main.css +0 -0
  112. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/index.html +0 -0
  113. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/components/cards.js +0 -0
  114. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/components/jobs.js +0 -0
  115. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/components/metrics.js +0 -0
  116. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/components/skeleton.js +0 -0
  117. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/api.js +0 -0
  118. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/dialogs.js +0 -0
  119. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/notifications.js +0 -0
  120. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/router.js +0 -0
  121. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/search.js +0 -0
  122. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/shortcuts.js +0 -0
  123. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/theme.js +0 -0
  124. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/ui.js +0 -0
  125. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/core/websocket.js +0 -0
  126. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/main.js +0 -0
  127. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/apps.js +0 -0
  128. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/backups.js +0 -0
  129. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/certs.js +0 -0
  130. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/config.js +0 -0
  131. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/dashboard.js +0 -0
  132. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/databases.js +0 -0
  133. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/jobs.js +0 -0
  134. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/logs.js +0 -0
  135. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/monitor.js +0 -0
  136. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/services.js +0 -0
  137. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/js/pages/sites.js +0 -0
  138. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/login.html +0 -0
  139. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/logo.png +0 -0
  140. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/static/logo.webp +0 -0
  141. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/websockets/__init__.py +0 -0
  142. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm/web/websockets/router.py +0 -0
  143. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
  144. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm_cli.egg-info/entry_points.txt +0 -0
  145. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm_cli.egg-info/requires.txt +0 -0
  146. {wasm_cli-0.14.1 → wasm_cli-0.14.2}/src/wasm_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wasm-cli
3
- Version: 0.14.1
3
+ Version: 0.14.2
4
4
  Summary: Web App System Management - Deploy and manage web applications on Linux servers
5
5
  Author: Yago López Prado
6
6
  Author-email: Yago López Prado <yago.lopez.adeje@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wasm-cli"
7
- version = "0.14.1"
7
+ version = "0.14.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" }
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
9
9
 
10
10
  setup(
11
11
  name="wasm-cli",
12
- version="0.14.1",
12
+ version="0.14.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",
@@ -8,7 +8,7 @@ WASM - Web App System Management
8
8
  A robust CLI tool for deploying and managing web applications on Linux servers.
9
9
  """
10
10
 
11
- __version__ = "0.14.1"
11
+ __version__ = "0.14.2"
12
12
  __author__ = "Yago López Prado"
13
13
  __license__ = "WASM-NCSAL"
14
14
 
@@ -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
- app_name = domain_to_app_name(domain)
474
- app_path = config.apps_directory / app_name
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
 
@@ -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",
@@ -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",
@@ -63,6 +63,8 @@ class Icons:
63
63
  CLOCK = "⏱"
64
64
  FOLDER = "📁"
65
65
  FILE = "📄"
66
+ DATABASE = "🗄️"
67
+ SEARCH = "🔍"
66
68
 
67
69
 
68
70
  class LogLevel(Enum):
@@ -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
 
@@ -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
  ]
@@ -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
  ]
@@ -0,0 +1,337 @@
1
+ # Copyright (c) 2024-2025 Yago Lopez Prado
2
+ # Licensed under WASM-NCSAL 1.0 (Commercial use prohibited)
3
+ # https://github.com/Perkybeet/wasm/blob/main/LICENSE
4
+
5
+ """
6
+ Turborepo helper for monorepo deployers.
7
+
8
+ Handles parsing of turbo.json configuration and provides build
9
+ pipeline information for monorepo deployments.
10
+ """
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Set
15
+
16
+ from wasm.core.logger import Logger
17
+
18
+
19
+ class TurboHelper:
20
+ """
21
+ Helper for Turborepo configuration analysis.
22
+
23
+ Provides parsing of turbo.json, build pipeline detection,
24
+ and dependency order calculation.
25
+ """
26
+
27
+ def __init__(self, logger: Optional[Logger] = None):
28
+ """
29
+ Initialize Turborepo helper.
30
+
31
+ Args:
32
+ logger: Logger instance for output.
33
+ """
34
+ self.logger = logger or Logger()
35
+ self._config: Optional[Dict] = None
36
+ self._config_path: Optional[Path] = None
37
+
38
+ def detect(self, app_path: Path) -> bool:
39
+ """
40
+ Detect if the project uses Turborepo.
41
+
42
+ Args:
43
+ app_path: Root path of the project.
44
+
45
+ Returns:
46
+ True if Turborepo is detected.
47
+ """
48
+ turbo_json = app_path / "turbo.json"
49
+ if turbo_json.exists():
50
+ return True
51
+
52
+ # Also check package.json for turbo field
53
+ package_json = app_path / "package.json"
54
+ if package_json.exists():
55
+ try:
56
+ with open(package_json) as f:
57
+ pkg = json.load(f)
58
+ return "turbo" in pkg
59
+ except (json.JSONDecodeError, OSError):
60
+ pass
61
+
62
+ return False
63
+
64
+ def load_config(self, app_path: Path) -> Dict:
65
+ """
66
+ Load and parse turbo.json configuration.
67
+
68
+ Args:
69
+ app_path: Root path of the project.
70
+
71
+ Returns:
72
+ Parsed configuration dictionary.
73
+ """
74
+ if self._config and self._config_path == app_path:
75
+ return self._config
76
+
77
+ turbo_json = app_path / "turbo.json"
78
+
79
+ if not turbo_json.exists():
80
+ self.logger.debug("No turbo.json found, using defaults")
81
+ self._config = self._default_config()
82
+ self._config_path = app_path
83
+ return self._config
84
+
85
+ try:
86
+ with open(turbo_json) as f:
87
+ self._config = json.load(f)
88
+ self._config_path = app_path
89
+ return self._config
90
+ except (json.JSONDecodeError, OSError) as e:
91
+ self.logger.warning(f"Error reading turbo.json: {e}")
92
+ self._config = self._default_config()
93
+ self._config_path = app_path
94
+ return self._config
95
+
96
+ def _default_config(self) -> Dict:
97
+ """
98
+ Return default Turborepo configuration.
99
+
100
+ Returns:
101
+ Default config dictionary.
102
+ """
103
+ return {
104
+ "tasks": {
105
+ "build": {
106
+ "dependsOn": ["^build"],
107
+ "outputs": [".next/**", "dist/**"],
108
+ },
109
+ "dev": {
110
+ "cache": False,
111
+ "persistent": True,
112
+ },
113
+ "start": {
114
+ "dependsOn": ["build"],
115
+ "cache": False,
116
+ },
117
+ }
118
+ }
119
+
120
+ def get_build_command(self, app_path: Path) -> List[str]:
121
+ """
122
+ Get the build command for the monorepo.
123
+
124
+ Args:
125
+ app_path: Root path of the project.
126
+
127
+ Returns:
128
+ Build command as list of strings.
129
+ """
130
+ config = self.load_config(app_path)
131
+
132
+ # Check if build task exists
133
+ tasks = config.get("tasks", config.get("pipeline", {}))
134
+ has_build = "build" in tasks
135
+
136
+ if has_build:
137
+ return ["pnpm", "build"]
138
+
139
+ # Fallback to turbo run build
140
+ return ["pnpm", "exec", "turbo", "run", "build"]
141
+
142
+ def get_build_outputs(self, app_path: Path) -> List[str]:
143
+ """
144
+ Get expected build output directories.
145
+
146
+ Args:
147
+ app_path: Root path of the project.
148
+
149
+ Returns:
150
+ List of output directory patterns.
151
+ """
152
+ config = self.load_config(app_path)
153
+ tasks = config.get("tasks", config.get("pipeline", {}))
154
+
155
+ build_config = tasks.get("build", {})
156
+ outputs = build_config.get("outputs", [])
157
+
158
+ # Default outputs if not specified
159
+ if not outputs:
160
+ outputs = [".next/**", "dist/**", "build/**"]
161
+
162
+ return outputs
163
+
164
+ def get_task_dependencies(self, task_name: str, app_path: Path) -> List[str]:
165
+ """
166
+ Get dependencies for a specific task.
167
+
168
+ Args:
169
+ task_name: Name of the task (e.g., "build", "start").
170
+ app_path: Root path of the project.
171
+
172
+ Returns:
173
+ List of dependent task names.
174
+ """
175
+ config = self.load_config(app_path)
176
+ tasks = config.get("tasks", config.get("pipeline", {}))
177
+
178
+ task_config = tasks.get(task_name, {})
179
+ depends_on = task_config.get("dependsOn", [])
180
+
181
+ # Filter out topological dependencies (^)
182
+ direct_deps = [d.lstrip("^") for d in depends_on if not d.startswith("$")]
183
+
184
+ return direct_deps
185
+
186
+ def get_global_dependencies(self, app_path: Path) -> List[str]:
187
+ """
188
+ Get global dependencies that affect all tasks.
189
+
190
+ These are files/directories that, when changed, should
191
+ invalidate all caches.
192
+
193
+ Args:
194
+ app_path: Root path of the project.
195
+
196
+ Returns:
197
+ List of global dependency patterns.
198
+ """
199
+ config = self.load_config(app_path)
200
+
201
+ # Turbo 2.x format
202
+ global_deps = config.get("globalDependencies", [])
203
+
204
+ # Common global dependencies
205
+ if not global_deps:
206
+ global_deps = [
207
+ "pnpm-lock.yaml",
208
+ "package.json",
209
+ "tsconfig.json",
210
+ ]
211
+
212
+ return global_deps
213
+
214
+ def get_env_vars(self, app_path: Path) -> Set[str]:
215
+ """
216
+ Get environment variables that should be considered for builds.
217
+
218
+ Args:
219
+ app_path: Root path of the project.
220
+
221
+ Returns:
222
+ Set of environment variable names.
223
+ """
224
+ config = self.load_config(app_path)
225
+
226
+ env_vars = set()
227
+
228
+ # Global env (Turbo 2.x)
229
+ global_env = config.get("globalEnv", [])
230
+ env_vars.update(global_env)
231
+
232
+ # Task-specific env
233
+ tasks = config.get("tasks", config.get("pipeline", {}))
234
+ for task_config in tasks.values():
235
+ if isinstance(task_config, dict):
236
+ task_env = task_config.get("env", [])
237
+ env_vars.update(task_env)
238
+
239
+ return env_vars
240
+
241
+ def is_cacheable(self, task_name: str, app_path: Path) -> bool:
242
+ """
243
+ Check if a task should be cached.
244
+
245
+ Args:
246
+ task_name: Name of the task.
247
+ app_path: Root path of the project.
248
+
249
+ Returns:
250
+ True if the task is cacheable.
251
+ """
252
+ config = self.load_config(app_path)
253
+ tasks = config.get("tasks", config.get("pipeline", {}))
254
+
255
+ task_config = tasks.get(task_name, {})
256
+
257
+ # Cache is enabled by default unless explicitly disabled
258
+ return task_config.get("cache", True) is not False
259
+
260
+ def get_filter_args(self, workspaces: List[str]) -> List[str]:
261
+ """
262
+ Generate filter arguments for turbo to only build specific workspaces.
263
+
264
+ Args:
265
+ workspaces: List of workspace names to include.
266
+
267
+ Returns:
268
+ List of filter arguments for turbo command.
269
+ """
270
+ if not workspaces:
271
+ return []
272
+
273
+ args = []
274
+ for workspace in workspaces:
275
+ args.extend(["--filter", workspace])
276
+
277
+ return args
278
+
279
+ def estimate_build_timeout(self, app_path: Path, workspace_count: int) -> int:
280
+ """
281
+ Estimate build timeout based on project size.
282
+
283
+ Args:
284
+ app_path: Root path of the project.
285
+ workspace_count: Number of workspaces to build.
286
+
287
+ Returns:
288
+ Recommended timeout in seconds.
289
+ """
290
+ # Base timeout: 5 minutes
291
+ base_timeout = 300
292
+
293
+ # Add 3 minutes per workspace
294
+ workspace_timeout = workspace_count * 180
295
+
296
+ # Cap at 30 minutes
297
+ return min(base_timeout + workspace_timeout, 1800)
298
+
299
+ def get_parallel_count(self) -> Optional[int]:
300
+ """
301
+ Get recommended parallelization count for builds.
302
+
303
+ Returns:
304
+ Number of parallel builds, or None for default.
305
+ """
306
+ # Let Turborepo decide based on available CPUs
307
+ return None
308
+
309
+ def validate_config(self, app_path: Path) -> List[str]:
310
+ """
311
+ Validate turbo.json configuration.
312
+
313
+ Args:
314
+ app_path: Root path of the project.
315
+
316
+ Returns:
317
+ List of validation warnings (empty if valid).
318
+ """
319
+ warnings = []
320
+ config = self.load_config(app_path)
321
+
322
+ # Check for deprecated pipeline key
323
+ if "pipeline" in config and "tasks" not in config:
324
+ warnings.append(
325
+ "turbo.json uses deprecated 'pipeline' key. "
326
+ "Consider upgrading to 'tasks' (Turbo 2.x)."
327
+ )
328
+
329
+ # Check for build task
330
+ tasks = config.get("tasks", config.get("pipeline", {}))
331
+ if "build" not in tasks:
332
+ warnings.append(
333
+ "No 'build' task defined in turbo.json. "
334
+ "Build command may not work as expected."
335
+ )
336
+
337
+ return warnings