Ctrl K
ComunidadGuías…

scripts/exe-boot-smoke.mjs

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Boot-smoke a packaged binary, not whichever service occupies the port.
 * All three executable release jobs use this entry point. Synthetic runtime
 * credentials and a fresh DATA_DIR prevent reading/writing adjacent user state.
 * A pass covers this child's health, dashboard HTML and two representative JSON
 * assets, not every bundled asset or real upstream model behaviour.
 */
import { spawn } from 'node:child_process';
import { existsSync, mkdtempSync, rmSync, chmodSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';

const PORT = Number(process.env.SMOKE_PORT || 3999);
const BOOT_TIMEOUT_MS = Number(process.env.SMOKE_BOOT_TIMEOUT_MS || 20000);
const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
const OUTPUT_LIMIT = 64 * 1024;
const BODY_LIMIT = 4 * 1024 * 1024;
function defaultBinary() {
  if (process.platform === 'win32') return 'dist-windows/windsurfapi.exe';
  return 'dist-macos/windsurfapi-macos-' + (process.arch === 'arm64' ? 'arm64' : 'x64');
}
const binary = resolve(process.argv[2] || defaultBinary());
let child = null, dataDir = null, childClosed = false, done = null;
let exitedEarly = null, output = '';
function appendOutput(bytes) { output = (output + bytes.toString()).slice(-OUTPUT_LIMIT); }
function childEnvironment() {
  // Preserve OS process-launch essentials, not the operator's account/proxy/config env.
  const env = {};
  for (const key of ['PATH','Path','SystemRoot','SYSTEMROOT','WINDIR','COMSPEC','PATHEXT',
    'HOME','USERPROFILE','APPDATA','LOCALAPPDATA','TEMP','TMP','TMPDIR','LANG','LC_ALL','TZ']) {
    if (process.env[key] != null) env[key] = process.env[key];
  }
  return { ...env, DEVIN_CONNECT:'1', HOST:'127.0.0.1', PORT:String(PORT),
    API_KEY:'ci-smoke', DASHBOARD_PASSWORD:'ci-smoke-dashboard', DATA_DIR:dataDir,
    WINDSURFAPI_SKIP_DOTENV:'1', WINDSURFAPI_NO_OPEN:'1', RELOGIN_LIVE:'0',
    DEVIN_CONNECT_AUTO_RELOGIN:'0', NO_COLOR:'1' };
}
async function cleanup() {
  if (child && !childClosed) {
    try { child.kill('SIGKILL'); } catch { /* close/error listener is authoritative */ }
    let timer;
    try {
      await Promise.race([done, new Promise((_, reject) => {
        timer = setTimeout(() => reject(new Error('cannot confirm smoke child termination')), 5000);
      })]);
    } finally { clearTimeout(timer); }
  }
  if (dataDir) rmSync(dataDir, { recursive:true, force:true });
}
async function probe(route) {
  try {
    const res = await fetch('http://127.0.0.1:' + PORT + route, {
      signal: AbortSignal.timeout(3000), redirect:'error',
    });
    const reader = res.body?.getReader();
    const chunks = []; let size = 0;
    if (reader) {
      for (;;) {
        const { done, value } = await reader.read();
        if (done) break;
        size += value.length;
        if (size > BODY_LIMIT) { await reader.cancel(); throw new Error('smoke body limit exceeded'); }
        chunks.push(Buffer.from(value));
      }
    }
    return { status:res.status, type:res.headers.get('content-type') || '', body:Buffer.concat(chunks).toString('utf8') };
  } catch { return { status:0, type:'', body:'' }; }
}
async function requireJsonAsset(route) {
  const result = await probe(route);
  let data = null;
  try { data = JSON.parse(result.body); } catch { /* rejected below */ }
  if (result.status !== 200 || !/application\/json/i.test(result.type)
      || !data || typeof data !== 'object' || !Object.keys(data).length) {
    throw new Error('missing or invalid bundled JSON asset: ' + route);
  }
  console.log('  PASS ' + route + ' JSON');
}
async function main() {
  if (!Number.isInteger(PORT) || PORT < 1 || PORT > 65535) throw new Error('invalid SMOKE_PORT');
  if (!Number.isFinite(BOOT_TIMEOUT_MS) || BOOT_TIMEOUT_MS < 1 || BOOT_TIMEOUT_MS > 300000) throw new Error('invalid SMOKE_BOOT_TIMEOUT_MS');
  if (!existsSync(binary)) throw new Error('binary not found: ' + binary);
  if (process.platform !== 'win32') { try { chmodSync(binary, 0o755); } catch {} }
  dataDir = mkdtempSync(join(tmpdir(), 'wa-smoke-'));
  console.log('boot-smoke ' + binary + ' (port ' + PORT + ')');
  child = spawn(binary, [], { env:childEnvironment(), stdio:['ignore','pipe','pipe'] });
  done = new Promise(resolveDone => child.once('close', () => { childClosed = true; resolveDone(); }));
  child.once('error', error => { exitedEarly = error.code || error.message; });
  child.once('exit', (code, signal) => { exitedEarly = signal || code; });
  child.stdout?.on('data', appendOutput); child.stderr?.on('data', appendOutput);
  const deadline = Date.now() + BOOT_TIMEOUT_MS;
  let health = { status:0 };
  while (Date.now() < deadline && exitedEarly === null) {
    health = await probe('/health');
    if (health.status === 200) break;
    await new Promise(r => setTimeout(r, 100));
  }
  if (exitedEarly !== null) throw new Error('binary exited before serving: ' + exitedEarly);
  if (health.status !== 200) throw new Error('/health did not become ready within the boot timeout');
  let identity = null;
  try { identity = JSON.parse(health.body); } catch { /* rejected below */ }
  if (!identity || identity.pid !== child.pid || identity.status !== 'ok'
      || identity.provider !== 'WindsurfAPI bydwgx1337' || identity.version !== VERSION) {
    throw new Error('/health identity/PID/version does not belong to the spawned binary');
  }
  console.log('  PASS /health identity pid=' + child.pid + ' version=' + identity.version);
  const dashboard = await probe('/dashboard');
  if (dashboard.status !== 200 || !/text\/html/i.test(dashboard.type)
      || !/<html\b/i.test(dashboard.body) || !/<body\b/i.test(dashboard.body) || dashboard.body.length < 1000) {
    throw new Error('/dashboard did not serve the bundled HTML');
  }
  console.log('  PASS /dashboard HTML');
  await requireJsonAsset('/dashboard/i18n/en.json');
  await requireJsonAsset('/dashboard/data/contributors.json');
  // Observe pending exit callbacks before approving; no claim about future uptime.
  await new Promise(r => setImmediate(r));
  if (exitedEarly !== null || childClosed) throw new Error('binary exited during smoke');
}
let code = 1;
try { await main(); code = 0; }
catch (error) { console.error('smoke failed: ' + (error?.message || error)); if (output) console.error(output.slice(-2000)); }
finally {
  try { await cleanup(); }
  catch (error) { code = 1; console.error('smoke cleanup failed: ' + error.message); }
}
if (code === 0) console.log('boot-smoke OK (owned process terminated)');
// close, unlike exit, includes draining the child stdio. Never force process.exit().
process.exitCode = code;