@@ -4,7 +4,8 @@ const fs = require('node:fs');
44const http = require ( 'node:http' ) ;
55const path = require ( 'node:path' ) ;
66
7- const HOST = '127.0.0.1' ;
7+ const HOST = process . env . HASH_CONTEXT_HOST || 'localhost' ;
8+ const PROBE_HOST = process . env . HASH_CONTEXT_PROBE_HOST || ( HOST === 'localhost' ? '127.0.0.1' : HOST ) ;
89const BACKEND_PORT = 8765 ;
910const FRONTEND_PORT = 5174 ;
1011const PROXY_PORT = 8787 ;
@@ -36,11 +37,11 @@ function writeLog(message) {
3637 ) ;
3738}
3839
39- function requestOk ( port , pathname = '/' ) {
40+ function requestOk ( port , pathname = '/' , hostname = HOST ) {
4041 return new Promise ( ( resolve ) => {
4142 const req = http . get (
4243 {
43- hostname : HOST ,
44+ hostname,
4445 port,
4546 path : pathname ,
4647 timeout : 1000 ,
@@ -140,33 +141,66 @@ function startControlServer() {
140141 } ) ;
141142}
142143
143- async function waitFor ( port , pathname , label , timeoutMs = 30000 ) {
144+ async function waitFor ( port , pathname , label , timeoutMs = 30000 , hostname = HOST ) {
144145 const startedAt = Date . now ( ) ;
145146
146147 while ( Date . now ( ) - startedAt < timeoutMs ) {
147- if ( await requestOk ( port , pathname ) ) {
148+ if ( await requestOk ( port , pathname , hostname ) ) {
148149 return ;
149150 }
150151 await new Promise ( ( resolve ) => setTimeout ( resolve , 300 ) ) ;
151152 }
152153
153- throw new Error ( `${ label } did not become ready on ${ HOST } :${ port } .` ) ;
154+ throw new Error ( `${ label } did not become ready on ${ hostname } :${ port } .` ) ;
154155}
155156
156- function pythonCommand ( root ) {
157+ function pythonCandidateWorks ( candidate ) {
158+ const result = spawnSync (
159+ candidate . command ,
160+ [ ...candidate . args , '-c' , 'import dotenv, zstandard' ] ,
161+ { encoding : 'utf8' , timeout : 5000 , windowsHide : true } ,
162+ ) ;
163+ return result . status === 0 ;
164+ }
165+
166+ function sourcePythonCandidates ( root ) {
157167 const localPython = process . platform === 'win32'
158168 ? path . join ( root , '.venv' , 'Scripts' , 'python.exe' )
159169 : path . join ( root , '.venv' , 'bin' , 'python' ) ;
160- const fallbackPython = process . platform === 'win32' ? 'python' : 'python3' ;
161- return fs . existsSync ( localPython ) ? localPython : fallbackPython ;
170+ const candidates = [ ] ;
171+
172+ if ( process . env . HASH_CONTEXT_PYTHON ) {
173+ candidates . push ( { command : process . env . HASH_CONTEXT_PYTHON , args : [ ] } ) ;
174+ }
175+ if ( fs . existsSync ( localPython ) ) {
176+ candidates . push ( { command : localPython , args : [ ] } ) ;
177+ }
178+ if ( process . platform === 'win32' ) {
179+ candidates . push ( { command : 'py' , args : [ '-3' ] } ) ;
180+ candidates . push ( { command : 'python' , args : [ ] } ) ;
181+ } else {
182+ candidates . push ( { command : 'python3' , args : [ ] } ) ;
183+ candidates . push ( { command : 'python' , args : [ ] } ) ;
184+ }
185+
186+ return candidates ;
187+ }
188+
189+ function pythonScriptCommand ( root , scriptName ) {
190+ for ( const candidate of sourcePythonCandidates ( root ) ) {
191+ if ( pythonCandidateWorks ( candidate ) ) {
192+ return { command : candidate . command , args : [ ...candidate . args , scriptName ] } ;
193+ }
194+ }
195+ throw new Error ( 'No usable Python runtime found. Run npm run setup:python or set HASH_CONTEXT_PYTHON to a Python with the project dependencies installed.' ) ;
162196}
163197
164198function pythonServerCommand ( root , scriptName , exeName ) {
165199 const preferSource =
166200 process . env . HASH_CONTEXT_PREFER_SOURCE_SERVERS === '1' ||
167201 ( ! app . isPackaged && process . env . HASH_CONTEXT_USE_BUNDLED_PYTHON !== '1' ) ;
168202 if ( preferSource ) {
169- return { command : pythonCommand ( root ) , args : [ scriptName ] } ;
203+ return pythonScriptCommand ( root , scriptName ) ;
170204 }
171205
172206 const candidates = process . platform === 'win32'
@@ -185,7 +219,7 @@ function pythonServerCommand(root, scriptName, exeName) {
185219 }
186220 }
187221
188- return { command : pythonCommand ( root ) , args : [ scriptName ] } ;
222+ return pythonScriptCommand ( root , scriptName ) ;
189223}
190224
191225function cleanEnv ( extra = { } ) {
@@ -200,7 +234,7 @@ function cleanEnv(extra = {}) {
200234
201235async function startBackend ( root ) {
202236 writeLog ( 'checking backend' ) ;
203- if ( await requestOk ( BACKEND_PORT , '/api/init' ) ) {
237+ if ( await requestOk ( BACKEND_PORT , '/api/health' , PROBE_HOST ) ) {
204238 writeLog ( 'backend already running' ) ;
205239 return ;
206240 }
@@ -228,13 +262,13 @@ async function startBackend(root) {
228262 writeLog ( `[backend:error] ${ chunk . toString ( ) . trim ( ) } ` ) ;
229263 } ) ;
230264
231- await waitFor ( BACKEND_PORT , '/api/init ' , 'Backend' ) ;
265+ await waitFor ( BACKEND_PORT , '/api/health ' , 'Backend' , 30000 , PROBE_HOST ) ;
232266 writeLog ( 'backend ready' ) ;
233267}
234268
235269async function startProxy ( root ) {
236270 writeLog ( 'checking proxy' ) ;
237- if ( await requestOk ( PROXY_PORT , '/api/proxy/sessions' ) ) {
271+ if ( await requestOk ( PROXY_PORT , '/api/proxy/health' , PROBE_HOST ) ) {
238272 writeLog ( 'proxy already running' ) ;
239273 return ;
240274 }
@@ -262,7 +296,7 @@ async function startProxy(root) {
262296 writeLog ( `[proxy:error] ${ chunk . toString ( ) . trim ( ) } ` ) ;
263297 } ) ;
264298
265- await waitFor ( PROXY_PORT , '/api/proxy/sessions ' , 'Proxy' ) ;
299+ await waitFor ( PROXY_PORT , '/api/proxy/health ' , 'Proxy' , 30000 , PROBE_HOST ) ;
266300 writeLog ( 'proxy ready' ) ;
267301}
268302
0 commit comments