shiplightai 0.1.74 → 0.1.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -121,6 +121,11 @@ declare class DebuggerManager {
121
121
  private readonly headed;
122
122
  constructor(options?: ManagerOptions);
123
123
  private log;
124
+ /**
125
+ * Reset a session's runtime state back to idle. Shared by stopSandbox,
126
+ * spawn-failure catch blocks, and the liveness probe.
127
+ */
128
+ private resetToIdle;
124
129
  private notifyStateChange;
125
130
  /**
126
131
  * Open a new debugger session for the given yaml path. If a session for
@@ -1,4 +1,4 @@
1
- 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(l){let s=!1;return()=>{if(!s){s=!0;try{w.unlinkSync(l)}catch{}}}}function $(l){let s=["playwright.config.ts","playwright.config.js","playwright.config.mjs"],e=m.resolve(l);for(;;){for(let r of s){let t=m.join(e,r);if(w.existsSync(t))return t}let n=m.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
1
+ 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
2
2
  import { test } from 'shiplightai/fixture';
3
3
  ${`
4
4
  test.use(${JSON.stringify(r)});
@@ -22,14 +22,14 @@ test('__shiplight_debug__', async ({ page, agent }) => {
22
22
  // Keep alive until the server is closed externally (Ctrl+C kills the process)
23
23
  await new Promise(() => {});
24
24
  });
25
- `}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=m.dirname(e),i=await U(16174),o;if(!w.existsSync(s))throw new Error(`Please select a test file before starting the debug session. File not found: ${s}`);try{let u=B(w.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=m.dirname(m.resolve(s)),a=n?`-${n}`:"",c=m.join(d,`.__shiplight_debug__${a}.yaml.spec.ts`),p=L(s,i,o,t);w.writeFileSync(c,p);let h=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),h(),u!==0&&u!==null&&console.error(`[debugger] Playwright process exited with code ${u}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let x=["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 h(),new Error(`Playwright process exited with code ${g.exitCode} before sandbox was ready`);for(let S of x)if(await b(S)){P=S;break}if(P){console.error(`[debugger] Playwright sandbox ready on ${P}:${i}`);break}if(u===179)throw f(),h(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(S=>setTimeout(S,1e3))}if(!P)throw f(),h(),new Error("Sandbox poll finished without a reachable host");return{port:i,host:P,pid:g.pid??0,cleanup:async()=>{f(),h()}}}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)}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)}`,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 ${y.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=$(y.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((p,h)=>{o=setTimeout(()=>h(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;let c=p=>{e.session.status!=="ended"&&(e.session.status="ended",e.session.exitInfo=p,this.notifyStateChange(e.session))};e.livenessTimer=this.startLivenessProbe(s,c),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(i){throw e.session.status="ended",e.session.exitInfo=i.message,this.notifyStateChange(e.session),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}`)}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,i=setInterval(()=>{let o=this.sessions.get(s);if(!o||o.session.status==="ended"){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=$(y.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 e.session.status="ended",e.session.exitInfo=a.message,this.notifyStateChange(e.session),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!=="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(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 C(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 _(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 C(r);await _(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){T(n,`HTTP/1.1 404 Not Found\r
25
+ `}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
26
26
  \r
27
- `);return}if(i.session.status==="ended"){T(n,`HTTP/1.1 410 Gone\r
27
+ `);return}if(i.session.status==="ended"){$(n,`HTTP/1.1 410 Gone\r
28
28
  \r
29
- `);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),h=c.hostname,v=c.pathname;t&&t.startsWith("/page/")&&(v=`/devtools${t}`);let g=j.createConnection(p,h);g.on("connect",()=>{let f=`GET ${v} HTTP/1.1\r
30
- Host: ${h}:${p}\r
31
- `;for(let[x,b]of Object.entries(e.headers)){let P=x.toLowerCase();P!=="host"&&P!=="origin"&&(f+=`${x}: ${Array.isArray(b)?b.join(", "):b}\r
29
+ `);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
30
+ Host: ${w}:${p}\r
31
+ `;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
32
32
  `)}f+=`\r
33
- `,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}`),T(n,`HTTP/1.1 502 Bad Gateway\r
33
+ `,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
34
34
  \r
35
- `)}}}async shutdown(){let s=Array.from(this.sessions.keys());await Promise.allSettled(s.map(e=>this.closeSession(e)))}};function C(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 _(l,s,e,n,r,t){return new Promise(i=>{let o=Array.isArray(t)?t[0]:t,d=N.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 T(l,s){try{(!("destroyed"in l)||!l.destroyed)&&(l.write(s),l.destroy())}catch{}}export{E as DebuggerManager};
35
+ `)}}}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};
package/dist/index.js CHANGED
@@ -4365,7 +4365,7 @@ ${m.join(`
4365
4365
  `)}function rc(e,t){let i=[],a=t?.version||"unknown";i.push(`// @generated by shiplightai v${a}`),i.push(...co()),i.push(""),t?.use&&Object.keys(t.use).length>0&&(i.push(`test.use(${JSON.stringify(t.use,null,2)});`),i.push(""));let n=new Set,o={imports:n,actionEntityStore:t?.actionEntityStore},r=t?.testName||"Test Suite",s=Cn(t?.tags);i.push(`test.describe.serial('${s}${ue(r)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(i.push(...fi("beforeAll",e.beforeAll,o,1)),i.push("")),e.beforeEach&&e.beforeEach.length>0&&(i.push(...fi("beforeEach",e.beforeEach,o,1)),i.push(""));for(let c=0;c<e.tests.length;c++){let d=e.tests[c],h=d.timeout||d.skip!==void 0||d.fail!==void 0||d.only||d.slow?{timeout:d.timeout,skip:d.skip,fail:d.fail,only:d.only,slow:d.slow}:void 0;if(d.parameters&&d.parameters.length>0)for(let m of d.parameters)i.push(...yi(d.testFlow,`${ue(d.name)} [${ue(m.name)}]`,o,1,h,m.name,m.values)),i.push("");else i.push(...yi(d.testFlow,ue(d.name),o,1,h)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&i.push("")}return e.afterEach&&e.afterEach.length>0&&(i.push(...fi("afterEach",e.afterEach,o,1)),i.push("")),e.afterAll&&e.afterAll.length>0&&i.push(...fi("afterAll",e.afterAll,o,1)),i.push("});"),uo(i,n),i.join(`
4366
4366
  `)}function Cn(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}var sc=["testContext","request"];function bi(e){let t=new Set;function i(a){for(let n of a)switch(n.type){case ce.ACTION:{let r=n.action_entity?.action_data?.kwargs;if(r?.args&&Array.isArray(r.args))for(let s of r.args)typeof s=="string"&&sc.includes(s)&&t.add(s);break}case ce.STEP:i(n.statements);break;case ce.IF_ELSE:{let o=n;i(o.then),o.else&&i(o.else);break}case ce.WHILE_LOOP:i(n.body);break}}return i(e),t}function lc(e){let t=bi(e.statements??[]);if(e.teardown)for(let i of bi(e.teardown))t.add(i);return t}function Dn(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function yi(e,t,i,a=0,n,o,r){let s=" ".repeat(a),l=[],c=lc(e),d=Dn(c),h=n?.only?"test.only":"test",m=o?`, { tag: '@${ue(o)}' }`:"";if(l.push(`${s}${h}('${t}'${m}, async (${d}) => {`),n?.skip===!0?l.push(`${s} test.skip();`):typeof n?.skip=="string"&&l.push(`${s} test.skip(true, '${ue(n.skip)}');`),n?.fail===!0?l.push(`${s} test.fail();`):typeof n?.fail=="string"&&l.push(`${s} test.fail(true, '${ue(n.fail)}');`),n?.slow&&l.push(`${s} test.slow();`),n?.timeout&&l.push(`${s} test.setTimeout(${n.timeout});`),r){for(let[y,b]of Object.entries(r))l.push(`${s} agent.agentServices.saveVariable(${JSON.stringify(y)}, ${JSON.stringify(b)});`);l.push("")}let g=e.teardown&&e.teardown.length>0,f=a+1;if(g){if(l.push(`${s} try {`),e.statements&&e.statements.length>0){l.push(`${s} // Test steps`);let b=he(e.statements,f+1,i);l.push(...b)}l.push(`${s} } finally {`),l.push(`${s} // Teardown`);let y=he(e.teardown,f+1,i,"teardown");l.push(...y),l.push(`${s} }`)}else if(e.statements&&e.statements.length>0){l.push(`${s} // Test steps`);let y=he(e.statements,f,i);l.push(...y)}return l.push(`${s}});`),l}function oo(e,t,i){let a=[],n=lo(t),o=bi(n),r=Dn(o);return a.push(`test.${e}(async (${r}) => {`),a.push(...he(n,1,i,e)),a.push("});"),a}function fi(e,t,i,a){let n=" ".repeat(a),o=[],r=lo(t);if(e==="beforeAll"||e==="afterAll"){let l={...i,noAgent:!0};o.push(`${n}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${n} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...he(r,a+1,l,e)),o.push(`${n} await page.close();`),o.push(`${n}});`)}else{let l=bi(r),c=Dn(l);o.push(`${n}test.${e}(async (${c}) => {`),o.push(...he(r,a+1,i,e)),o.push(`${n}});`)}return o}function lo(e){let i=Kl({goal:"_hook",statements:e});return xe(i).statements??[]}function co(){return["import { test, expect } from 'shiplightai/fixture';"]}function uo(e,t){if(t.size>0){let i=0;for(let n=0;n<e.length;n++)e[n].startsWith("import ")&&(i=n+1);let a=Array.from(t);e.splice(i,0,...a)}}var so=5;function po(e,t,i){let a={expandingPaths:new Set([On(t)]),depth:0,referencedPaths:new Set,basePath:i},n={...e};Array.isArray(n.statements)&&(n.statements=Oe(n.statements,t,a)),Array.isArray(n.teardown)&&(n.teardown=Oe(n.teardown,t,a));for(let o of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(n[o])&&(n[o]=Oe(n[o],t,a));return{doc:n,referencedTemplatePaths:Array.from(a.referencedPaths)}}function Oe(e,t,i){let a=[];for(let n of e)if(gc(n)){let o=fc(n,t,i);a.push(o)}else a.push(wc(n,t,i));return a}function gc(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function fc(e,t,i){if(i.depth>=so)throw new Error(`Template expansion exceeded maximum depth of ${so}. Check for deeply nested or circular template references.`);let a=On(pc(t),e.template),n=!hc(a)&&i.basePath?On(i.basePath,e.template):a;if(i.expandingPaths.has(n))throw new Error(`Circular template reference detected: ${n} is already being expanded. Stack: ${Array.from(i.expandingPaths).join(" \u2192 ")} \u2192 ${n}`);i.referencedPaths.add(n);let o;try{o=uc(n,"utf-8")}catch(f){throw new Error(`Failed to read template file: ${n} (referenced from ${t}): ${f.message}`)}let r=ro(o);if(!r||typeof r!="object")throw new Error(`Invalid template file: ${n} \u2014 expected a YAML object`);let s=r.params||[],l=e.params||{};for(let f of s)if(!(f in l))throw new Error(`Template ${e.template} requires param "${f}" but it was not provided. Required params: [${s.join(", ")}]`);let c=r.statements;if(!Array.isArray(c))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(l).length>0){let y=mc(c);for(let[b,p]of Object.entries(l))y=y.split(`<<${b}>>`).join(String(p));c=ro(y)}let d={expandingPaths:new Set([...i.expandingPaths,n]),depth:i.depth+1,referencedPaths:i.referencedPaths},h=Oe(c,n,d),g={STEP:r.name||e.template.replace(/\.yaml$/,"").split("/").pop()||e.template,template_path:e.template,statements:h};return Object.keys(l).length>0&&(g.template_params=l),g}function wc(e,t,i){if(typeof e!="object"||e===null)return e;let a={...e};return Array.isArray(a.statements)&&(a.statements=Oe(a.statements,t,i)),Array.isArray(a.THEN)&&(a.THEN=Oe(a.THEN,t,i)),Array.isArray(a.ELSE)&&(a.ELSE=Oe(a.ELSE,t,i)),Array.isArray(a.DO)&&(a.DO=Oe(a.DO,t,i)),a}var Rn=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}};function Fn(e,t,i){let a=dc(e),n=a?.name,o=a?.tags,r=a?.use;if(a&&(a.name!==void 0||a.tags!==void 0||a.use!==void 0)&&(delete a.name,delete a.tags,delete a.use),a?.suite){if(a.goal||a.statements)throw new Rn('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return yc(a,n,o,r,t,i)}return bc(a,n,o,r,t,i)}function bc(e,t,i,a,n,o){let r=e?.beforeEach,s=e?.afterEach,l=mo(e?.parameters),c=e?.timeout,d=e?.skip,h=e?.fail,m=e?.only,g=e?.slow;if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow),e?.url)throw new Rn(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let f=[];if(n&&e&&typeof e=="object"){let p=po(e,n,o);e=p.doc,f=p.referencedTemplatePaths}let y=ho(e),b=xe(y);return n&&(ze(b.statements??[],n,"main"),b.teardown&&ze(b.teardown,n,"teardown")),{testFlow:b,name:t,tags:i,use:a,beforeEach:r,afterEach:s,parameters:l,timeout:c,skip:d,fail:h,only:m,slow:g,referencedTemplatePaths:f}}function yc(e,t,i,a,n,o){let r=e.suite;if(!Array.isArray(r.tests)||r.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let s=r.beforeAll,l=r.afterAll,c=r.beforeEach,d=r.afterEach,h=[],m=r.tests.map(y=>{if(!y.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(y.statements)||y.statements.length===0)throw new Error(`Suite test "${y.name}" must have a non-empty "statements" array.`);let b={goal:y.name,statements:y.statements};y.teardown&&(b.teardown=y.teardown);let p=[],x=b;if(n&&typeof b=="object"){let M=po(b,n,o);x=M.doc,p=M.referencedTemplatePaths,h.push(...p)}let S=ho(x),E=xe(S),A=mo(y.parameters);return{testFlow:E,name:y.name,tags:Array.isArray(y.tags)?y.tags:void 0,parameters:A,timeout:y.timeout,skip:y.skip,fail:y.fail,only:y.only,slow:y.slow}}),g=r.base_url,f=g?{...a,baseURL:g}:a;return{suite:{beforeAll:s,afterAll:l,beforeEach:c,afterEach:d,tests:m},name:t,tags:i,use:f,referencedTemplatePaths:h}}function mo(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,i)=>{if(!t.name)throw new Error(`Parameter set at index ${i} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function ze(e,t,i){for(let a=0;a<e.length;a++){let n=e[a],o=`${i}.${a}`,r=n.description||"";if(n.uid=xc(t,o,r),n.type===ce.STEP)ze(n.statements,t,o);else if(n.type===ce.IF_ELSE){let s=n;ze(s.then,t,`${o}.then`),s.else&&ze(s.else,t,`${o}.else`)}else n.type===ce.WHILE_LOOP&&ze(n.body,t,`${o}.body`)}}function xc(e,t,i){let a=cc("sha256").update(`${e}:${t}:${i}`).digest("hex");return`${a.slice(0,8)}-${a.slice(8,12)}-${a.slice(12,16)}-${a.slice(16,20)}-${a.slice(20,32)}`}function go(e,t,i){let a=/\btemplate:\s/.test(e),n=/^suite:/m.test(e),o=a||n?null:Mn(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let r,s,l=[];try{let c=i?.parsed??Fn(e,t,i?.basePath);l=c.referencedTemplatePaths;let d={version:i?.version,actionEntityStore:i?.actionEntityStore},h=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?r=rc(c.suite,{...d,testName:c.name,tags:c.tags,use:c.use}):r=oc(c.testFlow,{...d,testName:c.name,tags:c.tags,use:h,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let m=r.split(`
4367
4367
  `).filter(g=>!g.startsWith("import ")).join(`
4368
- `);new Function(m),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),_c(kc(s),{recursive:!0}),vc(s,r)}catch(c){let d=c instanceof Rn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var Hn="0.1.74";function fo(e){try{return Sc(e).mtimeMs}catch{return 0}}var Ac=`// @generated by shiplightai v${Hn}`;function $c(e,t){if(!bo(e)||wo(e,"utf-8").split(`
4368
+ `);new Function(m),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),_c(kc(s),{recursive:!0}),vc(s,r)}catch(c){let d=c instanceof Rn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var Hn="0.1.75";function fo(e){try{return Sc(e).mtimeMs}catch{return 0}}var Ac=`// @generated by shiplightai v${Hn}`;function $c(e,t){if(!bo(e)||wo(e,"utf-8").split(`
4369
4369
  `,1)[0]!==Ac)return!1;let a=fo(e);for(let n of t)if(fo(n)>a)return!1;return!0}function Mc(e){let t=process.argv.slice(2),i=[],a=Wn(e);for(let n of t){if(n.startsWith("-"))continue;let o=n.endsWith(".yaml.spec.ts")?n.replace(/\.yaml\.spec\.ts$/,".test.yaml"):n;if(!o.endsWith(".test.yaml"))continue;let r=Wn(e,o);bo(r)&&i.push(r.startsWith(a)?r.slice(a.length+1):o)}return i.length>0?i:null}function yo(e){let t=Mc(e.cwd),i=t??Tc("**/*.test.yaml",{cwd:e.cwd,ignore:["**/node_modules/**"]}),a=[];for(let n of i){let o=Wn(e.cwd,n),r=o.replace(/\.test\.yaml$/,".yaml.spec.ts"),s=wo(o,"utf-8");try{let l=Fn(s,o,e.projectRoot??e.cwd),c=Ec(e.cwd,o),d=e.actionEntityStores?.get(c)??e.actionEntityStores?.get("*");if(!(d&&Object.keys(d.entries).length>0)&&$c(r,[o,...l.referencedTemplatePaths]))continue;let m=go(s,o,{version:Hn,actionEntityStore:d,parsed:l});if(!m.valid)throw new Error(m.errors.join("; "))}catch(l){console.error(`[shiplight] Failed to transpile ${n}:`,l),a.push({file:n,error:l})}}if(a.length>0){let n=`[shiplight] Transpilation failed for ${a.length} file(s):
4370
4370
  `+a.map(o=>` - ${o.file}`).join(`
4371
4371
  `);if(t)throw new Error(n);console.warn(n+" (skipped)")}}import*as ae from"fs";import*as pt from"path";import{globSync as Wc}from"glob";_i();function To(e){let t=vi().SHIPLIGHT_API_TOKEN;return process.env.CI&&t?new Kn:new jn(e)}var Uc=".shiplight/action-cache";function Eo(e){return e.replace(/\//g,"__")+".json"}function Bc(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var jn=class{constructor(t){this.cwd=t;this.cacheDir=pt.join(t,Uc)}isCloud=!1;cacheDir;async lookup(t){let i=new Map;if(t.length===0||!ae.existsSync(this.cacheDir))return i;for(let a of t){let n=pt.join(this.cacheDir,Eo(a));try{if(ae.existsSync(n)){let o=ae.readFileSync(n,"utf-8");i.set(a,JSON.parse(o))}}catch{}}return i}async update(t){if(t.size===0)return 0;ae.mkdirSync(this.cacheDir,{recursive:!0});let i=0;for(let[a,n]of t)try{let o=pt.join(this.cacheDir,Eo(a)),r=In();if(ae.existsSync(o))try{r=JSON.parse(ae.readFileSync(o,"utf-8"))}catch{}let s={...r,entries:{...r.entries,...n.entries}};ae.writeFileSync(o,JSON.stringify(s,null,2)),i++}catch{}return i}loadAll(){if(!ae.existsSync(this.cacheDir))return;let t=Wc("*.json",{cwd:this.cacheDir});if(t.length===0)return;let i=new Map,a=0;for(let n of t)try{let o=ae.readFileSync(pt.join(this.cacheDir,n),"utf-8"),r=JSON.parse(o),s=Bc(n);i.set(s,r),a+=Object.keys(r.entries??{}).length}catch{}if(i.size!==0)return console.log(`[shiplight] Cache: loaded ${a} cached action entit${a===1?"y":"ies"} for ${i.size} test file${i.size!==1?"s":""}`),i}},Kn=class{isCloud=!0;async lookup(t){let{lookupActionStores:i}=await Promise.resolve().then(()=>(Gn(),Bn));return i(t)}async update(t){let{updateActionStores:i}=await Promise.resolve().then(()=>(Gn(),Bn));return i(t)}loadAll(){}};_i();function Kc(e={}){e.dotenv!==!1&&Xc(e.scanDir||process.cwd());let t=e.scanDir||process.cwd(),a=To(t).loadAll();yo({cwd:t,projectRoot:process.cwd(),actionEntityStores:a});let n=process.env.SHIPLIGHT_REPORT_DIR,o=process.env.SHIPLIGHT_RUN_ID??zc();return process.env.SHIPLIGHT_RUN_ID=o,n?{outputDir:`test-results/${o}`,reporter:[["list"],["shiplightai/reporter",{outputFolder:n,open:"never"}]]}:{outputDir:`test-results/${o}`,reporter:[["list"],["shiplightai/reporter",{outputFolder:`shiplight-report/${o}`,latestSymlinkDir:"shiplight-report",open:"never"}]]}}function zc(){let e=new Date,t=(i,a=2)=>String(i).padStart(a,"0");return`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}-${t(e.getMinutes())}-${t(e.getSeconds())}-${t(e.getMilliseconds(),3)}`}function Vc(e,...t){return jc(e,...t)}function Xc(e){vo(e);for(let t of Un(e))Gc.config({path:t})}R();R();Pt();qe();import{z as Xs}from"zod";var ph=Xs.object({instruction:Xs.string().describe('The instruction of the operation to perform. Can only include one operation. Do not inlcude element indexes just describe the element, e.g. "select the text "Hello, world!" in "Hello, world!""')});function Ys(e){e.register({name:"perform_accurate_operation",description:"Perform an operation that requires accurate interaction like dragging or interacting with a specific area of an element. Only use this action when neccecary.",schema:ph,usesElementIndex:!1,async execute(t,i){let{instruction:a}=t,n={page:i.page,agentServices:i.agentServices,domService:i.domService},o=await Je(a,n,{});if(o.status==="error"||!o.actionEntity)return{success:!1,actionEntity:{action_description:a,action_data:{action_name:"perform_accurate_operation",kwargs:{instruction:a}}},error:o.error||"Failed to generate action"};let{actionEntity:r}=o,s=await Mt(r,n);return{success:s.success,actionEntity:r,message:s.success?`Successfully executed action: ${r.action_data?.action_name}`:void 0,error:s.error}}})}Se();R();import{generateText as mh}from"ai";import{convert as gh}from"html-to-text";async function fh(e,t,i){let{apiKey:a,domain:n}=e;if(!a||!n)throw new Error("Mailgun configuration missing. Please provide apiKey and domain");let o=[];try{let r=`https://api.mailgun.net/v3/${n}/events`,s={event:"accepted",limit:"10",ascending:"yes",recipient:t};if(i.from_email&&(s.from=i.from_email),i.since)s.begin=Math.floor(i.since/1e3).toString();else{let d=new Date(Date.now()-6e5);s.begin=Math.floor(d.getTime()/1e3).toString()}u.info(`Mailgun params: ${JSON.stringify(s)}`);let l=await fetch(r+"?"+new URLSearchParams(s),{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`}});if(!l.ok){let d=await l.text();throw new Error(`Mailgun events API error: ${d}`)}let c=(await l.json()).items||[];for(let d of c.slice(0,10)){if(d.event!=="accepted")continue;let h=(d.storage||{}).url;if(u.info(`message_url: ${h}`),!h){let f=(d.message||{}).headers||{},y=f.subject||"",b=f.from||"",p=f.to||"";if(i.from_email&&!b.toLowerCase().includes(i.from_email.toLowerCase())||i.to_email&&!p.toLowerCase().includes(i.to_email.toLowerCase())||i.subject&&!y.toLowerCase().includes(i.subject.toLowerCase()))continue;o.push({subject:y,from:b,to:p,date:new Date(d.timestamp*1e3).toUTCString(),body:"Message body not available (Mailgun storage disabled)",message_id:f["message-id"]||""});continue}let m=h.split("/"),g=m[m.length-1];if(u.info(`Storage key: ${g}`),g){let f=`https://api.mailgun.net/v3/domains/${n}/messages/${g}`;try{let y=await fetch(f,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`,Accept:"application/json"}});if(y.ok){let b=await y.json(),p=b.Subject||"",x=b.From||"",S=b.To||"",E=b.Date||"",A=b["Message-Id"]||"";u.info(`subject: ${p}`),u.info(`from_addr: ${x}`),u.info(`to_addr: ${S}`),u.info(`date: ${E}`),u.info(`message_id: ${A}`);let M=b["body-html"]||b["body-plain"]||"";if(M&&M.includes("<")&&(M=gh(M)),u.info(`Body: ${M.substring(0,200)}...`),i.subject&&!p.toLowerCase().includes(i.subject.toLowerCase())||i.body_contains&&!M.toLowerCase().includes(i.body_contains.toLowerCase()))continue;o.push({subject:p,from:x,to:S,date:E,body:M,message_id:A});continue}else u.warn(`Messages API returned ${y.status}`)}catch(y){u.warn(`Failed to parse JSON response: ${y}`)}}try{let f=await fetch(h,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`}});if(!f.ok){u.warn(`Could not fetch stored message: ${f.status}`);continue}let y=await f.text();u.info(`Fallback: Raw email length: ${y.length}`);let b=y.split(`
package/dist/reporter.js CHANGED
@@ -883,9 +883,9 @@ import*as x from"fs";import*as w from"path";import{v4 as At}from"uuid";import{z
883
883
  });
884
884
  </script>
885
885
  </body>
886
- </html>`}var $e="0.1.74",Ee=$e!=="dev"?$e:void 0;var kn=3600*1e3,Tn=10080*60*1e3;var gt={before:0,main:1,teardown:2,after:3};function Me(e){let t=e.split(".")[0];return gt[t]??1}function Pe(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function yt(e){return[...e].sort(([t],[r])=>{let i=Me(t),n=Me(r);if(i!==n)return i-n;let a=Pe(t),o=Pe(r);for(let p=0;p<Math.max(a.length,o.length);p++){let h=a[p]??-1,l=o[p]??-1;if(h!==l)return h-l}return 0})}function bt(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function _t(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function Z(e,t="main",r,i,n){r===void 0&&(r=!bt(e).has("test.step")),n||(n=new Map);let a=[];for(let o of e){if(o.category==="fixture"||o.category==="test.attach")continue;if(o.category==="hook"){let h=_t(o.title,t);a.push(...Z(o.steps,h,r,i,n));continue}if(o.category==="test.step"||r&&(o.category==="expect"||o.category==="pw:api")){let h=n.get(t)??0;n.set(t,h+1);let l=`${t}.${h}`,g={stepId:l,description:o.title,status:o.error?"failure":o.duration===-1?"skipped":"success",duration:o.duration>=0?o.duration:void 0};o.error&&(g.error=o.error.message??o.error.stack),i&&o.location&&i.set(l,o.location),a.push(g),o.steps.length>0&&a.push(...Z(o.steps,l,r,i,n))}}return a}function wt(e,t){let r=w.isAbsolute(e)?e:w.join(process.cwd(),e),i=w.join(r,"latest");try{if(x.lstatSync(i).isSymbolicLink())x.unlinkSync(i);else{console.warn("[report] 'latest' exists and is not a symlink; skipping update");return}}catch{}try{process.platform==="win32"?x.symlinkSync(w.join(r,t),i,"junction"):x.symlinkSync(t,i,"dir")}catch(n){console.warn(`[report] Could not create 'latest' symlink: ${n instanceof Error?n.message:String(n)}`)}}var Q=class{outputFolder;openMode;latestSymlinkDir;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure",this.latestSymlinkDir=t.latestSymlinkDir}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let l of this.collected){let g=l.test.titlePath().join(" > "),b=r.get(g);b||(b=[],r.set(g,b)),b.push(l)}let i=[];for(let[,l]of r.entries()){let g=l[0].test.location.file,b=[],c,u,m;for(let y=0;y<l.length;y++){let{test:v,result:T}=l[y],A=await this.buildReportTest(v,T,g);c=A,u||(u=A.startTime),m=A.endTime,b.push({attemptNumber:y+1,status:T.status,duration:T.duration,steps:A.steps,error:A.error,videoPath:A.videoPath,tracePath:A.tracePath})}let d=b[b.length-1],{test:S}=l[l.length-1],_={title:S.title,baseTitle:c?.baseTitle,file:w.relative(process.cwd(),g),status:d.status,duration:d.duration,steps:d.steps,error:d.error,videoPath:d.videoPath,tracePath:d.tracePath,actionStepsMap:c?.actionStepsMap,tags:c?.tags,baseUrl:c?.baseUrl,skip:c?.skip,slow:c?.slow,timeout:c?.timeout,parameterSetName:c?.parameterSetName,startTime:u,endTime:m,suiteName:c?.suiteName};b.length>1&&(_.retries=b.length-1,_.attempts=b,b.some(v=>v.status==="failed"||v.status==="timedOut")&&d.status==="passed"&&(_.flaky=!0)),i.push(_)}let n={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString(),shiplightVersion:Ee},a=w.isAbsolute(this.outputFolder)?this.outputFolder:w.join(process.cwd(),this.outputFolder);x.mkdirSync(a,{recursive:!0});let o=w.join(a,"screenshots");for(let l=0;l<n.tests.length;l++){let g=n.tests[l],b=g.attempts&&g.attempts.length>0,c=[{obj:g,prefix:b?`test-${l}-attempt-0`:`test-${l}`,screenshotSubDir:`test-${l}`}];if(g.attempts)for(let u=0;u<g.attempts.length;u++)c.push({obj:g.attempts[u],prefix:`test-${l}-attempt-${u+1}`,screenshotSubDir:`test-${l}/attempt-${u}`});for(let{obj:u,prefix:m,screenshotSubDir:d}of c){let S=w.join(o,d),_=!1;for(let y of u.steps)if(y.screenshot&&w.isAbsolute(y.screenshot))try{_||(x.mkdirSync(S,{recursive:!0}),_=!0);let v=`${y.stepId.replace(/\./g,"-")}.png`;x.copyFileSync(y.screenshot,w.join(S,v)),y.screenshot=`screenshots/${d}/${v}`}catch(v){console.warn(`[reporter] Failed to copy screenshot for ${y.stepId}:`,v)}if(u.videoPath&&w.isAbsolute(u.videoPath)){let y=w.extname(u.videoPath)||".webm",v=`${m}-video${y}`;try{x.copyFileSync(u.videoPath,w.join(a,v)),u.videoPath=v}catch{u.videoPath=void 0}}if(u.tracePath&&w.isAbsolute(u.tracePath)){let y=w.extname(u.tracePath)||".zip",v=`${m}-trace${y}`;try{x.copyFileSync(u.tracePath,w.join(a,v)),u.tracePath=v}catch{u.tracePath=void 0}}}}let p=w.join(a,"report-data.json");x.writeFileSync(p,JSON.stringify(n,null,2),"utf-8");let h=w.join(a,"index.html");if(x.writeFileSync(h,Ae({...n,outputDir:a}),"utf-8"),console.log(`
886
+ </html>`}var $e="0.1.75",Ee=$e!=="dev"?$e:void 0;var kn=3600*1e3,Tn=10080*60*1e3;var gt={before:0,main:1,teardown:2,after:3};function Me(e){let t=e.split(".")[0];return gt[t]??1}function Pe(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function yt(e){return[...e].sort(([t],[r])=>{let i=Me(t),n=Me(r);if(i!==n)return i-n;let a=Pe(t),o=Pe(r);for(let p=0;p<Math.max(a.length,o.length);p++){let h=a[p]??-1,l=o[p]??-1;if(h!==l)return h-l}return 0})}function bt(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function _t(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function Z(e,t="main",r,i,n){r===void 0&&(r=!bt(e).has("test.step")),n||(n=new Map);let a=[];for(let o of e){if(o.category==="fixture"||o.category==="test.attach")continue;if(o.category==="hook"){let h=_t(o.title,t);a.push(...Z(o.steps,h,r,i,n));continue}if(o.category==="test.step"||r&&(o.category==="expect"||o.category==="pw:api")){let h=n.get(t)??0;n.set(t,h+1);let l=`${t}.${h}`,g={stepId:l,description:o.title,status:o.error?"failure":o.duration===-1?"skipped":"success",duration:o.duration>=0?o.duration:void 0};o.error&&(g.error=o.error.message??o.error.stack),i&&o.location&&i.set(l,o.location),a.push(g),o.steps.length>0&&a.push(...Z(o.steps,l,r,i,n))}}return a}function wt(e,t){let r=w.isAbsolute(e)?e:w.join(process.cwd(),e),i=w.join(r,"latest");try{if(x.lstatSync(i).isSymbolicLink())x.unlinkSync(i);else{console.warn("[report] 'latest' exists and is not a symlink; skipping update");return}}catch{}try{process.platform==="win32"?x.symlinkSync(w.join(r,t),i,"junction"):x.symlinkSync(t,i,"dir")}catch(n){console.warn(`[report] Could not create 'latest' symlink: ${n instanceof Error?n.message:String(n)}`)}}var Q=class{outputFolder;openMode;latestSymlinkDir;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure",this.latestSymlinkDir=t.latestSymlinkDir}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let l of this.collected){let g=l.test.titlePath().join(" > "),b=r.get(g);b||(b=[],r.set(g,b)),b.push(l)}let i=[];for(let[,l]of r.entries()){let g=l[0].test.location.file,b=[],c,u,m;for(let y=0;y<l.length;y++){let{test:v,result:T}=l[y],A=await this.buildReportTest(v,T,g);c=A,u||(u=A.startTime),m=A.endTime,b.push({attemptNumber:y+1,status:T.status,duration:T.duration,steps:A.steps,error:A.error,videoPath:A.videoPath,tracePath:A.tracePath})}let d=b[b.length-1],{test:S}=l[l.length-1],_={title:S.title,baseTitle:c?.baseTitle,file:w.relative(process.cwd(),g),status:d.status,duration:d.duration,steps:d.steps,error:d.error,videoPath:d.videoPath,tracePath:d.tracePath,actionStepsMap:c?.actionStepsMap,tags:c?.tags,baseUrl:c?.baseUrl,skip:c?.skip,slow:c?.slow,timeout:c?.timeout,parameterSetName:c?.parameterSetName,startTime:u,endTime:m,suiteName:c?.suiteName};b.length>1&&(_.retries=b.length-1,_.attempts=b,b.some(v=>v.status==="failed"||v.status==="timedOut")&&d.status==="passed"&&(_.flaky=!0)),i.push(_)}let n={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString(),shiplightVersion:Ee},a=w.isAbsolute(this.outputFolder)?this.outputFolder:w.join(process.cwd(),this.outputFolder);x.mkdirSync(a,{recursive:!0});let o=w.join(a,"screenshots");for(let l=0;l<n.tests.length;l++){let g=n.tests[l],b=g.attempts&&g.attempts.length>0,c=[{obj:g,prefix:b?`test-${l}-attempt-0`:`test-${l}`,screenshotSubDir:`test-${l}`}];if(g.attempts)for(let u=0;u<g.attempts.length;u++)c.push({obj:g.attempts[u],prefix:`test-${l}-attempt-${u+1}`,screenshotSubDir:`test-${l}/attempt-${u}`});for(let{obj:u,prefix:m,screenshotSubDir:d}of c){let S=w.join(o,d),_=!1;for(let y of u.steps)if(y.screenshot&&w.isAbsolute(y.screenshot))try{_||(x.mkdirSync(S,{recursive:!0}),_=!0);let v=`${y.stepId.replace(/\./g,"-")}.png`;x.copyFileSync(y.screenshot,w.join(S,v)),y.screenshot=`screenshots/${d}/${v}`}catch(v){console.warn(`[reporter] Failed to copy screenshot for ${y.stepId}:`,v)}if(u.videoPath&&w.isAbsolute(u.videoPath)){let y=w.extname(u.videoPath)||".webm",v=`${m}-video${y}`;try{x.copyFileSync(u.videoPath,w.join(a,v)),u.videoPath=v}catch{u.videoPath=void 0}}if(u.tracePath&&w.isAbsolute(u.tracePath)){let y=w.extname(u.tracePath)||".zip",v=`${m}-trace${y}`;try{x.copyFileSync(u.tracePath,w.join(a,v)),u.tracePath=v}catch{u.tracePath=void 0}}}}let p=w.join(a,"report-data.json");x.writeFileSync(p,JSON.stringify(n,null,2),"utf-8");let h=w.join(a,"index.html");if(x.writeFileSync(h,Ae({...n,outputDir:a}),"utf-8"),console.log(`
887
887
  Shiplight report written to: ${h}`),this.latestSymlinkDir&&wt(this.latestSymlinkDir,w.basename(a)),this.openMode==="always"||this.openMode==="on-failure"&&t.status!=="passed")try{let l=(await import("open")).default;await l(h)}catch{}}printsToStdio(){return!1}async buildReportTest(t,r,i){let n={title:t.title,file:w.relative(process.cwd(),i),status:r.status,duration:r.duration,steps:[],startTime:new Date(r.startTime).toISOString(),endTime:new Date(r.startTime.getTime()+r.duration).toISOString()};r.errors.length>0&&(n.error=r.errors.map(c=>c.message||c.stack||String(c)).join(`
888
888
 
889
- `)),r.stdout.length>0&&(n.stdout=r.stdout.map(c=>typeof c=="string"?c:c.toString()).join("")),r.stderr.length>0&&(n.stderr=r.stderr.map(c=>typeof c=="string"?c:c.toString()).join(""));for(let c of r.attachments)c.name==="video"&&c.path&&(n.videoPath=c.path),c.name==="trace"&&c.path&&(n.tracePath=c.path);let a=r.attachments.find(c=>c.name==="shiplight-results"),o=null;if(a)try{if(a.body)o=JSON.parse(a.body.toString("utf-8"));else if(a.path){let c=x.readFileSync(a.path,"utf-8");o=JSON.parse(c)}}catch{}let p=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),h={},l=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),g=l?l[1]:t.title,b=l?l[2]:void 0;if(b&&(n.parameterSetName=b),x.existsSync(p))try{let c=x.readFileSync(p,"utf-8"),u=ve(c,p);if(u.suite){let m=u.suite.tests.find(d=>d.name===g);m&&(h=K(m.testFlow),m.tags?.length&&(n.tags=m.tags),m.skip!==void 0&&(n.skip=m.skip),m.slow&&(n.slow=m.slow),m.timeout!==void 0&&(n.timeout=m.timeout),n.baseTitle=m.name||m.testFlow?.goal),n.suiteName=u.name,u.tags?.length&&(n.suiteTags=u.tags),u.use?.baseURL&&(n.baseUrl=u.use.baseURL)}else u.testFlow&&(h=K(u.testFlow),n.baseTitle=u.name||u.testFlow?.goal,u.tags?.length&&(n.tags=u.tags),u.use?.baseURL&&(n.baseUrl=u.use.baseURL))}catch{}if(o||Object.keys(h).length>0){let c=new Set([...Object.keys(h),...Object.keys(o||{})]),u=Array.from(c).map(d=>[d,null]),m=yt(u);for(let[d]of m){let S=h[d],_=o?.[d],y=S?.description;if(!y||y==="Action"||y==="Draft"){let T=S?.action_entity;y=T?.action_description||T?.action_data?.kwargs?.description||_?.description||d}let v={stepId:d,description:y,status:_?.status||"pending",duration:_?.duration};if(_?.message){let T=typeof _.message=="string"?_.message:JSON.stringify(_.message,null,2);_.status==="failure"?v.error=T:v.message=T}if(_?.screenshot){let T=_.screenshot,A=a?.path?w.dirname(a.path):"",O=w.isAbsolute(T)?T:w.join(A,T);x.existsSync(O)&&(v.screenshot=O)}_?.code&&(v.code=_.code),n.steps.push(v)}}if(o===null&&Object.keys(h).length===0&&!i.endsWith(".yaml.spec.ts")){let c=new Map;if(n.steps=Z(r.steps,"main",void 0,c),n.steps.length>0){let u=new Map;n.actionStepsMap=Object.fromEntries(n.steps.map(m=>{let d=c.get(m.stepId),S;if(d?.file){if(!u.has(d.file))try{u.set(d.file,x.readFileSync(d.file,"utf-8").split(`
889
+ `)),r.stdout.length>0&&(n.stdout=r.stdout.map(c=>typeof c=="string"?c:c.toString()).join("")),r.stderr.length>0&&(n.stderr=r.stderr.map(c=>typeof c=="string"?c:c.toString()).join(""));for(let c of r.attachments)c.name==="video"&&c.path&&(n.videoPath=c.path),c.name==="trace"&&c.path&&(n.tracePath=c.path);let a=r.attachments.find(c=>c.name==="shiplight-results"),o=null;if(a)try{if(a.body)o=JSON.parse(a.body.toString("utf-8"));else if(a.path){let c=x.readFileSync(a.path,"utf-8");o=JSON.parse(c)}}catch{}let p=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),h={},l=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),g=l?l[1]:t.title,b=l?l[2]:void 0;if(b&&(n.parameterSetName=b),x.existsSync(p))try{let c=x.readFileSync(p,"utf-8"),u=ve(c,p,this.config.rootDir);if(u.suite){let m=u.suite.tests.find(d=>d.name===g);m&&(h=K(m.testFlow),m.tags?.length&&(n.tags=m.tags),m.skip!==void 0&&(n.skip=m.skip),m.slow&&(n.slow=m.slow),m.timeout!==void 0&&(n.timeout=m.timeout),n.baseTitle=m.name||m.testFlow?.goal),n.suiteName=u.name,u.tags?.length&&(n.suiteTags=u.tags),u.use?.baseURL&&(n.baseUrl=u.use.baseURL)}else u.testFlow&&(h=K(u.testFlow),n.baseTitle=u.name||u.testFlow?.goal,u.tags?.length&&(n.tags=u.tags),u.use?.baseURL&&(n.baseUrl=u.use.baseURL))}catch{}if(o||Object.keys(h).length>0){let c=new Set([...Object.keys(h),...Object.keys(o||{})]),u=Array.from(c).map(d=>[d,null]),m=yt(u);for(let[d]of m){let S=h[d],_=o?.[d],y=S?.description;if(!y||y==="Action"||y==="Draft"){let T=S?.action_entity;y=T?.action_description||T?.action_data?.kwargs?.description||_?.description||d}let v={stepId:d,description:y,status:_?.status||"pending",duration:_?.duration};if(_?.message){let T=typeof _.message=="string"?_.message:JSON.stringify(_.message,null,2);_.status==="failure"?v.error=T:v.message=T}if(_?.screenshot){let T=_.screenshot,A=a?.path?w.dirname(a.path):"",O=w.isAbsolute(T)?T:w.join(A,T);x.existsSync(O)&&(v.screenshot=O)}_?.code&&(v.code=_.code),n.steps.push(v)}}if(o===null&&Object.keys(h).length===0&&!i.endsWith(".yaml.spec.ts")){let c=new Map;if(n.steps=Z(r.steps,"main",void 0,c),n.steps.length>0){let u=new Map;n.actionStepsMap=Object.fromEntries(n.steps.map(m=>{let d=c.get(m.stepId),S;if(d?.file){if(!u.has(d.file))try{u.set(d.file,x.readFileSync(d.file,"utf-8").split(`
890
890
  `))}catch{u.set(d.file,[])}let y=u.get(d.file);S=y[d.line-1]?.trim();let v=d.line-2,T=d.line,A=[],O=d.line;v>=0&&(A.push(y[v]??""),O=d.line-1),A.push(y[d.line-1]??""),T<y.length&&A.push(y[T]??""),m.code=A.join(`
891
891
  `),m.codeStartLine=O,m.codeLine=d.line}let _={description:m.description,...S&&{action_entity:{action_description:m.description,action_data:{action_name:"js_code",args:[],kwargs:{code:S}}}}};return[m.stepId,_]}))}}return Object.keys(h).length>0?n.actionStepsMap=h:o&&n.steps.length>0&&(n.actionStepsMap=Object.fromEntries(n.steps.map(c=>{let u=o[c.stepId],m=u?.type,d={description:c.description,...m&&{action_entity:{action_description:c.description,action_data:{action_name:m==="step"?"js_code":m,args:[],kwargs:m==="step"?{code:u?.code??c.description}:{statement:c.description}}}}};return[c.stepId,d]}))),n}},vt=Q;export{vt as default};
@@ -1,4 +1,4 @@
1
- import { r as reactExports, W as We } from "./index-BEW7CdFm.js";
1
+ import { r as reactExports, W as We } from "./index-Mg0a6DkO.js";
2
2
  function _defineProperty$1(obj, key, value) {
3
3
  if (key in obj) {
4
4
  Object.defineProperty(obj, key, {
@@ -649,4 +649,4 @@ export {
649
649
  loader,
650
650
  Le as useMonaco
651
651
  };
652
- //# sourceMappingURL=index-CoKedfWS.js.map
652
+ //# sourceMappingURL=index-Dk5ihq1Y.js.map
@@ -42987,6 +42987,11 @@ const DebuggerProvider = ({
42987
42987
  setIsDebugging(true);
42988
42988
  } catch (error) {
42989
42989
  console.error("Failed to start debugging:", error);
42990
+ notifications.show({
42991
+ title: "Failed to start debug session",
42992
+ message: error instanceof Error ? error.message : "Unknown error",
42993
+ color: "red"
42994
+ });
42990
42995
  } finally {
42991
42996
  setIsStepping(false);
42992
42997
  }
@@ -43320,6 +43325,11 @@ const DebuggerProvider = ({
43320
43325
  }
43321
43326
  } catch (error) {
43322
43327
  console.error("Failed to start debugging and run until target:", error);
43328
+ notifications.show({
43329
+ title: "Failed to start debug session",
43330
+ message: error instanceof Error ? error.message : "Unknown error",
43331
+ color: "red"
43332
+ });
43323
43333
  setIsDebugging(false);
43324
43334
  setIsStepping(false);
43325
43335
  throw error;
@@ -187982,7 +187992,7 @@ function dynamic(importFn, _options) {
187982
187992
  );
187983
187993
  return Wrapper;
187984
187994
  }
187985
- const MonacoEditor = dynamic(() => __vitePreload(() => import("./index-CoKedfWS.js"), true ? [] : void 0), {});
187995
+ const MonacoEditor = dynamic(() => __vitePreload(() => import("./index-Dk5ihq1Y.js"), true ? [] : void 0), {});
187986
187996
  const CodeEditor = ({
187987
187997
  initialCode = "",
187988
187998
  onConfirm,
@@ -197605,6 +197615,12 @@ const TestFlowEditorController = ({
197605
197615
  return debugSession;
197606
197616
  } catch (error) {
197607
197617
  setSessionInitProgress(null);
197618
+ if (onReceivedLiveviewUrl) {
197619
+ onReceivedLiveviewUrl(void 0, void 0);
197620
+ }
197621
+ if (onModeChange) {
197622
+ onModeChange();
197623
+ }
197608
197624
  console.error("Session initialization failed:", error);
197609
197625
  throw new Error(`Failed to initialize debug session: ${error instanceof Error ? error.message : "Unknown error"}`);
197610
197626
  }
@@ -200236,6 +200252,36 @@ function useInitialConfig() {
200236
200252
  }, []);
200237
200253
  return config2;
200238
200254
  }
200255
+ class EditorErrorBoundary extends We.Component {
200256
+ constructor() {
200257
+ super(...arguments);
200258
+ this.state = { error: null };
200259
+ }
200260
+ static getDerivedStateFromError(error) {
200261
+ return { error };
200262
+ }
200263
+ componentDidCatch(error, info) {
200264
+ console.error("Editor render error:", error, info.componentStack);
200265
+ }
200266
+ render() {
200267
+ if (this.state.error) {
200268
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { align: "center", gap: "sm", children: [
200269
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "lg", c: "red", children: "Something went wrong" }),
200270
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", style: { maxWidth: 400, textAlign: "center" }, children: this.state.error.message }),
200271
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
200272
+ Button,
200273
+ {
200274
+ variant: "light",
200275
+ color: "blue",
200276
+ onClick: () => this.setState({ error: null }),
200277
+ children: "Retry"
200278
+ }
200279
+ )
200280
+ ] }) });
200281
+ }
200282
+ return this.props.children;
200283
+ }
200284
+ }
200239
200285
  const DEFAULT_SIDEBAR_WIDTH = 300;
200240
200286
  const MIN_SIDEBAR_WIDTH = 180;
200241
200287
  const MAX_SIDEBAR_WIDTH = 600;
@@ -200513,7 +200559,7 @@ function LocalDebuggerPage() {
200513
200559
  /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: error || "No test flow data" })
200514
200560
  ] }) });
200515
200561
  }
200516
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
200562
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(EditorErrorBoundary, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
200517
200563
  TestFlowEditorController,
200518
200564
  {
200519
200565
  intRunner: localIntRunnerApi,
@@ -200532,7 +200578,7 @@ function LocalDebuggerPage() {
200532
200578
  v2: true
200533
200579
  },
200534
200580
  selectedFile
200535
- );
200581
+ ) });
200536
200582
  };
200537
200583
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { height: "100vh", display: "flex", flexDirection: "column" }, children: [
200538
200584
  /* @__PURE__ */ jsxRuntimeExports.jsx(Notifications$1, { position: "top-right" }),
@@ -200710,4 +200756,4 @@ export {
200710
200756
  We as W,
200711
200757
  reactExports as r
200712
200758
  };
200713
- //# sourceMappingURL=index-BEW7CdFm.js.map
200759
+ //# sourceMappingURL=index-Mg0a6DkO.js.map
@@ -8,7 +8,7 @@
8
8
  /* Prevent FOUC */
9
9
  body { margin: 0; background: #1a1b1e; color: #c1c2c5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; }
10
10
  </style>
11
- <script type="module" crossorigin src="/assets/index-BEW7CdFm.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-Mg0a6DkO.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-uF1WN2rG.css">
13
13
  </head>
14
14
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiplightai",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
4
4
  "type": "module",
5
5
  "description": "Shiplight CLI for running and debugging .test.yaml files",
6
6
  "main": "dist/index.js",
@@ -97,9 +97,9 @@
97
97
  "@types/node": "^24.0.0",
98
98
  "tsup": "^8.3.5",
99
99
  "typescript": "5.5.4",
100
- "mcp-tools": "1.0.0",
101
- "sdk-internal": "0.1.1",
102
100
  "sdk-core": "0.1.0",
101
+ "sdk-internal": "0.1.1",
102
+ "mcp-tools": "1.0.0",
103
103
  "shiplight-tools": "1.0.0",
104
104
  "shiplight-types": "0.1.0",
105
105
  "@loggia/common": "1.0.0"