import { spawn, spawnSync } from 'node:child_process'; import fs from 'node:fs'; import http from 'node:http'; import net from 'node:net'; import os from 'node:os'; import path from 'node:path'; import process from 'node:process'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; const require = createRequire(import.meta.url); const { buildChildEnv } = require('../apps/desktop/sidecar.cjs'); const PYTHON_VERSION = '3.13.12'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const venvDir = path.join(repoRoot, '.sidecar-venv'); const python = path.join(venvDir, 'Scripts', 'python.exe'); const lockFile = path.join(repoRoot, 'packaging', 'requirements-sidecar.lock'); const specFile = path.join(repoRoot, 'packaging', 'aps-sidecar.spec'); const executable = path.join(repoRoot, 'dist', 'sidecar', 'aps-sidecar', 'aps-sidecar.exe'); function run(command, args, options = {}) { const completed = spawnSync(command, args, { cwd: repoRoot, env: buildChildEnv(process.env, { UV_NO_CONFIG: '1' }), stdio: 'inherit', windowsHide: true, ...options, }); if (completed.error) throw completed.error; if (completed.status !== 0) { throw new Error(`${command} ${args.join(' ')} exited with code ${completed.status}`); } } function readRuntimeIdentity() { const script = [ 'import json, platform, sys', 'print(json.dumps({', ' "implementation": platform.python_implementation(),', ' "version": platform.python_version(),', ' "machine": platform.machine(),', ' "base_executable": getattr(sys, "_base_executable", sys.executable),', '}))', ].join('\n'); const completed = spawnSync(python, ['-c', script], { cwd: repoRoot, env: buildChildEnv(process.env, {}), encoding: 'utf8', windowsHide: true, }); if (completed.status !== 0) throw new Error(completed.stderr || 'cannot inspect sidecar Python'); return JSON.parse(completed.stdout.trim()); } function validateRuntime() { const identity = readRuntimeIdentity(); if (identity.implementation !== 'CPython' || identity.version !== PYTHON_VERSION) { throw new Error(`Sidecar runtime must be CPython ${PYTHON_VERSION}; got ${JSON.stringify(identity)}`); } if (!/amd64|x86_64/i.test(identity.machine)) { throw new Error(`Sidecar runtime must be x86-64; got ${identity.machine}`); } if (/anaconda|conda/i.test(identity.base_executable)) { throw new Error(`Conda runtime is forbidden for release sidecars: ${identity.base_executable}`); } console.log(`sidecar runtime: ${identity.implementation} ${identity.version} ${identity.machine}`); } function reservePort() { return new Promise((resolve, reject) => { const server = net.createServer(); server.once('error', reject); server.listen({ host: '127.0.0.1', port: 0, exclusive: true }, () => { const address = server.address(); const port = typeof address === 'object' && address ? address.port : 0; server.close((error) => error ? reject(error) : resolve(port)); }); }); } function healthProbe(url, expectedNonce) { return new Promise((resolve, reject) => { const request = http.get(url, { timeout: 1_500, headers: { 'x-aps-sidecar-nonce': expectedNonce }, }, (response) => { response.resume(); if (response.statusCode !== 200) { reject(new Error(`health returned HTTP ${response.statusCode}`)); } else if (response.headers['x-aps-sidecar-nonce'] !== expectedNonce) { reject(new Error('health returned an unexpected sidecar identity')); } else { resolve(); } }); request.once('timeout', () => request.destroy(new Error('health probe timed out'))); request.once('error', reject); }); } function sidecarEnv(overrides) { return buildChildEnv(process.env, overrides); } async function smokeFrozenSidecar() { const port = await reservePort(); const smokeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'aps-frozen-smoke-')); const uiDir = path.join(repoRoot, 'apps', 'web', 'dist'); const nonce = 'f'.repeat(64); const child = spawn(executable, [], { cwd: path.dirname(executable), env: sidecarEnv({ APS_MODE: 'desktop', APS_HOME: smokeHome, APS_API_HOST: '127.0.0.1', APS_API_PORT: String(port), APS_UI_DIR: uiDir, APS_SIDECAR_NONCE: nonce, }), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, }); let output = ''; child.stdout.on('data', (chunk) => { output += String(chunk); }); child.stderr.on('data', (chunk) => { output += String(chunk); }); try { const deadline = Date.now() + 60_000; while (Date.now() < deadline) { if (child.exitCode !== null) { throw new Error(`frozen sidecar exited with ${child.exitCode}:\n${output}`); } try { await healthProbe(`http://127.0.0.1:${port}/api/health`, nonce); console.log(`frozen sidecar smoke passed on port ${port}`); return; } catch { await new Promise((resolve) => setTimeout(resolve, 250)); } } throw new Error(`frozen sidecar health timeout:\n${output}`); } finally { child.kill(); fs.rmSync(smokeHome, { recursive: true, force: true }); } } if (process.platform !== 'win32') { throw new Error('The current Sidecar release target is Windows x86-64.'); } run('uv', ['venv', '--managed-python', '--python', PYTHON_VERSION, '--clear', venvDir]); validateRuntime(); run('uv', [ 'pip', 'install', '--python', python, '--require-hashes', '--only-binary', ':all:', '--requirements', lockFile, ]); run(python, ['scripts/check_solver_runtime.py', '--iterations', '5']); run(python, [ '-m', 'PyInstaller', '--clean', '--noconfirm', '--distpath', path.join(repoRoot, 'dist', 'sidecar'), '--workpath', path.join(repoRoot, 'build', 'sidecar'), specFile, ]); run(executable, ['--probe-child']); await smokeFrozenSidecar();