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