@@ -104,7 +104,7 @@ jobs:
104104 - name : Verify binary size
105105 shell : bash
106106 run : |
107- if [[ "${{ runner.os }}" == "macOS" ]] || [[ "${{ runner.os }}" == "macos-latest" ]] || [[ "${{ runner.os }}" == "macos-15-intel" ]] ; then
107+ if [[ "${{ runner.os }}" == "macOS" ]]; then
108108 size=$(stat -f%z "dist/${{ matrix.output }}")
109109 elif [[ "${{ runner.os }}" == "Windows" ]]; then
110110 size=$(powershell -Command "(Get-Item 'dist/${{ matrix.output }}').length")
@@ -118,88 +118,179 @@ jobs:
118118 fi
119119 echo "✓ Binary size: $size bytes"
120120
121- - name : Start binary in background
122- if : matrix.can_execute == true
121+ # Windows: use PowerShell Start-Process for reliable background process with log capture
122+ - name : Start binary (Windows)
123+ if : matrix.can_execute == true && runner.os == 'Windows'
124+ shell : pwsh
125+ run : |
126+ $proc = Start-Process -FilePath ".\dist\${{ matrix.output }}" `
127+ -ArgumentList "--no-printers" `
128+ -RedirectStandardOutput "startup.log" `
129+ -RedirectStandardError "startup-err.log" `
130+ -NoNewWindow -PassThru
131+ $proc.Id | Out-File -FilePath server.pid -Encoding ascii
132+ Write-Host "Started server with PID: $($proc.Id)"
133+
134+ # Unix: use standard background process
135+ - name : Start binary (Unix)
136+ if : matrix.can_execute == true && runner.os != 'Windows'
123137 shell : bash
124138 run : |
125- if [[ "${{ runner.os }}" == "Windows" ]]; then
126- powershell -Command "Start-Process -FilePath '.\dist\${{ matrix.output }}' -ArgumentList '--no-printers' -WindowStyle Hidden"
127- else
128- chmod +x dist/${{ matrix.output }}
129- ./dist/${{ matrix.output }} --no-printers > startup.log 2>&1 &
130- echo $! > binary.pid
131- fi
139+ chmod +x dist/${{ matrix.output }}
140+ ./dist/${{ matrix.output }} --no-printers > startup.log 2>&1 &
141+ echo $! > server.pid
132142
133- - name : Wait for startup
143+ - name : Wait for server to be ready
134144 if : matrix.can_execute == true
135145 shell : bash
136- run : sleep 15
146+ run : |
147+ max_attempts=30
148+ attempt=0
149+ until curl -sf http://localhost:3000/ > /dev/null 2>&1; do
150+ attempt=$((attempt + 1))
151+ if [ $attempt -ge $max_attempts ]; then
152+ echo "::error::Server failed to start after $max_attempts attempts (60s)"
153+ echo "--- startup.log ---"
154+ cat startup.log 2>/dev/null || echo "(no stdout log)"
155+ echo "--- startup-err.log ---"
156+ cat startup-err.log 2>/dev/null || echo "(no stderr log)"
157+ exit 1
158+ fi
159+ echo "Waiting for server... (attempt $attempt/$max_attempts)"
160+ sleep 2
161+ done
162+ echo "✓ Server is responding"
137163
138- - name : Validate startup
164+ - name : Validate startup logs
139165 if : matrix.can_execute == true
140166 shell : bash
141167 run : |
142- if [[ "${{ runner.os }}" != "Windows" ] ]; then
143- if grep -iE "\[Error\]|\[Fatal\]|exception" startup.log; then
144- echo "::error::Errors detected during startup"
168+ if [ -f startup.log ]; then
169+ if grep -iE "\[Error\]|\[Fatal\]|exception|EADDRINUSE " startup.log; then
170+ echo "::error::Errors detected in startup log "
145171 cat startup.log
146172 exit 1
147173 fi
148174
149175 if ! grep -q "\[Ready\] FlashForgeWebUI is ready" startup.log; then
150176 echo "::error::Startup did not complete - missing ready marker"
151- tail -n 50 startup.log
177+ cat startup.log
152178 exit 1
153179 fi
154- echo "✓ Startup successful"
180+ echo "✓ Startup log looks clean"
181+ else
182+ echo "::warning::No startup.log found"
183+ fi
184+
185+ - name : Test static file serving
186+ if : matrix.can_execute == true
187+ shell : bash
188+ run : |
189+ # Test index.html is served at root
190+ status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/)
191+ if [ "$status" != "200" ]; then
192+ echo "::error::GET / returned HTTP $status (expected 200)"
193+ exit 1
194+ fi
195+
196+ # Test index.html contains expected content
197+ if ! curl -sf http://localhost:3000/ | grep -q "FlashForge Web UI"; then
198+ echo "::error::GET / did not contain 'FlashForge Web UI'"
199+ curl -s http://localhost:3000/ | head -20
200+ exit 1
155201 fi
202+ echo "✓ Static file serving works"
156203
157204 - name : Test API endpoints
158205 if : matrix.can_execute == true
159206 shell : bash
160207 run : |
161- response=$(curl -s http://localhost:3000/api/auth/status)
162- if ! echo "$response" | jq '.' > /dev/null 2>&1; then
163- echo "::error::Auth status API returned invalid JSON: $response"
208+ # Auth status endpoint
209+ status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/auth/status)
210+ if [ "$status" != "200" ]; then
211+ echo "::error::GET /api/auth/status returned HTTP $status (expected 200)"
164212 exit 1
165213 fi
166214
167- if ! curl -s http://localhost:3000/ | grep -q "FlashForge Web UI"; then
168- echo "::error::Failed to serve index.html"
215+ response=$(curl -sf http://localhost:3000/api/auth/status)
216+ if ! echo "$response" | jq '.' > /dev/null 2>&1; then
217+ echo "::error::Auth status API returned invalid JSON: $response"
169218 exit 1
170219 fi
220+ echo "✓ GET /api/auth/status - valid JSON response"
171221
172- login_response=$(curl -s -X POST http://localhost:3000/api/auth/login \
222+ # Login endpoint
223+ login_response=$(curl -s -w "\n%{http_code}" -X POST http://localhost:3000/api/auth/login \
173224 -H "Content-Type: application/json" \
174225 -d '{"password":"changeme"}')
175- success=$(echo "$login_response" | jq -r '.success')
226+ login_body=$(echo "$login_response" | sed '$d')
227+ login_status=$(echo "$login_response" | tail -1)
228+
229+ if [ "$login_status" != "200" ]; then
230+ echo "::error::POST /api/auth/login returned HTTP $login_status (expected 200)"
231+ echo "Response: $login_body"
232+ exit 1
233+ fi
234+
235+ success=$(echo "$login_body" | jq -r '.success')
176236 if [ "$success" != "true" ]; then
177- echo "::error::Login API failed: $login_response"
237+ echo "::error::Login API returned success=$success (expected true)"
238+ echo "Response: $login_body"
239+ exit 1
240+ fi
241+ echo "✓ POST /api/auth/login - login successful"
242+
243+ # 404 handler for unknown API routes
244+ unknown_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/nonexistent)
245+ if [ "$unknown_status" != "404" ]; then
246+ echo "::error::GET /api/nonexistent returned HTTP $unknown_status (expected 404)"
178247 exit 1
179248 fi
249+ echo "✓ GET /api/nonexistent - returns 404"
180250
181251 echo "✓ All API tests passed"
182252
183- - name : Stop binary
184- if : matrix.can_execute == true
253+ # Windows cleanup
254+ - name : Stop binary (Windows)
255+ if : always() && matrix.can_execute == true && runner.os == 'Windows'
256+ shell : pwsh
257+ run : |
258+ if (Test-Path server.pid) {
259+ $pid = Get-Content server.pid
260+ $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue
261+ if ($proc) {
262+ Stop-Process -Id $pid -Force
263+ Write-Host "Stopped server (PID: $pid)"
264+ } else {
265+ Write-Host "Process already exited"
266+ }
267+ }
268+ # Fallback: kill by image name
269+ Get-Process -Name "flashforge-webui*" -ErrorAction SilentlyContinue | Stop-Process -Force
270+ Start-Sleep -Seconds 3
271+
272+ # Unix cleanup
273+ - name : Stop binary (Unix)
274+ if : always() && matrix.can_execute == true && runner.os != 'Windows'
185275 shell : bash
186276 run : |
187- if [[ "${{ runner.os }}" == "Windows" ]]; then
188- taskkill /F /IM "${{ matrix.output }}" 2>/dev/null || true
189- else
190- if [ -f binary.pid ]; then
191- kill -TERM $(cat binary.pid) || true
192- rm binary.pid
193- fi
194- pkill -TERM -f "${{ matrix.output }}" || true
277+ if [ -f server.pid ]; then
278+ kill -TERM $(cat server.pid) 2>/dev/null || true
279+ rm server.pid
195280 fi
281+ pkill -TERM -f "${{ matrix.output }}" 2>/dev/null || true
196282 sleep 3
197283
198284 - name : Verify cleanup
199- if : matrix.can_execute == true
285+ if : always() && matrix.can_execute == true
200286 shell : bash
201287 run : |
202- if [[ "${{ runner.os }}" != "Windows" ]]; then
288+ if [[ "${{ runner.os }}" == "Windows" ]]; then
289+ if powershell -Command "Get-Process -Name 'flashforge-webui*' -ErrorAction SilentlyContinue"; then
290+ echo "::error::Binary left zombie processes"
291+ exit 1
292+ fi
293+ else
203294 if pgrep -f "${{ matrix.output }}"; then
204295 echo "::error::Binary left zombie processes"
205296 exit 1
@@ -216,6 +307,17 @@ jobs:
216307 retention-days : 7
217308 compression-level : 6
218309
310+ - name : Upload startup logs
311+ uses : actions/upload-artifact@v4
312+ if : always() && matrix.can_execute == true
313+ with :
314+ name : logs-${{ matrix.platform }}-${{ matrix.arch }}
315+ path : |
316+ startup.log
317+ startup-err.log
318+ if-no-files-found : ignore
319+ retention-days : 7
320+
219321 - name : Generate summary
220322 if : always()
221323 shell : bash
0 commit comments