File size: 5,232 Bytes
b4143a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPythonBaseUrl = getPythonBaseUrl;
exports.startPythonBackend = startPythonBackend;
exports.stopPythonBackend = stopPythonBackend;
exports.pyGet = pyGet;
exports.pyPost = pyPost;
/**
 * Spawns the FastAPI Python backend and exposes HTTP helpers for the main process.
 * Python handles hot paths (filesystem sizes, processes, drives, system, network, env).
 */
const node_child_process_1 = require("node:child_process");
const net = __importStar(require("node:net"));
const path = __importStar(require("node:path"));
let child = null;
let baseUrl = '';
function getPythonBaseUrl() {
    return baseUrl;
}
function getFreePort() {
    return new Promise((resolve, reject) => {
        const s = net.createServer();
        s.listen(0, '127.0.0.1', () => {
            const a = s.address();
            const p = typeof a === 'object' && a ? a.port : 0;
            s.close(() => resolve(p));
        });
        s.on('error', reject);
    });
}
async function waitForHealth(port, timeoutMs) {
    const url = `http://127.0.0.1:${port}/health`;
    const t0 = Date.now();
    while (Date.now() - t0 < timeoutMs) {
        try {
            const r = await fetch(url);
            if (r.ok)
                return;
        }
        catch {
            /* retry */
        }
        await new Promise((r) => setTimeout(r, 120));
    }
    throw new Error(`Python backend did not respond at ${url}`);
}
async function startPythonBackend() {
    const port = await getFreePort();
    const backendDir = path.join(__dirname, '..', 'backend');
    const py = process.env.PYTHON_PATH ?? (process.platform === 'win32' ? 'python' : 'python3');
    child = (0, node_child_process_1.spawn)(py, ['-m', 'uvicorn', 'main:app', '--host', '127.0.0.1', '--port', String(port), '--log-level', 'warning'], {
        cwd: backendDir,
        env: { ...process.env, AUDITOR_PY_PORT: String(port) },
        stdio: ['ignore', 'pipe', 'pipe'],
        windowsHide: true,
    });
    let stderrBuf = '';
    child.stderr?.on('data', (d) => {
        stderrBuf += d.toString();
        if (stderrBuf.length > 8000)
            stderrBuf = stderrBuf.slice(-4000);
    });
    await new Promise((resolve, reject) => {
        const fail = (err) => {
            try {
                child?.kill();
            }
            catch {
                /* ignore */
            }
            reject(err);
        };
        const t = setTimeout(() => {
            fail(new Error(`Python backend startup timed out. Install: cd backend && pip install -r requirements.txt\n${stderrBuf}`));
        }, 25_000);
        child.once('error', (err) => {
            clearTimeout(t);
            fail(err instanceof Error ? err : new Error(String(err)));
        });
        waitForHealth(port, 24_000)
            .then(() => {
            clearTimeout(t);
            resolve();
        })
            .catch((e) => {
            clearTimeout(t);
            fail(e instanceof Error ? e : new Error(String(e)));
        });
    });
    baseUrl = `http://127.0.0.1:${port}`;
}
function stopPythonBackend() {
    if (child) {
        try {
            child.kill();
        }
        catch {
            /* ignore */
        }
        child = null;
    }
    baseUrl = '';
}
async function pyGet(pathname) {
    const r = await fetch(`${baseUrl}${pathname}`);
    if (!r.ok) {
        const t = await r.text();
        throw new Error(t || r.statusText);
    }
    return r.json();
}
async function pyPost(pathname, body) {
    const r = await fetch(`${baseUrl}${pathname}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
    if (!r.ok) {
        const t = await r.text();
        throw new Error(t || r.statusText);
    }
    return r.json();
}