feat: start scheduler in local development

This commit is contained in:
2026-08-22 17:25:00 +08:00
parent 35a1101c8b
commit e2decb9e01
3 changed files with 8 additions and 4 deletions

View File

@@ -1,13 +1,13 @@
# Local development without Docker Compose # Local development without Docker Compose
This Windows-only helper runs the API, analytics worker, and frontend directly from the three sibling repositories. PostgreSQL and Redis must already be reachable locally. It does not create databases, run migrations/imports, generate credentials, edit service-owned environment files, or start Docker. This Windows-only helper runs the API, analytics worker, growth-coach scheduler, and frontend directly from the three sibling repositories. PostgreSQL and Redis must already be reachable locally. It does not create databases, run migrations/imports, generate credentials, edit service-owned environment files, or start Docker.
## Prerequisites ## Prerequisites
- Go and npm are on `PATH`. - Go and npm are on `PATH`.
- PostgreSQL is reachable at `127.0.0.1:5432`. - PostgreSQL is reachable at `127.0.0.1:5432`.
- Redis is reachable at `127.0.0.1:6379`. - Redis is reachable at `127.0.0.1:6379`.
- The backend has `cmd/api` and `cmd/worker`, plus a protected backend-owned `.env`. - The backend has `cmd/api`, `cmd/worker`, and `cmd/scheduler`, plus a protected backend-owned `.env`.
- The frontend has a `package.json` `dev` script and its frontend-owned local environment file. - The frontend has a `package.json` `dev` script and its frontend-owned local environment file.
- Dependencies, migrations, the one-time importer, and demo merchant creation have already completed through their owning repositories. - Dependencies, migrations, the one-time importer, and demo merchant creation have already completed through their owning repositories.
@@ -41,6 +41,6 @@ After `start`, open the frontend URL declared by the frontend development server
The AI growth analyst is controlled by service-owned environment files; no AvalAI credential belongs in this deployment repository or in the frontend. The AI growth analyst is controlled by service-owned environment files; no AvalAI credential belongs in this deployment repository or in the frontend.
For local deterministic verification, set `AI_GROWTH_ENABLED=true` in the ignored backend `.env` and `PUBLIC_AI_GROWTH_ENABLED=true` in the ignored frontend `.env`. With `AVALAI_API_KEY` empty, the worker must not make a provider request: it persists and serves a deterministic `PARTIAL` report instead. For local verification, set `AI_COACH_ENABLED=true`, `AI_PAGE_INSIGHTS_ENABLED=true`, and `AI_GROWTH_ENABLED=true` in the ignored backend `.env`, and `PUBLIC_AI_GROWTH_ENABLED=true` in the ignored frontend `.env`. With `AVALAI_API_KEY` empty, no generation is misrepresented as a fresh AI response.
The backend `.env.example` is authoritative for the complete contract. The production operator must explicitly provide `AVALAI_API_KEY`, `AVALAI_MODEL`, `AI_DAILY_REQUEST_BUDGET`, and `AI_DAILY_TOKEN_BUDGET` before enabling the feature. `AVALAI_BASE_URL`, `AI_MAX_CONCURRENT`, `AI_CREATE_PER_MINUTE`, and `AI_UNCACHED_SCOPES_PER_DAY` remain configurable safety limits. Never place `AVALAI_API_KEY` in a public frontend variable. The backend `.env.example` is authoritative for the complete contract. The production operator must explicitly provide `AVALAI_API_KEY`, `AVALAI_MODEL`, `AI_DAILY_REQUEST_BUDGET`, and `AI_DAILY_TOKEN_BUDGET` before enabling the feature. `AVALAI_BASE_URL`, `AI_MAX_CONCURRENT`, `AI_CREATE_PER_MINUTE`, and `AI_UNCACHED_SCOPES_PER_DAY` remain configurable safety limits. Never place `AVALAI_API_KEY` in a public frontend variable.

View File

@@ -39,7 +39,7 @@ function Resolve-LocalDevCommands {
[Parameter(Mandatory)][string]$FrontendPath [Parameter(Mandatory)][string]$FrontendPath
) )
foreach ($entrypoint in 'api', 'worker') { foreach ($entrypoint in 'api', 'worker', 'scheduler') {
$path = Join-Path $BackendPath "cmd\$entrypoint" $path = Join-Path $BackendPath "cmd\$entrypoint"
if (-not (Test-Path -LiteralPath $path -PathType Container)) { if (-not (Test-Path -LiteralPath $path -PathType Container)) {
throw "Backend entrypoint cmd/$entrypoint is missing under $BackendPath" throw "Backend entrypoint cmd/$entrypoint is missing under $BackendPath"
@@ -58,6 +58,7 @@ function Resolve-LocalDevCommands {
return @{ return @{
Api = @{ FilePath = 'go'; Arguments = @('run', './cmd/api'); WorkingDirectory = $BackendPath } Api = @{ FilePath = 'go'; Arguments = @('run', './cmd/api'); WorkingDirectory = $BackendPath }
Worker = @{ FilePath = 'go'; Arguments = @('run', './cmd/worker'); WorkingDirectory = $BackendPath } Worker = @{ FilePath = 'go'; Arguments = @('run', './cmd/worker'); WorkingDirectory = $BackendPath }
Scheduler = @{ FilePath = 'go'; Arguments = @('run', './cmd/scheduler'); WorkingDirectory = $BackendPath }
Frontend = @{ FilePath = 'npm.cmd'; Arguments = @('run', 'dev', '--', '--host', '127.0.0.1'); WorkingDirectory = $FrontendPath } Frontend = @{ FilePath = 'npm.cmd'; Arguments = @('run', 'dev', '--', '--host', '127.0.0.1'); WorkingDirectory = $FrontendPath }
} }
} }
@@ -188,6 +189,7 @@ function Start-LocalDev {
try { try {
$started += Start-LocalProcess -Name 'api' -Command $commands.Api -Environment $backendEnvironment -LogDirectory $logDirectory $started += Start-LocalProcess -Name 'api' -Command $commands.Api -Environment $backendEnvironment -LogDirectory $logDirectory
$started += Start-LocalProcess -Name 'worker' -Command $commands.Worker -Environment $backendEnvironment -LogDirectory $logDirectory $started += Start-LocalProcess -Name 'worker' -Command $commands.Worker -Environment $backendEnvironment -LogDirectory $logDirectory
$started += Start-LocalProcess -Name 'scheduler' -Command $commands.Scheduler -Environment $backendEnvironment -LogDirectory $logDirectory
$started += Start-LocalProcess -Name 'frontend' -Command $commands.Frontend -Environment @{} -LogDirectory $logDirectory $started += Start-LocalProcess -Name 'frontend' -Command $commands.Frontend -Environment @{} -LogDirectory $logDirectory
$started | ConvertTo-Json | Set-Content -LiteralPath (Get-StatePath -DeploymentPath $DeploymentPath) -Encoding utf8 $started | ConvertTo-Json | Set-Content -LiteralPath (Get-StatePath -DeploymentPath $DeploymentPath) -Encoding utf8
return $started return $started

View File

@@ -55,12 +55,14 @@ try {
$frontend = Join-Path $fixture 'frontend' $frontend = Join-Path $fixture 'frontend'
New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\api') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\api') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\worker') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\worker') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\scheduler') -Force | Out-Null
New-Item -ItemType Directory -Path $frontend -Force | Out-Null New-Item -ItemType Directory -Path $frontend -Force | Out-Null
'{"scripts":{"dev":"vite"}}' | Set-Content -LiteralPath (Join-Path $frontend 'package.json') -Encoding utf8 '{"scripts":{"dev":"vite"}}' | Set-Content -LiteralPath (Join-Path $frontend 'package.json') -Encoding utf8
$commands = Resolve-LocalDevCommands -BackendPath $backend -FrontendPath $frontend $commands = Resolve-LocalDevCommands -BackendPath $backend -FrontendPath $frontend
Assert-Equal './cmd/api' $commands.Api.Arguments[1] 'API command should follow the backend contract' Assert-Equal './cmd/api' $commands.Api.Arguments[1] 'API command should follow the backend contract'
Assert-Equal './cmd/worker' $commands.Worker.Arguments[1] 'Worker command should follow the backend contract' Assert-Equal './cmd/worker' $commands.Worker.Arguments[1] 'Worker command should follow the backend contract'
Assert-Equal './cmd/scheduler' $commands.Scheduler.Arguments[1] 'Scheduler command should follow the backend contract'
Assert-Equal 'run' $commands.Frontend.Arguments[0] 'Frontend should use its checked-in dev script' Assert-Equal 'run' $commands.Frontend.Arguments[0] 'Frontend should use its checked-in dev script'
$stateDirectory = Join-Path $fixture '.local' $stateDirectory = Join-Path $fixture '.local'