import*as N from"http";import*as j from"net";import*as y from"path";import*as D from"fs";import{randomUUID as M}from"crypto";import*as w from"fs";import*as m from"path";import{spawn as A}from"child_process";import{parse as B}from"yaml";function O(d){let s=!1;return()=>{if(!s){s=!0;try{w.unlinkSync(d)}catch{}}}}function $(d){let s=["playwright.config.ts","playwright.config.js","playwright.config.mjs"],e=m.resolve(d);for(;;){for(let i of s){let t=m.join(e,i);if(w.existsSync(t))return t}let n=m.dirname(e);if(n===e)break;e=n}return null}function L(d,s,e,n){let i={...e},t=i.launchOptions?.args??[];return i.launchOptions={...i.launchOptions??{},args:[...t,"--remote-debugging-port=0"]},`// @generated by shiplightai \u2014 temporary debug test
import { test } from 'shiplightai/fixture';
${`
test.use(${JSON.stringify(i)});
`}
test('__shiplight_debug__', async ({ page, agent }) => {
  // Disable timeout \u2014 debugger runs until user stops it
  test.setTimeout(0);

  const { startPlaywrightDebugServer } = await import('shiplightai/debugger-pw');

  const server = await startPlaywrightDebugServer({
    yamlFilePath: ${JSON.stringify(d)},
    port: ${s},
    page,
    agent,
    projectRoot: ${JSON.stringify(n??null)},
  });

  console.error('[debugger] Playwright sandbox ready on internal port ${s}');

  // Keep alive until the server is closed externally (Ctrl+C kills the process)
  await new Promise(() => {});
});
`}async function U(d){let{createServer:s}=await import("net");for(let e=d;e<d+20;e++)if(await new Promise(i=>{let t=s();t.once("error",()=>i(!1)),t.once("listening",()=>{t.close(()=>i(!0))}),t.listen(e,"127.0.0.1")}))return e;throw new Error(`No available port found in range ${d}-${d+19}`)}async function I(d){let{yamlFilePath:s,configPath:e,tempSuffix:n="",headed:i}=d,t=m.dirname(e),o=await U(16174),r;if(!w.existsSync(s))throw new Error(`Please select a test file before starting the debug session. File not found: ${s}`);try{let l=B(w.readFileSync(s,"utf-8"));l?.use&&typeof l.use=="object"&&!Array.isArray(l.use)&&(r=l.use),l?.base_url&&!r?.baseURL&&(r={...r,baseURL:l.base_url}),l?.settings?.auto_dismiss_modal!==void 0&&(r={...r,autoDismissModal:!!l.settings.auto_dismiss_modal}),l?.settings?.browser_timezone!=null&&(r={...r,timezoneId:String(l.settings.browser_timezone)}),l?.settings?.browser_language!=null&&(r={...r,locale:String(l.settings.browser_language)}),l?.settings?.extra_http_headers!=null&&typeof l.settings.extra_http_headers=="object"&&(r={...r,extraHTTPHeaders:l.settings.extra_http_headers})}catch(l){console.error("[debugger] Could not parse YAML for `use` block:",l)}let u=m.dirname(m.resolve(s)),a=n?`-${n}`:"",g=m.join(u,`.__shiplight_debug__${a}.yaml.spec.ts`),p=L(s,o,r,t);w.writeFileSync(g,p);let h=O(g),v=["playwright","test",g,...i?["--headed"]:[]],c=A("npx",v,{stdio:["ignore","pipe","pipe"],shell:!0,cwd:t,env:{...process.env,PWDEBUG:"console",SHIPLIGHT_REGISTRY_URL:""}});c.stdout?.on("data",l=>{process.stderr.write(l)}),c.stderr?.on("data",l=>{process.stderr.write(l)});let f=()=>{c.killed||c.kill("SIGTERM")};process.on("SIGTERM",f),process.on("SIGINT",f),process.on("exit",f),c.on("close",l=>{process.removeListener("SIGTERM",f),process.removeListener("SIGINT",f),process.removeListener("exit",f),h(),l!==0&&l!==null&&console.error(`[debugger] Playwright process exited with code ${l}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let x=["127.0.0.1","::1"];async function b(l){try{let P=l.includes(":")?`[${l}]`:l,_=await fetch(`http://${P}:${o}/api/test-flow`);if(_.ok){try{await _.text()}catch{}return!0}}catch{}return!1}let S=null;for(let l=0;l<180;l++){if(c.exitCode!==null)throw h(),new Error(`Playwright process exited with code ${c.exitCode} before sandbox was ready`);for(let P of x)if(await b(P)){S=P;break}if(S){console.error(`[debugger] Playwright sandbox ready on ${S}:${o}`);break}if(l===179)throw f(),h(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(P=>setTimeout(P,1e3))}if(!S)throw f(),h(),new Error("Sandbox poll finished without a reachable host");return{port:o,host:S,pid:c.pid??0,cleanup:async()=>{f(),h()}}}var R=1e4,E=class{sessions=new Map;byYamlPath=new Map;options;spawner;headed;constructor(s={}){this.options=s,this.spawner=s.spawner??I,this.headed=s.headed??!1}log(s){this.options.onLog?this.options.onLog(s):console.error(s)}notifyStateChange(s){try{this.options.onSessionStateChange?.({...s})}catch(e){this.log(`[manager] onSessionStateChange listener threw: ${e.message}`)}}openSession(s){let e=y.resolve(s),n=this.byYamlPath.get(e);if(n){let a=this.sessions.get(n);if(a&&a.session.status!=="ended")return{...a.session};this.byYamlPath.delete(e)}if(!D.existsSync(e))throw new Error(`YAML file not found: ${e}`);if(!$(y.dirname(e)))throw new Error(`No Playwright config found for ${e} (searched parents for playwright.config.{ts,js,mjs}).`);let t=`dbg-${M().slice(0,8)}`,o=new Date().toISOString(),r={sessionId:t,yamlPath:e,innerPort:0,innerHost:"127.0.0.1",pid:0,startedAt:o,status:"idle",exitInfo:null},u={session:r,cleanup:async()=>{},readyPromise:Promise.resolve()};return this.sessions.set(t,u),this.byYamlPath.set(e,t),this.notifyStateChange(r),this.log(`[manager] session ${t} created (idle) for ${y.basename(e)}`),{...r}}async startSandbox(s){let e=this.sessions.get(s);if(!e)throw new Error(`No session ${s} to start sandbox for`);if(e.session.status==="running"||e.session.status==="starting"){e.readyPromise&&await e.readyPromise;return}if(e.session.status==="ended")throw new Error(`Session ${s} has ended`);let n=e.session.yamlPath,i=$(y.dirname(n));if(!i)throw new Error(`No Playwright config found for ${n} (searched parents for playwright.config.{ts,js,mjs}).`);e.session.status="starting",this.notifyStateChange(e.session);let t=(async()=>{let o=R+18e4,r,u=new Promise((p,h)=>{r=setTimeout(()=>h(new Error(`Timed out after ${o/1e3}s waiting for inner playwright to register on a port.`)),o)}),a;try{a=await Promise.race([this.spawner({yamlFilePath:n,configPath:i,tempSuffix:s,headed:this.headed}),u])}finally{r&&clearTimeout(r)}e.session.innerPort=a.port,e.session.innerHost=a.host,e.session.pid=a.pid,e.session.status="running",e.cleanup=a.cleanup;let g=p=>{e.session.status!=="ended"&&(e.session.status="ended",e.session.exitInfo=p,this.notifyStateChange(e.session))};e.livenessTimer=this.startLivenessProbe(s,g),this.notifyStateChange(e.session),this.log(`[manager] session ${s} running on ${e.session.innerHost}:${e.session.innerPort} (yaml=${y.basename(n)})`)})();e.readyPromise=t;try{await t}catch(o){throw e.session.status="ended",e.session.exitInfo=o.message,this.notifyStateChange(e.session),o}}async stopSandbox(s){let e=this.sessions.get(s);if(e&&e.session.status!=="idle"&&e.session.status!=="ended"){this.log(`[manager] stopSandbox ${s}`),e.livenessTimer&&(clearInterval(e.livenessTimer),e.livenessTimer=void 0);try{await e.cleanup()}catch(n){this.log(`[manager] stopSandbox ${s} cleanup error: ${n.message}`)}e.session.innerPort=0,e.session.innerHost="127.0.0.1",e.session.pid=0,e.session.status="idle",e.session.exitInfo=null,e.cleanup=async()=>{},e.readyPromise=Promise.resolve(),this.notifyStateChange(e.session)}}startLivenessProbe(s,e){let t=0,o=setInterval(()=>{let r=this.sessions.get(s);if(!r||r.session.status==="ended"){clearInterval(o);return}let u=!1;try{process.kill(r.session.pid,0),u=!0}catch(a){a?.code!=="ESRCH"&&(u=!0)}u?t=0:(t+=1,t>=3&&(clearInterval(o),e("process-exited")))},3e3);return o.unref(),o}async closeSession(s){let e=this.sessions.get(s);if(e){this.log(`[manager] closeSession ${s} (status=${e.session.status})`),this.sessions.delete(s),this.byYamlPath.get(e.session.yamlPath)===s&&this.byYamlPath.delete(e.session.yamlPath),e.livenessTimer&&(clearInterval(e.livenessTimer),e.livenessTimer=void 0),e.session.status!=="ended"&&(e.session.status="ended",e.session.exitInfo="SIGTERM",this.notifyStateChange(e.session));try{await e.cleanup()}catch(n){this.log(`[manager] closeSession ${s} cleanup error: ${n.message}`)}}}async restartInner(s){let e=this.sessions.get(s);if(!e)throw new Error(`No session ${s} to restart`);if(e.restartInProgress)throw new Error(`Restart already in progress for session ${s}`);let n=e.session.yamlPath,i=$(y.dirname(n));if(!i)throw new Error(`No Playwright config found for ${n} (searched parents for playwright.config.{ts,js,mjs}).`);e.restartInProgress=!0;try{e.livenessTimer&&(clearInterval(e.livenessTimer),e.livenessTimer=void 0);try{await e.cleanup()}catch(a){this.log(`[manager] restartInner ${s} old cleanup error: ${a.message}`)}e.session.status="starting",e.session.innerPort=0,e.session.pid=0,this.notifyStateChange(e.session);let t=R+18e4,o,r=new Promise((a,g)=>{o=setTimeout(()=>g(new Error(`Timed out after ${t/1e3}s waiting for inner playwright to respawn.`)),t)}),u;try{u=await Promise.race([this.spawner({yamlFilePath:n,configPath:i,tempSuffix:s,headed:this.headed}),r])}catch(a){throw e.session.status="ended",e.session.exitInfo=a.message,this.notifyStateChange(e.session),a}finally{o&&clearTimeout(o)}e.session.innerPort=u.port,e.session.innerHost=u.host,e.session.pid=u.pid,e.session.status="running",e.cleanup=u.cleanup,e.livenessTimer=this.startLivenessProbe(s,a=>{e.session.status!=="ended"&&(e.session.status="ended",e.session.exitInfo=a,this.notifyStateChange(e.session))}),this.notifyStateChange(e.session),this.log(`[manager] session ${s} restarted on ${e.session.innerHost}:${e.session.innerPort} (yaml=${y.basename(n)})`)}finally{e.restartInProgress=!1}}listSessions(){return Array.from(this.sessions.values()).map(s=>({...s.session}))}getSession(s){let e=this.sessions.get(s);return e?{...e.session}:void 0}httpProxyFor(s,e={}){let{liveviewUrlBuilder:n}=e;return async(i,t,o)=>{let r=this.sessions.get(s);if(!r){t.status(404).json({status:"error",message:"Session not found"});return}if(r.session.status==="ended"){t.status(410).json({status:"error",message:"Session has ended",exitInfo:r.session.exitInfo});return}if(r.session.status==="idle"){t.status(503).json({status:"error",message:"Sandbox not started"});return}let u=`${i.method} ${i.path}`;if(u==="POST /api/int-runner/create-session"){let g=await H(i),p={};if(g.length)try{p=JSON.parse(g.toString("utf-8"))}catch{t.status(400).json({status:"error",message:"Invalid JSON body"});return}p.testFilePath=r.session.yamlPath,await C(i,t,r.session.innerHost,r.session.innerPort,Buffer.from(JSON.stringify(p),"utf-8"),"application/json");return}if(u==="POST /api/int-runner/terminate-session"){try{await this.stopSandbox(s),t.json({status:"success",details:"Sandbox stopped"})}catch(g){t.status(500).json({status:"error",message:g.message})}return}if(u==="POST /api/int-runner/liveview-url"){let g=n?.(i)??"";t.json({liveviewUrl:g,browserWsUrl:""});return}let a=await H(i);await C(i,t,r.session.innerHost,r.session.innerPort,a,i.headers["content-type"])}}wsUpgradeFor(s){return async(e,n,i,t)=>{let o=this.sessions.get(s);if(!o){T(n,`HTTP/1.1 404 Not Found\r
\r
`);return}if(o.session.status==="ended"){T(n,`HTTP/1.1 410 Gone\r
\r
`);return}try{let r=o.session.innerHost.includes(":")?`[${o.session.innerHost}]`:o.session.innerHost,u=await fetch(`http://${r}:${o.session.innerPort}/api/browser-cdp`);if(!u.ok)throw new Error(`Inner /api/browser-cdp returned ${u.status}`);let{cdpUrl:a}=await u.json(),g=new URL(a.replace(/^ws/,"http")),p=parseInt(g.port||"80",10),h=g.hostname,v=g.pathname;t&&t.startsWith("/page/")&&(v=`/devtools${t}`);let c=j.createConnection(p,h);c.on("connect",()=>{let f=`GET ${v} HTTP/1.1\r
Host: ${h}:${p}\r
`;for(let[x,b]of Object.entries(e.headers)){let S=x.toLowerCase();S!=="host"&&S!=="origin"&&(f+=`${x}: ${Array.isArray(b)?b.join(", "):b}\r
`)}f+=`\r
`,c.write(f),i.length&&c.write(i),c.pipe(n),n.pipe(c)}),c.on("error",()=>{(!("destroyed"in n)||!n.destroyed)&&n.destroy()}),n.on("error",()=>{c.destroyed||c.destroy()}),n.on("close",()=>{c.destroyed||c.destroy()})}catch(r){this.log(`[manager] WS upgrade for ${s} failed: ${r.message}`),T(n,`HTTP/1.1 502 Bad Gateway\r
\r
`)}}}async shutdown(){let s=Array.from(this.sessions.keys());await Promise.allSettled(s.map(e=>this.closeSession(e)))}};function H(d){return new Promise((s,e)=>{if(d.body&&typeof d.body=="object")try{s(Buffer.from(JSON.stringify(d.body),"utf-8"));return}catch{}if(d.readableEnded){s(Buffer.alloc(0));return}let n=[];d.on("data",i=>n.push(i)),d.on("end",()=>s(Buffer.concat(n))),d.on("error",e)})}function C(d,s,e,n,i,t){return new Promise(o=>{let r=Array.isArray(t)?t[0]:t,u=N.request({hostname:e,port:n,path:d.originalUrl,method:d.method,headers:{"content-type":r||"application/json","content-length":String(i.length)},timeout:3e5},a=>{s.writeHead(a.statusCode??502,a.headers),a.on("data",g=>s.write(g)),a.on("end",()=>{s.end(),o()}),a.on("error",()=>{s.writableEnded||s.end(),o()})});u.on("error",a=>{s.headersSent?s.writableEnded||s.end():s.status(502).json({status:"error",message:"Inner server unavailable: "+a.message}),o()}),u.end(i)})}function T(d,s){try{(!("destroyed"in d)||!d.destroyed)&&(d.write(s),d.destroy())}catch{}}export{E as DebuggerManager};
