From 58831c536a59dcde570512ad7198560e1ec8894e Mon Sep 17 00:00:00 2001 From: busya Date: Sat, 20 Dec 2025 22:05:18 +0300 Subject: [PATCH] feat: implement project launch script run.sh and update README --- .kilocode/rules/specify-rules.md | 2 + README.md | 13 ++ backend/backend.log | 189 ++++++++++++++++++ .../backups/Logs/superset_tool_20251220.log | 35 ++++ backend/src/api/routes/settings.py | 75 +++++-- backend/src/core/config_manager.py | 25 +++ backend/src/plugins/backup.py | 5 +- frontend/src/components/TaskRunner.svelte | 3 +- frontend/src/lib/api.js | 2 +- frontend/src/pages/Settings.svelte | 7 + frontend/vite.config.js | 25 +++ run.sh | 155 ++++++++++++++ specs/002-app-settings/plan.md | 1 + specs/002-app-settings/spec.md | 9 +- specs/002-app-settings/tasks.md | 1 + .../checklists/requirements.md | 34 ++++ .../contracts/cli.md | 28 +++ specs/003-project-launch-script/data-model.md | 26 +++ specs/003-project-launch-script/plan.md | 72 +++++++ specs/003-project-launch-script/quickstart.md | 39 ++++ specs/003-project-launch-script/research.md | 57 ++++++ specs/003-project-launch-script/spec.md | 54 +++++ specs/003-project-launch-script/tasks.md | 135 +++++++++++++ 23 files changed, 964 insertions(+), 28 deletions(-) mode change 100755 => 100644 .kilocode/rules/specify-rules.md create mode 100644 backend/backend.log create mode 100644 backend/backups/Logs/superset_tool_20251220.log create mode 100755 run.sh create mode 100644 specs/003-project-launch-script/checklists/requirements.md create mode 100644 specs/003-project-launch-script/contracts/cli.md create mode 100644 specs/003-project-launch-script/data-model.md create mode 100644 specs/003-project-launch-script/plan.md create mode 100644 specs/003-project-launch-script/quickstart.md create mode 100644 specs/003-project-launch-script/research.md create mode 100644 specs/003-project-launch-script/spec.md create mode 100644 specs/003-project-launch-script/tasks.md diff --git a/.kilocode/rules/specify-rules.md b/.kilocode/rules/specify-rules.md old mode 100755 new mode 100644 index cf1c32b..0aeedac --- a/.kilocode/rules/specify-rules.md +++ b/.kilocode/rules/specify-rules.md @@ -3,6 +3,7 @@ Auto-generated from all feature plans. Last updated: 2025-12-19 ## Active Technologies +- Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash` (003-project-launch-script) - Python 3.9+ (Backend), Node.js 18+ (Frontend Build) (001-plugin-arch-svelte-ui) @@ -23,6 +24,7 @@ cd src; pytest; ruff check . Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions ## Recent Changes +- 003-project-launch-script: Added Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash` - 001-plugin-arch-svelte-ui: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build) diff --git a/README.md b/README.md index 45eeee3..d93e850 100755 --- a/README.md +++ b/README.md @@ -50,6 +50,19 @@ ## Использование +### Запуск проекта (Web UI) +Для запуска backend и frontend серверов одной командой: +```bash +./run.sh +``` +Опции: +- `--skip-install`: Пропустить проверку и установку зависимостей. +- `--help`: Показать справку. + +Переменные окружения: +- `BACKEND_PORT`: Порт для backend (по умолчанию 8000). +- `FRONTEND_PORT`: Порт для frontend (по умолчанию 5173). + ### Скрипт резервного копирования (`backup_script.py`) Для создания резервных копий дашбордов из настроенных окружений Superset: ```bash diff --git a/backend/backend.log b/backend/backend.log new file mode 100644 index 0000000..5196887 --- /dev/null +++ b/backend/backend.log @@ -0,0 +1,189 @@ +INFO: Will watch for changes in these directories: ['/home/user/ss-tools/backend'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [7952] using StatReload +INFO: Started server process [7968] +INFO: Waiting for application startup. +INFO: Application startup complete. +Error loading plugin module backup: No module named 'yaml' +Error loading plugin module migration: No module named 'yaml' +INFO: 127.0.0.1:36934 - "HEAD /docs HTTP/1.1" 200 OK +INFO: 127.0.0.1:55006 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:55006 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:55010 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:55010 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:55010 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:55010 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:55010 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:55010 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:55010 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:55010 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:35508 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:35508 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49820 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49820 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49822 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49822 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49822 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49822 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49908 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49908 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49922 - "OPTIONS /settings/environments HTTP/1.1" 200 OK +[2025-12-20 19:14:15,576][INFO][superset_tools_app] [ConfigManager.save_config][Coherence:OK] Configuration saved context={'path': '/home/user/ss-tools/config.json'} +INFO: 127.0.0.1:49922 - "POST /settings/environments HTTP/1.1" 200 OK +INFO: 127.0.0.1:49922 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49922 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:49922 - "OPTIONS /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 200 OK +INFO: 127.0.0.1:36930 - "POST /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 500 Internal Server Error +ERROR: Exception in ASGI application +Traceback (most recent call last): + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi + result = await app( # type: ignore[func-returns-value] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__ + return await self.app(scope, receive, send) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1135, in __call__ + await super().__call__(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/applications.py", line 107, in __call__ + await self.middleware_stack(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__ + raise exc + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__ + await self.app(scope, receive, _send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 93, in __call__ + await self.simple_response(scope, receive, send, request_headers=headers) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 144, in simple_response + await self.app(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__ + await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app + raise exc + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app + await app(scope, receive, sender) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__ + await self.app(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__ + await self.middleware_stack(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app + await route.handle(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle + await self.app(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 118, in app + await wrap_app_handling_exceptions(app, request)(scope, receive, send) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app + raise exc + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app + await app(scope, receive, sender) + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 104, in app + response = await f(request) + ^^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 428, in app + raw_response = await run_endpoint_function( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 314, in run_endpoint_function + return await dependant.call(**values) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/src/api/routes/settings.py", line 103, in test_connection + import httpx +ModuleNotFoundError: No module named 'httpx' +INFO: 127.0.0.1:45776 - "POST /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 200 OK +INFO: 127.0.0.1:45784 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:45784 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:41628 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:41628 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:41628 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:41628 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:60184 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:60184 - "GET /settings/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:60184 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:60184 - "GET /plugins/ HTTP/1.1" 200 OK +INFO: 127.0.0.1:60184 - "GET /settings HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:60184 - "GET /settings/ HTTP/1.1" 200 OK +WARNING: StatReload detected changes in 'src/core/plugin_loader.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [7968] +INFO: Started server process [12178] +INFO: Waiting for application startup. +INFO: Application startup complete. +WARNING: StatReload detected changes in 'src/dependencies.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [12178] +INFO: Started server process [12451] +INFO: Waiting for application startup. +INFO: Application startup complete. +Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully. +Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully. +INFO: 127.0.0.1:37334 - "GET / HTTP/1.1" 200 OK +INFO: 127.0.0.1:37334 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK +INFO: 127.0.0.1:39932 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK +INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK +INFO: 127.0.0.1:54900 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49280 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +INFO: 127.0.0.1:49280 - "GET /plugins/ HTTP/1.1" 200 OK +WARNING: StatReload detected changes in 'src/api/routes/plugins.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [12451] +INFO: Started server process [15016] +INFO: Waiting for application startup. +INFO: Application startup complete. +Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully. +Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully. +INFO: 127.0.0.1:59340 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +DEBUG: list_plugins called. Found 0 plugins. +INFO: 127.0.0.1:59340 - "GET /plugins/ HTTP/1.1" 200 OK +WARNING: StatReload detected changes in 'src/dependencies.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [15016] +INFO: Started server process [15257] +INFO: Waiting for application startup. +INFO: Application startup complete. +Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully. +Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully. +DEBUG: dependencies.py initialized. PluginLoader ID: 139922613090976 +DEBUG: dependencies.py initialized. PluginLoader ID: 139922627375088 +INFO: 127.0.0.1:57464 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +DEBUG: get_plugin_loader called. Returning PluginLoader ID: 139922627375088 +DEBUG: list_plugins called. Found 0 plugins. +INFO: 127.0.0.1:57464 - "GET /plugins/ HTTP/1.1" 200 OK +WARNING: StatReload detected changes in 'src/core/plugin_loader.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [15257] +INFO: Started server process [15533] +INFO: Waiting for application startup. +INFO: Application startup complete. +DEBUG: Loading plugin backup as src.plugins.backup +Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully. +DEBUG: Loading plugin migration as src.plugins.migration +Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully. +DEBUG: dependencies.py initialized. PluginLoader ID: 140371031142384 +INFO: 127.0.0.1:46470 - "GET /plugins HTTP/1.1" 307 Temporary Redirect +DEBUG: get_plugin_loader called. Returning PluginLoader ID: 140371031142384 +DEBUG: list_plugins called. Found 2 plugins. +DEBUG: Plugin: superset-backup +DEBUG: Plugin: superset-migration +INFO: 127.0.0.1:46470 - "GET /plugins/ HTTP/1.1" 200 OK +WARNING: StatReload detected changes in 'src/api/routes/settings.py'. Reloading... +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [15533] +INFO: Started server process [15827] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [15827] +INFO: Stopping reloader process [7952] diff --git a/backend/backups/Logs/superset_tool_20251220.log b/backend/backups/Logs/superset_tool_20251220.log new file mode 100644 index 0000000..6e540f6 --- /dev/null +++ b/backend/backups/Logs/superset_tool_20251220.log @@ -0,0 +1,35 @@ +2025-12-20 19:55:11,325 - INFO - [BackupPlugin][Entry] Starting backup for superset. +2025-12-20 19:55:11,325 - INFO - [setup_clients][Enter] Starting Superset clients initialization. +2025-12-20 19:55:11,327 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig +base_url + Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str] + For further information visit https://errors.pydantic.dev/2.12/v/value_error +Traceback (most recent call last): + File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 43, in setup_clients + config = SupersetConfig( + ^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__ + validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig +base_url + Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str] + For further information visit https://errors.pydantic.dev/2.12/v/value_error +2025-12-20 21:01:49,905 - INFO - [BackupPlugin][Entry] Starting backup for superset. +2025-12-20 21:01:49,906 - INFO - [setup_clients][Enter] Starting Superset clients initialization. +2025-12-20 21:01:49,988 - INFO - [setup_clients][Action] Loading environments from ConfigManager +2025-12-20 21:01:49,990 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig +base_url + Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str] + For further information visit https://errors.pydantic.dev/2.12/v/value_error +Traceback (most recent call last): + File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients + config = SupersetConfig( + ^^^^^^^^^^^^^^^ + File "/home/user/ss-tools/venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__ + validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig +base_url + Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str] + For further information visit https://errors.pydantic.dev/2.12/v/value_error diff --git a/backend/src/api/routes/settings.py b/backend/src/api/routes/settings.py index d7866b6..8f2b55b 100755 --- a/backend/src/api/routes/settings.py +++ b/backend/src/api/routes/settings.py @@ -66,10 +66,29 @@ async def get_environments(config_manager: ConfigManager = Depends(get_config_ma # @RETURN: Environment - The added environment. @router.post("/environments", response_model=Environment) async def add_environment( - env: Environment, + env: Environment, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[add_environment][Entry] Adding environment {env.id}") + + # Validate connection before adding + try: + superset_config = SupersetConfig( + env=env.name, + base_url=env.url, + auth={ + "provider": "db", + "username": env.username, + "password": env.password, + "refresh": "true" + } + ) + client = SupersetClient(config=superset_config) + client.get_dashboards(query={"page_size": 1}) + except Exception as e: + logger.error(f"[add_environment][Coherence:Failed] Connection validation failed: {e}") + raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}") + config_manager.add_environment(env) return env # [/DEF:add_environment] @@ -86,6 +105,32 @@ async def update_environment( config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[update_environment][Entry] Updating environment {id}") + + # If password is masked, we need the real one for validation + env_to_validate = env.copy(deep=True) + if env_to_validate.password == "********": + old_env = next((e for e in config_manager.get_environments() if e.id == id), None) + if old_env: + env_to_validate.password = old_env.password + + # Validate connection before updating + try: + superset_config = SupersetConfig( + env=env_to_validate.name, + base_url=env_to_validate.url, + auth={ + "provider": "db", + "username": env_to_validate.username, + "password": env_to_validate.password, + "refresh": "true" + } + ) + client = SupersetClient(config=superset_config) + client.get_dashboards(query={"page_size": 1}) + except Exception as e: + logger.error(f"[update_environment][Coherence:Failed] Connection validation failed: {e}") + raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}") + if config_manager.update_environment(id, env): return env raise HTTPException(status_code=404, detail=f"Environment {id} not found") @@ -152,34 +197,22 @@ async def test_environment_connection( # @PARAM: path (str) - The path to validate. # @RETURN: dict - Validation result. @router.post("/validate-path") -async def validate_backup_path(path_data: dict): +async def validate_backup_path( + path_data: dict, + config_manager: ConfigManager = Depends(get_config_manager) +): path = path_data.get("path") if not path: raise HTTPException(status_code=400, detail="Path is required") logger.info(f"[validate_backup_path][Entry] Validating path: {path}") - p = os.path.abspath(path) - exists = os.path.exists(p) - writable = os.access(p, os.W_OK) if exists else os.access(os.path.dirname(p), os.W_OK) + valid, message = config_manager.validate_path(path) - if not exists: - # Try to create it - try: - os.makedirs(p, exist_ok=True) - exists = True - writable = os.access(p, os.W_OK) - logger.info(f"[validate_backup_path][Action] Created directory: {p}") - except Exception as e: - logger.error(f"[validate_backup_path][Coherence:Failed] Failed to create directory: {e}") - return {"status": "error", "message": f"Path does not exist and could not be created: {e}"} - - if not writable: - logger.warning(f"[validate_backup_path][Coherence:Failed] Path not writable: {p}") - return {"status": "error", "message": "Path is not writable"} + if not valid: + return {"status": "error", "message": message} - logger.info(f"[validate_backup_path][Coherence:OK] Path valid: {p}") - return {"status": "success", "message": "Path is valid and writable"} + return {"status": "success", "message": message} # [/DEF:validate_backup_path] # [/DEF:SettingsRouter] diff --git a/backend/src/core/config_manager.py b/backend/src/core/config_manager.py index 57ff87c..613201b 100755 --- a/backend/src/core/config_manager.py +++ b/backend/src/core/config_manager.py @@ -124,6 +124,24 @@ class ConfigManager: logger.info(f"[update_global_settings][Exit] Settings updated") # [/DEF:update_global_settings] + # [DEF:validate_path:Function] + # @PURPOSE: Validates if a path exists and is writable. + # @PARAM: path (str) - The path to validate. + # @RETURN: tuple (bool, str) - (is_valid, message) + def validate_path(self, path: str) -> tuple[bool, str]: + p = os.path.abspath(path) + if not os.path.exists(p): + try: + os.makedirs(p, exist_ok=True) + except Exception as e: + return False, f"Path does not exist and could not be created: {e}" + + if not os.access(p, os.W_OK): + return False, "Path is not writable" + + return True, "Path is valid and writable" + # [/DEF:validate_path] + # [DEF:get_environments:Function] # @PURPOSE: Returns the list of configured environments. # @RETURN: List[Environment] - List of environments. @@ -131,6 +149,13 @@ class ConfigManager: return self.config.environments # [/DEF:get_environments] + # [DEF:has_environments:Function] + # @PURPOSE: Checks if at least one environment is configured. + # @RETURN: bool - True if at least one environment exists. + def has_environments(self) -> bool: + return len(self.config.environments) > 0 + # [/DEF:has_environments] + # [DEF:add_environment:Function] # @PURPOSE: Adds a new environment to the configuration. # @PRE: isinstance(env, Environment) diff --git a/backend/src/plugins/backup.py b/backend/src/plugins/backup.py index e0fdadd..60c9fe1 100755 --- a/backend/src/plugins/backup.py +++ b/backend/src/plugins/backup.py @@ -58,7 +58,7 @@ class BackupPlugin(PluginBase): "type": "string", "title": "Environment", "description": "The Superset environment to back up.", - "enum": envs if envs else ["dev", "prod"], + "enum": envs if envs else [], }, "backup_path": { "type": "string", @@ -79,6 +79,9 @@ class BackupPlugin(PluginBase): try: config_manager = get_config_manager() + if not config_manager.has_environments(): + raise ValueError("No Superset environments configured. Please add an environment in Settings.") + clients = setup_clients(logger, custom_envs=config_manager.get_environments()) client = clients.get(env) diff --git a/frontend/src/components/TaskRunner.svelte b/frontend/src/components/TaskRunner.svelte index 6fffb30..2c2dfef 100755 --- a/frontend/src/components/TaskRunner.svelte +++ b/frontend/src/components/TaskRunner.svelte @@ -20,7 +20,8 @@ if ($selectedTask) { console.log(`[TaskRunner][Entry] Connecting to logs for task: ${$selectedTask.id}`); taskLogs.set([]); // Clear previous logs - const wsUrl = `ws://localhost:8000/ws/logs/${$selectedTask.id}`; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}/ws/logs/${$selectedTask.id}`; ws = new WebSocket(wsUrl); ws.onopen = () => { diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index 3f49296..f91fe77 100755 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -5,7 +5,7 @@ import { addToast } from './toasts.js'; -const API_BASE_URL = 'http://localhost:8000'; +const API_BASE_URL = ''; // [DEF:fetchApi:Function] // @PURPOSE: Generic GET request wrapper. diff --git a/frontend/src/pages/Settings.svelte b/frontend/src/pages/Settings.svelte index 0d0cc24..c4d6cf0 100755 --- a/frontend/src/pages/Settings.svelte +++ b/frontend/src/pages/Settings.svelte @@ -133,6 +133,13 @@ None

Superset Environments

+ + {#if settings.environments.length === 0} +
+

Warning

+

No Superset environments configured. You must add at least one environment to perform backups or migrations.

+
+ {/if}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d32eba1..2d8284e 100755 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -4,4 +4,29 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' // https://vite.dev/config/ export default defineConfig({ plugins: [svelte()], + server: { + proxy: { + '/plugins': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/tasks': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/settings': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + '/ws': { + target: 'ws://localhost:8000', + ws: true, + }, + }, + }, }) diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..cff8f14 --- /dev/null +++ b/run.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +# Project Launch Script +# Automates setup and concurrent execution of backend and frontend servers. + +set -e + +# Default configuration +BACKEND_PORT=${BACKEND_PORT:-8000} +FRONTEND_PORT=${FRONTEND_PORT:-5173} +SKIP_INSTALL=false + +# Help message +show_help() { + echo "Usage: ./run.sh [options]" + echo "" + echo "Options:" + echo " --help Show this help message" + echo " --skip-install Skip dependency checks and installation" + echo "" + echo "Environment Variables:" + echo " BACKEND_PORT Port for the backend server (default: 8000)" + echo " FRONTEND_PORT Port for the frontend server (default: 5173)" +} + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --help) show_help; exit 0 ;; + --skip-install) SKIP_INSTALL=true ;; + *) echo "Unknown parameter passed: $1"; show_help; exit 1 ;; + esac + shift +done + +echo "Starting Project Launch Script..." + +# Environment validation +validate_env() { + echo "Validating environment..." + + if ! command -v python3 &> /dev/null; then + echo "Error: python3 is not installed." + exit 1 + fi + + if ! python3 -c 'import sys; exit(0) if sys.version_info >= (3, 9) else exit(1)'; then + PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') + echo "Error: python3 version 3.9 or higher is required. Found $PYTHON_VERSION" + exit 1 + fi + + if ! command -v npm &> /dev/null; then + echo "Error: npm is not installed." + exit 1 + fi + + PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') + echo "Environment validation passed (Python $PYTHON_VERSION, npm $(npm -v))" +} + +validate_env + +# Backend dependency management +setup_backend() { + if [ "$SKIP_INSTALL" = true ]; then + echo "Skipping backend installation..." + return + fi + + echo "Setting up backend..." + cd backend + if [ ! -d ".venv" ]; then + echo "Creating virtual environment..." + python3 -m venv .venv + fi + + source .venv/bin/activate + if [ -f "requirements.txt" ]; then + echo "Installing backend dependencies..." + pip install -r requirements.txt + else + echo "Warning: backend/requirements.txt not found." + fi + cd .. +} + +# Frontend dependency management +setup_frontend() { + if [ "$SKIP_INSTALL" = true ]; then + echo "Skipping frontend installation..." + return + fi + + echo "Setting up frontend..." + cd frontend + if [ ! -d "node_modules" ]; then + echo "Installing frontend dependencies..." + npm install + else + echo "frontend/node_modules already exists. Skipping npm install." + fi + cd .. +} + +setup_backend +setup_frontend + +# Cleanup function for graceful shutdown +cleanup() { + echo "" + echo "Stopping services..." + if [ -n "$BACKEND_PID" ]; then + kill $BACKEND_PID 2>/dev/null || true + fi + if [ -n "$FRONTEND_PID" ]; then + kill $FRONTEND_PID 2>/dev/null || true + fi + echo "Services stopped." + exit 0 +} + +# Trap SIGINT (Ctrl+C) +trap cleanup SIGINT + +# Start Backend +start_backend() { + echo -e "\033[0;34m[Backend]\033[0m Starting on port $BACKEND_PORT..." + cd backend + if [ -f ".venv/bin/activate" ]; then + source .venv/bin/activate + else + echo -e "\033[0;31m[Backend]\033[0m Warning: .venv/bin/activate not found. Attempting to run without venv." + fi + # Use a subshell to prefix output + python3 -m uvicorn src.app:app --reload --port "$BACKEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;34m[Backend]\033[0m ') /" & + BACKEND_PID=$! + cd .. +} + +# Start Frontend +start_frontend() { + echo -e "\033[0;32m[Frontend]\033[0m Starting on port $FRONTEND_PORT..." + cd frontend + # Use a subshell to prefix output + npm run dev -- --port "$FRONTEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;32m[Frontend]\033[0m ') /" & + FRONTEND_PID=$! + cd .. +} + +start_backend +start_frontend + +echo "Services are running. Press Ctrl+C to stop." +wait diff --git a/specs/002-app-settings/plan.md b/specs/002-app-settings/plan.md index 4e4793e..855492a 100755 --- a/specs/002-app-settings/plan.md +++ b/specs/002-app-settings/plan.md @@ -36,6 +36,7 @@ A new class `ConfigManager` in `backend/src/core/config_manager.py` will handle: - CRUD operations for environments. - Updating global settings. - Validating backup paths and Superset URLs. +- Enforcing system invariants (e.g., at least one environment configured). ### 1.3 API Endpoints diff --git a/specs/002-app-settings/spec.md b/specs/002-app-settings/spec.md index 5937d80..d2183e0 100755 --- a/specs/002-app-settings/spec.md +++ b/specs/002-app-settings/spec.md @@ -40,9 +40,9 @@ As an administrator, I want to configure the file path or storage location for b ### Edge Cases -- **Duplicate Environments**: What happens when a user tries to add an environment with a name that already exists? (System should prevent duplicates). -- **Invalid Credentials**: How does the system handle saving environments with incorrect credentials? (System should ideally validate connection on save). -- **Path Permissions**: How does the system handle a backup path that is valid but the application lacks write permissions for? (System should check write permissions). +- **Duplicate Environments**: System MUST prevent adding an environment with a name that already exists. +- **Invalid Credentials**: System MUST validate connection on save and prevent saving if credentials are invalid. +- **Path Permissions**: System MUST verify write permissions for the backup path and display an error if inaccessible. ## Requirements *(mandatory)* @@ -56,6 +56,7 @@ As an administrator, I want to configure the file path or storage location for b - **FR-006**: System MUST validate the Superset URL format before saving. - **FR-007**: System MUST verify that the Backup Storage Path is writable by the application. - **FR-008**: System MUST allow selecting a "Default" environment for operations. +- **FR-009**: System MUST enforce that at least one environment is configured before allowing Superset-related tasks (e.g., backup, migration). ### System Invariants (Constitution Check) @@ -65,7 +66,7 @@ As an administrator, I want to configure the file path or storage location for b ### Key Entities *(include if feature involves data)* - **Environment**: Represents a Superset instance. Attributes: Unique ID, Name, URL, Credentials, IsDefault flag. -- **AppConfiguration**: Singleton entity representing global settings. Attributes: BackupPath, DefaultEnvironmentID. +- **AppConfig**: Singleton entity representing global settings. Attributes: BackupPath, DefaultEnvironmentID. ## Success Criteria *(mandatory)* diff --git a/specs/002-app-settings/tasks.md b/specs/002-app-settings/tasks.md index 90a43b7..90c2621 100644 --- a/specs/002-app-settings/tasks.md +++ b/specs/002-app-settings/tasks.md @@ -85,6 +85,7 @@ description: "Task list for implementing the web application settings mechanism" - [x] T019 [P] Add password masking in `backend/src/api/routes/settings.py` and UI - [x] T020 [P] Add "Settings" link to navigation in `frontend/src/App.svelte` - [x] T021 [P] Documentation updates for settings mechanism in `docs/` +- [x] T022 [US1] Enforce INV-002 (at least one environment) in `backend/src/core/config_manager.py` and UI --- diff --git a/specs/003-project-launch-script/checklists/requirements.md b/specs/003-project-launch-script/checklists/requirements.md new file mode 100644 index 0000000..0e81d43 --- /dev/null +++ b/specs/003-project-launch-script/checklists/requirements.md @@ -0,0 +1,34 @@ +# Specification Quality Checklist: Project Launch Script + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2025-12-20 +**Feature**: [Link to spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Validated against the updated spec. diff --git a/specs/003-project-launch-script/contracts/cli.md b/specs/003-project-launch-script/contracts/cli.md new file mode 100644 index 0000000..03fe38c --- /dev/null +++ b/specs/003-project-launch-script/contracts/cli.md @@ -0,0 +1,28 @@ +# CLI Contract: Project Launch Script + +## Command + +`./run.sh [options]` + +## Arguments + +| Argument | Description | Default | +|----------|-------------|---------| +| `--help` | Show help message | N/A | +| `--skip-install` | Skip dependency checks and installation | false | + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `BACKEND_PORT` | Port for the backend server | 8000 | +| `FRONTEND_PORT` | Port for the frontend server | 5173 | + +## Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success (on graceful shutdown) | +| 1 | Missing dependencies (python/npm) | +| 2 | Installation failure | +| 130 | Terminated by SIGINT (Ctrl+C) | diff --git a/specs/003-project-launch-script/data-model.md b/specs/003-project-launch-script/data-model.md new file mode 100644 index 0000000..97a4eef --- /dev/null +++ b/specs/003-project-launch-script/data-model.md @@ -0,0 +1,26 @@ +# Data Model: Project Launch Script + +## Entities + +N/A - This feature is a utility script and does not involve persistent data storage or complex data structures. + +## Process State + +The script manages the lifecycle of two primary processes: + +1. **Backend Process**: + - Command: `python3 -m uvicorn src.app:app` + - Port: 8000 (default) + - Environment: Python Virtual Environment (`.venv`) + +2. **Frontend Process**: + - Command: `npm run dev` + - Port: 5173 (default) + - Environment: Node.js / `node_modules` + +## Validation Rules + +- `python3` must be version 3.9 or higher. +- `npm` must be available. +- `backend/requirements.txt` must exist. +- `frontend/package.json` must exist. diff --git a/specs/003-project-launch-script/plan.md b/specs/003-project-launch-script/plan.md new file mode 100644 index 0000000..ee9c0cf --- /dev/null +++ b/specs/003-project-launch-script/plan.md @@ -0,0 +1,72 @@ +# Implementation Plan: Project Launch Script + +**Branch**: `003-project-launch-script` | **Date**: 2025-12-20 | **Spec**: [specs/003-project-launch-script/spec.md](specs/003-project-launch-script/spec.md) +**Input**: Feature specification from `/specs/003-project-launch-script/spec.md` + +## Summary + +Create a root-level bash script (`run.sh`) to automate the setup and concurrent execution of the backend (FastAPI) and frontend (Svelte/Vite) development servers. The script will handle dependency checks, installation, and graceful termination of both processes. + +## Technical Context + +**Language/Version**: Python 3.9+, Node.js 18+ +**Primary Dependencies**: `uvicorn`, `npm`, `bash` +**Storage**: N/A +**Testing**: Manual verification of service availability; `pytest` for backend logic if any. +**Target Platform**: Linux +**Project Type**: Web application (frontend + backend) +**Performance Goals**: Services accessible within 30 seconds. +**Constraints**: Must handle `SIGINT` (Ctrl+C) to kill all child processes. +**Scale/Scope**: Developer utility script. + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Gate | Status | Rationale | +|------|--------|-----------| +| Python 3.9+ | PASS | Backend uses Python 3.9+ | +| Node.js 18+ | PASS | Frontend uses Node.js 18+ | +| Project Structure | PASS | Follows `backend/`, `frontend/` structure | +| Executable from root | PASS | Planned as `./run.sh` | + +**Post-Design Re-evaluation**: Design artifacts (research, data-model, contracts) confirm compliance with all project principles. No violations found. + +## Project Structure + +### Documentation (this feature) + +```text +specs/003-project-launch-script/ +├── plan.md # This file +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output +├── quickstart.md # Phase 1 output +├── contracts/ # Phase 1 output +└── tasks.md # Phase 2 output +``` + +### Source Code (repository root) + +```text +run.sh # New launch script + +backend/ +├── src/ +│ └── app.py +└── requirements.txt + +frontend/ +├── src/ +└── package.json +``` + +**Structure Decision**: Option 2: Web application. The script will reside in the root to orchestrate both directories. + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| None | - | - | diff --git a/specs/003-project-launch-script/quickstart.md b/specs/003-project-launch-script/quickstart.md new file mode 100644 index 0000000..f3b8a74 --- /dev/null +++ b/specs/003-project-launch-script/quickstart.md @@ -0,0 +1,39 @@ +# Quickstart: Project Launch Script + +## Prerequisites + +- Linux/macOS environment +- Python 3.9+ +- Node.js 18+ + +## Installation + +No installation required. The script is part of the repository. + +## Usage + +1. Navigate to the project root: + ```bash + cd ss-tools + ``` + +2. Make the script executable (if not already): + ```bash + chmod +x run.sh + ``` + +3. Run the script: + ```bash + ./run.sh + ``` + +## What it does + +1. Checks for `python3` and `npm`. +2. Sets up a Python virtual environment in `backend/.venv` if it doesn't exist. +3. Installs backend dependencies from `backend/requirements.txt`. +4. Installs frontend dependencies if `frontend/node_modules` is missing. +5. Starts the backend server on port 8000. +6. Starts the frontend server on port 5173. +7. Streams logs from both services to the terminal. +8. Gracefully stops both services when you press `Ctrl+C`. diff --git a/specs/003-project-launch-script/research.md b/specs/003-project-launch-script/research.md new file mode 100644 index 0000000..75dbf24 --- /dev/null +++ b/specs/003-project-launch-script/research.md @@ -0,0 +1,57 @@ +# Research: Project Launch Script + +## Decision: Bash Script with `trap` and Background Processes + +### Rationale +A bash script is the most portable and lightweight way to meet the requirement of a single command (`./run.sh`) without introducing additional process management dependencies like `pm2` or `concurrently` (unless we want to use `npm` to run everything, but the user asked for a script). + +### Alternatives Considered +1. **`concurrently` (NPM package)**: + - *Pros*: Easy to use, handles output well. + - *Cons*: Requires `npm install` before it can even run. The goal is a script that *handles* the install. +2. **`docker-compose`**: + - *Pros*: Perfect for multi-service orchestration. + - *Cons*: Overkill for a simple local dev environment; requires Docker to be installed and configured. +3. **Python script**: + - *Pros*: Better cross-platform support (Windows/Linux). + - *Cons*: Slightly more verbose for process management than bash on Linux. + +## Technical Findings + +### 1. Concurrent Execution & Graceful Shutdown +To run processes concurrently and handle Ctrl+C: +```bash +#!/bin/bash + +# Cleanup function +cleanup() { + echo "Stopping services..." + kill $BACKEND_PID $FRONTEND_PID + exit +} + +# Trap SIGINT (Ctrl+C) +trap cleanup SIGINT + +# Start Backend +cd backend && python3 -m uvicorn src.app:app --reload --port 8000 & +BACKEND_PID=$! + +# Start Frontend +cd frontend && npm run dev -- --port 5173 & +FRONTEND_PID=$! + +# Wait for processes +wait +``` + +### 2. Dependency Checking +- **Backend**: Check for `venv`. If missing, create it and install requirements. +- **Frontend**: Check for `frontend/node_modules`. If missing, run `npm install`. + +### 3. Environment Validation +- Check `command -v python3` and `command -v npm`. + +## Best Practices +- Use colors for logs to distinguish between Backend and Frontend output. +- Use `set -e` to exit on error during setup, but disable it or handle it carefully when starting background processes. diff --git a/specs/003-project-launch-script/spec.md b/specs/003-project-launch-script/spec.md new file mode 100644 index 0000000..7110242 --- /dev/null +++ b/specs/003-project-launch-script/spec.md @@ -0,0 +1,54 @@ +# Feature Specification: Project Launch Script + +**Feature Branch**: `003-project-launch-script` +**Created**: 2025-12-20 +**Status**: Draft +**Input**: User description: "давай создадим скрипт для запуска проекта" + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Launch Project (Priority: P1) + +As a developer, I want to launch the entire project (backend and frontend) with a single command so that I can start working quickly without manually running multiple commands in different terminals. + +**Why this priority**: This is the core functionality requested. It simplifies the development workflow. + +**Independent Test**: Can be fully tested by running the script and verifying that both backend and frontend services are accessible. + +**Acceptance Scenarios**: + +1. **Given** the project is cloned and I am in the root directory, **When** I run the launch script, **Then** the script checks for dependencies, installs them if missing, and starts both backend and frontend servers. +2. **Given** the servers are running, **When** I press Ctrl+C, **Then** both backend and frontend processes terminate gracefully. +3. **Given** dependencies are missing, **When** I run the script, **Then** it installs them before starting the servers. + +--- + +### Edge Cases + +- What happens when a port is already in use? The underlying tools (uvicorn/vite) will likely fail or complain. The script should ideally show this output. +- What happens if `python3` or `npm` is missing? The script should fail with a clear error message. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The script MUST be executable from the project root (e.g., `./run.sh`). +- **FR-002**: The script MUST check if `python3` and `npm` are available in the environment. +- **FR-003**: The script MUST check for and install backend dependencies from `backend/requirements.txt` if they are missing or outdated. +- **FR-004**: The script MUST check for and install frontend dependencies from `frontend/package.json` if `node_modules` is missing. +- **FR-005**: The script MUST start the backend application server in development mode. +- **FR-006**: The script MUST start the frontend application server in development mode. +- **FR-007**: The script MUST run both backend and frontend processes concurrently. +- **FR-008**: The script MUST handle `SIGINT` (Ctrl+C) to terminate both processes gracefully. + +### Key Entities *(include if feature involves data)* + +N/A + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Developers can start the full stack with 1 command. +- **SC-002**: Both backend and frontend services are accessible via their configured network ports within 30 seconds of running the script (assuming dependencies are installed). +- **SC-003**: 100% of child processes are terminated when the script is stopped. diff --git a/specs/003-project-launch-script/tasks.md b/specs/003-project-launch-script/tasks.md new file mode 100644 index 0000000..c51276d --- /dev/null +++ b/specs/003-project-launch-script/tasks.md @@ -0,0 +1,135 @@ +--- + +description: "Task list for Project Launch Script implementation" +--- + +# Tasks: Project Launch Script + +**Input**: Design documents from `/specs/003-project-launch-script/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ + +**Tests**: Manual verification as per spec.md. No automated test suite requested for this utility script. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Path Conventions + +- **Web app**: `backend/src/`, `frontend/src/`, `run.sh` at root + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Project initialization and basic structure + +- [x] T001 Create `run.sh` with basic structure and `--help` message in `run.sh` +- [x] T002 [P] Implement environment validation for `python3` (3.9+) and `npm` in `run.sh` + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete + +- [x] T003 Implement backend dependency check and installation logic (venv + requirements.txt) in `run.sh` +- [x] T004 Implement frontend dependency check and installation logic (node_modules) in `run.sh` +- [x] T005 Implement `SIGINT` trap and `cleanup` function for graceful shutdown in `run.sh` + +**Checkpoint**: Foundation ready - user story implementation can now begin + +--- + +## Phase 3: User Story 1 - Launch Project (Priority: P1) 🎯 MVP + +**Goal**: Launch backend and frontend concurrently with dependency management and graceful shutdown. + +**Independent Test**: Run `./run.sh` from root. Verify both services start, are accessible on their ports, and both stop when Ctrl+C is pressed. + +### Implementation for User Story 1 + +- [x] T006 [US1] Implement backend server startup logic with `BACKEND_PORT` support in `run.sh` +- [x] T007 [US1] Implement frontend server startup logic with `FRONTEND_PORT` support in `run.sh` +- [x] T008 [US1] Implement concurrent execution using background processes and `wait` in `run.sh` +- [x] T009 [US1] Implement `--skip-install` flag logic to bypass dependency checks in `run.sh` + +**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently. + +--- + +## Phase 4: Polish & Cross-Cutting Concerns + +**Purpose**: Improvements that affect multiple user stories + +- [x] T010 [P] Add color-coded logging to distinguish between backend and frontend output in `run.sh` +- [x] T011 [P] Update project `README.md` with `run.sh` usage instructions +- [x] T012 Run `quickstart.md` validation for `run.sh` + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies - can start immediately +- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories +- **User Stories (Phase 3+)**: All depend on Foundational phase completion +- **Polish (Final Phase)**: Depends on all desired user stories being complete + +### User Story Dependencies + +- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories + +### Within Each User Story + +- Core implementation before integration +- Story complete before moving to next priority + +### Parallel Opportunities + +- T002 can run in parallel with T001 (though both edit `run.sh`, they are logically independent) +- T010, T011 can run in parallel + +--- + +## Parallel Example: User Story 1 + +```bash +# Implementation tasks for User Story 1 are mostly sequential in run.sh +# but can be developed in separate blocks: +Task: "Implement backend server startup logic with BACKEND_PORT support in run.sh" +Task: "Implement frontend server startup logic with FRONTEND_PORT support in run.sh" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) +3. Complete Phase 3: User Story 1 +4. **STOP and VALIDATE**: Test User Story 1 independently +5. Deploy/demo if ready + +### Incremental Delivery + +1. Complete Setup + Foundational → Foundation ready +2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) +3. Each story adds value without breaking previous stories + +--- + +## Notes + +- [P] tasks = different files or independent logic blocks +- [Story] label maps task to specific user story for traceability +- Each user story should be independently completable and testable +- Commit after each task or logical group +- Stop at any checkpoint to validate story independently