feat: implement project launch script run.sh and update README
This commit is contained in:
2
.kilocode/rules/specify-rules.md
Executable file → Normal file
2
.kilocode/rules/specify-rules.md
Executable file → Normal file
@@ -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)
|
||||
|
||||
|
||||
13
README.md
13
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
|
||||
|
||||
189
backend/backend.log
Normal file
189
backend/backend.log
Normal file
@@ -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]
|
||||
35
backend/backups/Logs/superset_tool_20251220.log
Normal file
35
backend/backups/Logs/superset_tool_20251220.log
Normal file
@@ -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
|
||||
@@ -70,6 +70,25 @@ async def add_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 valid:
|
||||
return {"status": "error", "message": message}
|
||||
|
||||
if not writable:
|
||||
logger.warning(f"[validate_backup_path][Coherence:Failed] Path not writable: {p}")
|
||||
return {"status": "error", "message": "Path is not writable"}
|
||||
|
||||
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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -134,6 +134,13 @@ None
|
||||
<section class="mb-8 bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Superset Environments</h2>
|
||||
|
||||
{#if settings.environments.length === 0}
|
||||
<div class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700">
|
||||
<p class="font-bold">Warning</p>
|
||||
<p>No Superset environments configured. You must add at least one environment to perform backups or migrations.</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-6 overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
155
run.sh
Executable file
155
run.sh
Executable file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
34
specs/003-project-launch-script/checklists/requirements.md
Normal file
34
specs/003-project-launch-script/checklists/requirements.md
Normal file
@@ -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.
|
||||
28
specs/003-project-launch-script/contracts/cli.md
Normal file
28
specs/003-project-launch-script/contracts/cli.md
Normal file
@@ -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) |
|
||||
26
specs/003-project-launch-script/data-model.md
Normal file
26
specs/003-project-launch-script/data-model.md
Normal file
@@ -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.
|
||||
72
specs/003-project-launch-script/plan.md
Normal file
72
specs/003-project-launch-script/plan.md
Normal file
@@ -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 | - | - |
|
||||
39
specs/003-project-launch-script/quickstart.md
Normal file
39
specs/003-project-launch-script/quickstart.md
Normal file
@@ -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`.
|
||||
57
specs/003-project-launch-script/research.md
Normal file
57
specs/003-project-launch-script/research.md
Normal file
@@ -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.
|
||||
54
specs/003-project-launch-script/spec.md
Normal file
54
specs/003-project-launch-script/spec.md
Normal file
@@ -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.
|
||||
135
specs/003-project-launch-script/tasks.md
Normal file
135
specs/003-project-launch-script/tasks.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user