ripplo 0.3.7 → 0.3.8

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.
Files changed (2) hide show
  1. package/dist/index.js +2 -2
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -315,7 +315,7 @@ ${t.join(`
315
315
  }
316
316
  }
317
317
  }
318
- `)});import{Box as ww,Text as qo}from"ink";import{jsx as Gc,jsxs as Kc}from"react/jsx-runtime";function Qc({activeRunCount:e,browsersReady:t,maxConcurrency:n}){return Kc(ww,{paddingX:1,children:[Gc(qo,{dimColor:!0,children:"Activity "}),Kc(qo,{children:[String(e),"/",String(n)," running \xB7 browsers"," "]}),Gc(qo,{color:t?"green":"yellow",children:t?"ready":"warming"})]})}var Yc=f(()=>{"use strict"});import{Box as bw,Text as _n}from"ink";import{jsx as Un,jsxs as Rw}from"react/jsx-runtime";function Zc({check:e}){let{detail:t,label:n,tone:r}=In(e),o=Aw[r];return Rw(bw,{children:[Un(_n,{color:o,children:r==="info"?"\u25CB":"\u25CF"}),Un(_n,{children:" "}),Un(_n,{children:n.padEnd(18," ")}),Un(_n,{color:o,children:t})]})}var Aw,ed=f(()=>{"use strict";Bo();Aw={error:"red",info:"gray",ok:"green",warn:"yellow"}});import{Box as vw,Text as td}from"ink";import{jsx as Fo,jsxs as Tw}from"react/jsx-runtime";function nd({checks:e}){return Tw(vw,{borderColor:"gray",borderStyle:"round",flexDirection:"column",marginTop:1,paddingX:1,children:[Fo(td,{bold:!0,children:"Health"}),e.length===0?Fo(td,{dimColor:!0,children:"Running first pass..."}):e.map(t=>Fo(Zc,{check:t},t.type))]})}var rd=f(()=>{"use strict";ed()});import{Box as jn,Text as yt}from"ink";import{jsx as gt,jsxs as $n}from"react/jsx-runtime";function od({appUrl:e,developerUrl:t,projectName:n}){return $n(jn,{borderColor:"gray",borderStyle:"round",flexDirection:"column",paddingX:1,children:[$n(jn,{children:[gt(yt,{dimColor:!0,children:"Project "}),gt(yt,{bold:!0,children:n})]}),$n(jn,{children:[gt(yt,{dimColor:!0,children:"App "}),gt(yt,{color:"cyan",children:e})]}),$n(jn,{children:[gt(yt,{dimColor:!0,children:"Dashboard "}),gt(yt,{color:"cyan",children:t})]})]})}var id=f(()=>{"use strict"});import{Box as Ho,Text as Fe}from"ink";import{jsx as He,jsxs as Mo}from"react/jsx-runtime";function ad({paused:e,scope:t}){return Mo(Ho,{borderColor:"gray",borderStyle:"round",flexDirection:"column",marginTop:1,paddingX:1,children:[Mo(Ho,{children:[He(Fe,{bold:!0,children:"Scope"}),He(Fe,{dimColor:!0,children:` (${String(t.length)})`}),He(Fe,{children:" "}),He(Fe,{dimColor:!0,children:"Hooks "}),He(Fe,{color:e?"yellow":"green",children:e?"paused":"active"})]}),t.length===0?He(Fe,{dimColor:!0,children:"no scope items \u2014 add from the dashboard or `ripplo scope add`"}):t.map(n=>Mo(Ho,{children:[He(Fe,{color:"cyan",children:"\u2022 "}),He(Fe,{children:n.label})]},n.id))]})}var sd=f(()=>{"use strict"});import{Box as Nw,Text as ld}from"ink";import{jsx as Cw,jsxs as cd}from"react/jsx-runtime";function dd({preconditions:e,workflows:t}){let n=t?.type==="workflows"?t.total:0,r=t?.type==="workflows"?t.invalidNames.length:0,o=e?.type==="preconditions"?e.count:0;return cd(Nw,{marginTop:1,paddingX:1,children:[Cw(ld,{dimColor:!0,children:"Workflows "}),cd(ld,{children:[String(n)," tests \xB7 ",String(o)," preconditions",r>0?` \xB7 ${String(r)} invalid`:""]})]})}var pd=f(()=>{"use strict"});import BI from"chalk";import{z as ud}from"zod";async function md(){let e=new AbortController,t=setTimeout(()=>{e.abort()},3e3);try{let n=await fetch(`https://registry.npmjs.org/${Lw}/latest`,{headers:{accept:"application/json"},signal:e.signal});if(!n.ok)return;let r=await n.json();return xw.parse(r).version}catch{return}finally{clearTimeout(t)}}var Jo,Lw,xw,fd=f(()=>{"use strict";G();Jo="0.3.7",Lw="ripplo",xw=ud.object({version:ud.string()})});import{useEffect as Pw,useState as Ew}from"react";import{Text as Ze}from"ink";import{jsx as ht,jsxs as Uw}from"react/jsx-runtime";function yd({activeRunCount:e,browsersReady:t,connected:n,projectId:r,ripploServerUrl:o,syncError:i,width:a}){let s=_w(),l=s!=null&&s!==Jo,c=Ow({connected:n,syncError:i}),d=t?"":" warming up browsers...",p=e>0?` ${String(e)} running`:"",u=`v${Jo}`,m=l?` \u2192 v${s}`:"",h=`project ${r} \xB7 q to quit`,S=` ripplo ${u}${m} | ${o} ${c}${d}${p}`,w=Math.max(0,a-S.length-h.length-1);return Uw(Ze,{inverse:!0,children:[ht(Ze,{bold:!0,children:" ripplo "}),ht(Ze,{dimColor:!0,children:u}),l?ht(Ze,{color:"yellow",children:` \u2192 v${s}`}):null,` | ${o} `,ht(Ze,{color:Iw({connected:n,syncError:i}),children:c}),t?null:ht(Ze,{color:"yellow",children:d}),p," ".repeat(w),ht(Ze,{dimColor:!0,children:h})," "]})}function Ow({connected:e,syncError:t}){return t!=null?"sync failed":e?"connected":"connecting..."}function Iw({connected:e,syncError:t}){return t!=null?"red":e?"green":"yellow"}function _w(){let[e,t]=Ew();return Pw(()=>{md().then(n=>{n!=null&&t(n)})},[]),e}var gd=f(()=>{"use strict";fd()});import{useEffect as jw}from"react";import{availableParallelism as $w}from"os";import{Box as zo,Text as Dw,useApp as Bw,useInput as Vw}from"ink";import{jsx as Me,jsxs as hd}from"react/jsx-runtime";function kd({config:e,cwd:t}){let n=Bw(),{height:r,width:o}=ft(),{config:i,devSessionId:a,syncError:s}=_c({config:e,cwd:t}),{activeRunCount:l,browsersReady:c,connected:d}=uc({config:i,cwd:t,devSessionId:a}),{checks:p}=Dc({config:i,cwd:t}),{paused:u,projectName:m,scope:h}=zc({projectId:e.projectId});qc({cwd:t,projectId:e.projectId}),jw(()=>$l(t),[t]),Vw(v=>{v==="q"&&n.exit()});let S=`${i.ripploServerUrl}/projects/${i.projectId}/developer`,w=p.filter(v=>Ww.has(v.type)),k=p.find(v=>v.type==="workflows"),b=p.find(v=>v.type==="preconditions"),T=$w(),x=m??"\u2026";return hd(zo,{flexDirection:"column",height:r,width:o,children:[Me(yd,{activeRunCount:l,browsersReady:c,connected:d,projectId:i.projectId,ripploServerUrl:i.ripploServerUrl,syncError:s,width:o}),hd(zo,{flexDirection:"column",flexGrow:1,paddingX:1,paddingY:1,children:[Me(od,{appUrl:i.appUrl,developerUrl:S,projectName:x}),Me(ad,{paused:u,scope:h}),Me(nd,{checks:w}),Me(dd,{preconditions:b,workflows:k}),Me(Qc,{activeRunCount:l,browsersReady:c,maxConcurrency:T}),Me(zo,{marginTop:1,paddingX:1,children:Me(Dw,{dimColor:!0,children:"q quit"})})]})]})}var Ww,Sd=f(()=>{"use strict";vo();On();Uc();Bc();Fc();Xc();Eo();Yc();rd();id();sd();pd();gd();Ww=new Set(["dev-server","preconditions","adapter-enabled","webhook-verification","lockfile","pre-commit-hook","browser"])});import{Text as wd}from"ink";import qw from"ink-spinner";import{jsx as bd,jsxs as Hw}from"react/jsx-runtime";function Ad({loading:e}){return e?Hw(wd,{color:Fw,children:[bd(qw,{type:"dots"})," Starting..."]}):bd(wd,{children:""})}var Fw,Rd=f(()=>{"use strict";Fw="#24CFFF"});import{useEffect as Mw,useState as $t}from"react";import{Box as et,Text as Xo,useInput as Jw}from"ink";import{jsx as Je,jsxs as Go}from"react/jsx-runtime";function Td({onDismiss:e}){let{height:t,width:n}=ft(),[r,o]=$t(0),[i,a]=$t(!1),[s,l]=$t(!1),[c,d]=$t(!1),[p,u]=$t(!1);Jw(()=>{p&&e()}),Mw(()=>{let S=80+Dn.length*70+80,w=[...Dn.map((k,b)=>setTimeout(()=>{o(b+1)},80+b*70)),setTimeout(()=>{a(!0)},S),setTimeout(()=>{l(!0)},S+200),setTimeout(()=>{d(!0)},S+400),setTimeout(()=>{d(!1),u(!0)},S+2e3),setTimeout(()=>{e()},S+2800)];return()=>{w.forEach(k=>{clearTimeout(k)})}},[e]);let m=Dn.length+5,h=Math.max(0,Math.floor((t-m)/2));return Go(et,{flexDirection:"column",height:t,width:n,children:[Je(et,{height:h}),Go(et,{alignItems:"flex-start",flexDirection:"row",justifyContent:"center",children:[Je(et,{flexDirection:"column",children:Dn.map((S,w)=>Je(Xo,{color:vd,children:w<r?S:""},S))}),Go(et,{flexDirection:"column",paddingLeft:4,paddingTop:5,children:[Je(Xo,{bold:!0,color:vd,children:i?zw:""}),Je(et,{marginTop:1,children:Je(Xo,{color:"gray",dimColor:!0,children:s?"developer CLI":""})}),Je(et,{marginTop:2,children:Je(Ad,{loading:c})})]})]})]})}var Dn,vd,zw,Nd=f(()=>{"use strict";On();Rd();Dn=[" XXXX XXXXXXX "," XX XX X X "," XXX XX XX X X ","XXXXXX XX XX XXXXXXX ","XX XXX XX XX ","XX XXX XX XXXXXXXX "," XX XX XX XX "," XX XX XX XX "," XX XXXXX XX"," XX XX XX XX"," XX XX XXXX "," XXX XX "," XXXXXXXXX XX "," XXX XX "," XXXXXXX "],vd="#24CFFF",zw="ripplo"});import{createAuthClient as Xw}from"better-auth/client";import{deviceAuthorizationClient as Gw}from"better-auth/client/plugins";function Bn({baseURL:e}){return Xw({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[Gw()]})}var Ko=f(()=>{"use strict"});import{exec as Kw}from"child_process";async function Ld({cwd:e,onDeviceCode:t,url:n}){let r=n??It().RIPPLO_SERVER_URL,o=Bn({baseURL:r}),i=await o.device.code({client_id:Cd});if(i.error!=null)throw new Error(`Failed to request device code: ${i.error.error_description}`);let{device_code:a,user_code:s,verification_uri_complete:l}=i.data;t({userCode:s,verificationUrl:l}),rb(l);let c=await Yw({authClient:o,deviceCode:a});return qr(e,c),c}async function Yw({authClient:e,deviceCode:t}){for(;;){await tb(Qw);let n=await e.device.token({client_id:Cd,device_code:t,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(n.data?.access_token!=null)return n.data.access_token;if(n.error==null)continue;if(!eb(n.error.error))throw new Error(`Authorization failed: ${n.error.error_description}`)}}function eb(e){return Zw.has(e)}function tb(e){return new Promise(t=>{setTimeout(t,e)})}function nb(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function rb(e){let t=nb();Kw(`${t} "${e}"`,()=>{})}var Qw,Cd,Zw,xd=f(()=>{"use strict";G();Ko();Ke();Qw=5e3,Cd="ripplo-cli";Zw=new Set(["authorization_pending","slow_down"])});import{useCallback as Vn,useEffect as ob,useState as ze}from"react";import{exec as ib}from"child_process";import{promisify as ab}from"util";import J from"fs";import M from"path";function Ed({cwd:e,initialToken:t,serverUrl:n}){let[r,o]=ze(t==null?"login":"validating"),[i,a]=ze(),[s,l]=ze(),[c,d]=ze(t),[p,u]=ze(!1),[m,h]=ze(),[S,w]=ze(),[k,b]=ze([]);ob(()=>{if(t==null)return;Bn({baseURL:n}).getSession({fetchOptions:{headers:{Authorization:`Bearer ${t}`}}}).then(L=>{if(L.data==null){o("login");return}return Qo({cwd:e,serverUrl:n,token:t})}).then(L=>{if(L==null){o("select-project");return}a(L),o("complete")}).catch(L=>{l(L instanceof Error?L.message:String(L)),o("login")})},[e,t,n]);let T=Vn(()=>{p||(u(!0),l(void 0),Ld({cwd:e,onDeviceCode:h,url:n}).then(O=>(d(O),Qo({cwd:e,serverUrl:n,token:O}))).then(O=>{O==null?o("select-project"):(a(O),o("complete"))}).catch(O=>{u(!1),h(void 0),l(O instanceof Error?O.message:String(O))}))},[e,p,n]),x=Vn(O=>{let L=i?.token??c??t;if(L==null){l("No authentication token available");return}o("scaffolding"),l(void 0),db({cwd:e,onStep:w,projectId:O.id}).then(async V=>{b(V);let Xe=await Qo({cwd:e,serverUrl:n,token:L});a(Xe??sb({projectId:O.id,serverUrl:n,token:L})),o(V.length>0?"scaffolding-warnings":"complete")}).catch(V=>{l(V instanceof Error?V.message:String(V)),o("select-project")})},[i,e,t,n,c]),v=Vn(()=>{o("complete")},[]),U=Vn(()=>{a(void 0),o("login"),u(!1),h(void 0),l(void 0)},[]);return{config:i,deviceCodeInfo:m,dismissScaffoldWarnings:v,error:s,loginStarted:p,resetToLogin:U,scaffoldingStep:S,scaffoldWarnings:k,selectProject:x,setStage:o,stage:r,startLogin:T,token:c}}function sb({projectId:e,serverUrl:t,token:n}){return{appUrl:"",engineUrl:"",projectId:e,ripploServerUrl:t,token:n,webhookSecret:""}}async function Qo({cwd:e,serverUrl:t,token:n}){let r=await I(e);if(r.ok)return{...r.result.config,ripploServerUrl:t,token:n}}async function db({cwd:e,onStep:t,projectId:n}){t("Scaffolding project files..."),Sb({cwd:e,projectId:n}),t("Updating .gitignore..."),Lb(e),t("Marking ripplo.lock as generated..."),fb(e),t("Installing dependencies...");let r=await pb(e),o=[];if(r.ok||o.push({manualCommand:r.cmd,message:`Couldn't auto-install dev dependencies (${r.reason}). Run this manually, then re-run \`npx ripplo\`.`}),r.ok){t("Compiling initial lockfile...");let i=await ub(e);i!=null&&o.push({manualCommand:void 0,message:i})}return t("Setting up browser..."),await vr(),o}async function pb(e){let t=yb({cwd:e,pm:kb(e)});A.info("Installing dependencies: %s",t);try{return await cb(t,{cwd:e}),{ok:!0}}catch(n){let r=n instanceof Error?n.message.split(`
318
+ `)});import{Box as ww,Text as qo}from"ink";import{jsx as Gc,jsxs as Kc}from"react/jsx-runtime";function Qc({activeRunCount:e,browsersReady:t,maxConcurrency:n}){return Kc(ww,{paddingX:1,children:[Gc(qo,{dimColor:!0,children:"Activity "}),Kc(qo,{children:[String(e),"/",String(n)," running \xB7 browsers"," "]}),Gc(qo,{color:t?"green":"yellow",children:t?"ready":"warming"})]})}var Yc=f(()=>{"use strict"});import{Box as bw,Text as _n}from"ink";import{jsx as Un,jsxs as Rw}from"react/jsx-runtime";function Zc({check:e}){let{detail:t,label:n,tone:r}=In(e),o=Aw[r];return Rw(bw,{children:[Un(_n,{color:o,children:r==="info"?"\u25CB":"\u25CF"}),Un(_n,{children:" "}),Un(_n,{children:n.padEnd(18," ")}),Un(_n,{color:o,children:t})]})}var Aw,ed=f(()=>{"use strict";Bo();Aw={error:"red",info:"gray",ok:"green",warn:"yellow"}});import{Box as vw,Text as td}from"ink";import{jsx as Fo,jsxs as Tw}from"react/jsx-runtime";function nd({checks:e}){return Tw(vw,{borderColor:"gray",borderStyle:"round",flexDirection:"column",marginTop:1,paddingX:1,children:[Fo(td,{bold:!0,children:"Health"}),e.length===0?Fo(td,{dimColor:!0,children:"Running first pass..."}):e.map(t=>Fo(Zc,{check:t},t.type))]})}var rd=f(()=>{"use strict";ed()});import{Box as jn,Text as yt}from"ink";import{jsx as gt,jsxs as $n}from"react/jsx-runtime";function od({appUrl:e,developerUrl:t,projectName:n}){return $n(jn,{borderColor:"gray",borderStyle:"round",flexDirection:"column",paddingX:1,children:[$n(jn,{children:[gt(yt,{dimColor:!0,children:"Project "}),gt(yt,{bold:!0,children:n})]}),$n(jn,{children:[gt(yt,{dimColor:!0,children:"App "}),gt(yt,{color:"cyan",children:e})]}),$n(jn,{children:[gt(yt,{dimColor:!0,children:"Dashboard "}),gt(yt,{color:"cyan",children:t})]})]})}var id=f(()=>{"use strict"});import{Box as Ho,Text as Fe}from"ink";import{jsx as He,jsxs as Mo}from"react/jsx-runtime";function ad({paused:e,scope:t}){return Mo(Ho,{borderColor:"gray",borderStyle:"round",flexDirection:"column",marginTop:1,paddingX:1,children:[Mo(Ho,{children:[He(Fe,{bold:!0,children:"Scope"}),He(Fe,{dimColor:!0,children:` (${String(t.length)})`}),He(Fe,{children:" "}),He(Fe,{dimColor:!0,children:"Hooks "}),He(Fe,{color:e?"yellow":"green",children:e?"paused":"active"})]}),t.length===0?He(Fe,{dimColor:!0,children:"no scope items \u2014 add from the dashboard or `ripplo scope add`"}):t.map(n=>Mo(Ho,{children:[He(Fe,{color:"cyan",children:"\u2022 "}),He(Fe,{children:n.label})]},n.id))]})}var sd=f(()=>{"use strict"});import{Box as Nw,Text as ld}from"ink";import{jsx as Cw,jsxs as cd}from"react/jsx-runtime";function dd({preconditions:e,workflows:t}){let n=t?.type==="workflows"?t.total:0,r=t?.type==="workflows"?t.invalidNames.length:0,o=e?.type==="preconditions"?e.count:0;return cd(Nw,{marginTop:1,paddingX:1,children:[Cw(ld,{dimColor:!0,children:"Workflows "}),cd(ld,{children:[String(n)," tests \xB7 ",String(o)," preconditions",r>0?` \xB7 ${String(r)} invalid`:""]})]})}var pd=f(()=>{"use strict"});import BI from"chalk";import{z as ud}from"zod";async function md(){let e=new AbortController,t=setTimeout(()=>{e.abort()},3e3);try{let n=await fetch(`https://registry.npmjs.org/${Lw}/latest`,{headers:{accept:"application/json"},signal:e.signal});if(!n.ok)return;let r=await n.json();return xw.parse(r).version}catch{return}finally{clearTimeout(t)}}var Jo,Lw,xw,fd=f(()=>{"use strict";G();Jo="0.3.8",Lw="ripplo",xw=ud.object({version:ud.string()})});import{useEffect as Pw,useState as Ew}from"react";import{Text as Ze}from"ink";import{jsx as ht,jsxs as Uw}from"react/jsx-runtime";function yd({activeRunCount:e,browsersReady:t,connected:n,projectId:r,ripploServerUrl:o,syncError:i,width:a}){let s=_w(),l=s!=null&&s!==Jo,c=Ow({connected:n,syncError:i}),d=t?"":" warming up browsers...",p=e>0?` ${String(e)} running`:"",u=`v${Jo}`,m=l?` \u2192 v${s}`:"",h=`project ${r} \xB7 q to quit`,S=` ripplo ${u}${m} | ${o} ${c}${d}${p}`,w=Math.max(0,a-S.length-h.length-1);return Uw(Ze,{inverse:!0,children:[ht(Ze,{bold:!0,children:" ripplo "}),ht(Ze,{dimColor:!0,children:u}),l?ht(Ze,{color:"yellow",children:` \u2192 v${s}`}):null,` | ${o} `,ht(Ze,{color:Iw({connected:n,syncError:i}),children:c}),t?null:ht(Ze,{color:"yellow",children:d}),p," ".repeat(w),ht(Ze,{dimColor:!0,children:h})," "]})}function Ow({connected:e,syncError:t}){return t!=null?"sync failed":e?"connected":"connecting..."}function Iw({connected:e,syncError:t}){return t!=null?"red":e?"green":"yellow"}function _w(){let[e,t]=Ew();return Pw(()=>{md().then(n=>{n!=null&&t(n)})},[]),e}var gd=f(()=>{"use strict";fd()});import{useEffect as jw}from"react";import{availableParallelism as $w}from"os";import{Box as zo,Text as Dw,useApp as Bw,useInput as Vw}from"ink";import{jsx as Me,jsxs as hd}from"react/jsx-runtime";function kd({config:e,cwd:t}){let n=Bw(),{height:r,width:o}=ft(),{config:i,devSessionId:a,syncError:s}=_c({config:e,cwd:t}),{activeRunCount:l,browsersReady:c,connected:d}=uc({config:i,cwd:t,devSessionId:a}),{checks:p}=Dc({config:i,cwd:t}),{paused:u,projectName:m,scope:h}=zc({projectId:e.projectId});qc({cwd:t,projectId:e.projectId}),jw(()=>$l(t),[t]),Vw(v=>{v==="q"&&n.exit()});let S=`${i.ripploServerUrl}/projects/${i.projectId}/developer`,w=p.filter(v=>Ww.has(v.type)),k=p.find(v=>v.type==="workflows"),b=p.find(v=>v.type==="preconditions"),T=$w(),x=m??"\u2026";return hd(zo,{flexDirection:"column",height:r,width:o,children:[Me(yd,{activeRunCount:l,browsersReady:c,connected:d,projectId:i.projectId,ripploServerUrl:i.ripploServerUrl,syncError:s,width:o}),hd(zo,{flexDirection:"column",flexGrow:1,paddingX:1,paddingY:1,children:[Me(od,{appUrl:i.appUrl,developerUrl:S,projectName:x}),Me(ad,{paused:u,scope:h}),Me(nd,{checks:w}),Me(dd,{preconditions:b,workflows:k}),Me(Qc,{activeRunCount:l,browsersReady:c,maxConcurrency:T}),Me(zo,{marginTop:1,paddingX:1,children:Me(Dw,{dimColor:!0,children:"q quit"})})]})]})}var Ww,Sd=f(()=>{"use strict";vo();On();Uc();Bc();Fc();Xc();Eo();Yc();rd();id();sd();pd();gd();Ww=new Set(["dev-server","preconditions","adapter-enabled","webhook-verification","lockfile","pre-commit-hook","browser"])});import{Text as wd}from"ink";import qw from"ink-spinner";import{jsx as bd,jsxs as Hw}from"react/jsx-runtime";function Ad({loading:e}){return e?Hw(wd,{color:Fw,children:[bd(qw,{type:"dots"})," Starting..."]}):bd(wd,{children:""})}var Fw,Rd=f(()=>{"use strict";Fw="#24CFFF"});import{useEffect as Mw,useState as $t}from"react";import{Box as et,Text as Xo,useInput as Jw}from"ink";import{jsx as Je,jsxs as Go}from"react/jsx-runtime";function Td({onDismiss:e}){let{height:t,width:n}=ft(),[r,o]=$t(0),[i,a]=$t(!1),[s,l]=$t(!1),[c,d]=$t(!1),[p,u]=$t(!1);Jw(()=>{p&&e()}),Mw(()=>{let S=80+Dn.length*70+80,w=[...Dn.map((k,b)=>setTimeout(()=>{o(b+1)},80+b*70)),setTimeout(()=>{a(!0)},S),setTimeout(()=>{l(!0)},S+200),setTimeout(()=>{d(!0)},S+400),setTimeout(()=>{d(!1),u(!0)},S+2e3),setTimeout(()=>{e()},S+2800)];return()=>{w.forEach(k=>{clearTimeout(k)})}},[e]);let m=Dn.length+5,h=Math.max(0,Math.floor((t-m)/2));return Go(et,{flexDirection:"column",height:t,width:n,children:[Je(et,{height:h}),Go(et,{alignItems:"flex-start",flexDirection:"row",justifyContent:"center",children:[Je(et,{flexDirection:"column",children:Dn.map((S,w)=>Je(Xo,{color:vd,children:w<r?S:""},S))}),Go(et,{flexDirection:"column",paddingLeft:4,paddingTop:5,children:[Je(Xo,{bold:!0,color:vd,children:i?zw:""}),Je(et,{marginTop:1,children:Je(Xo,{color:"gray",dimColor:!0,children:s?"developer CLI":""})}),Je(et,{marginTop:2,children:Je(Ad,{loading:c})})]})]})]})}var Dn,vd,zw,Nd=f(()=>{"use strict";On();Rd();Dn=[" XXXX XXXXXXX "," XX XX X X "," XXX XX XX X X ","XXXXXX XX XX XXXXXXX ","XX XXX XX XX ","XX XXX XX XXXXXXXX "," XX XX XX XX "," XX XX XX XX "," XX XXXXX XX"," XX XX XX XX"," XX XX XXXX "," XXX XX "," XXXXXXXXX XX "," XXX XX "," XXXXXXX "],vd="#24CFFF",zw="ripplo"});import{createAuthClient as Xw}from"better-auth/client";import{deviceAuthorizationClient as Gw}from"better-auth/client/plugins";function Bn({baseURL:e}){return Xw({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[Gw()]})}var Ko=f(()=>{"use strict"});import{exec as Kw}from"child_process";async function Ld({cwd:e,onDeviceCode:t,url:n}){let r=n??It().RIPPLO_SERVER_URL,o=Bn({baseURL:r}),i=await o.device.code({client_id:Cd});if(i.error!=null)throw new Error(`Failed to request device code: ${i.error.error_description}`);let{device_code:a,user_code:s,verification_uri_complete:l}=i.data;t({userCode:s,verificationUrl:l}),rb(l);let c=await Yw({authClient:o,deviceCode:a});return qr(e,c),c}async function Yw({authClient:e,deviceCode:t}){for(;;){await tb(Qw);let n=await e.device.token({client_id:Cd,device_code:t,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(n.data?.access_token!=null)return n.data.access_token;if(n.error==null)continue;if(!eb(n.error.error))throw new Error(`Authorization failed: ${n.error.error_description}`)}}function eb(e){return Zw.has(e)}function tb(e){return new Promise(t=>{setTimeout(t,e)})}function nb(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function rb(e){let t=nb();Kw(`${t} "${e}"`,()=>{})}var Qw,Cd,Zw,xd=f(()=>{"use strict";G();Ko();Ke();Qw=5e3,Cd="ripplo-cli";Zw=new Set(["authorization_pending","slow_down"])});import{useCallback as Vn,useEffect as ob,useState as ze}from"react";import{exec as ib}from"child_process";import{promisify as ab}from"util";import J from"fs";import M from"path";function Ed({cwd:e,initialToken:t,serverUrl:n}){let[r,o]=ze(t==null?"login":"validating"),[i,a]=ze(),[s,l]=ze(),[c,d]=ze(t),[p,u]=ze(!1),[m,h]=ze(),[S,w]=ze(),[k,b]=ze([]);ob(()=>{if(t==null)return;Bn({baseURL:n}).getSession({fetchOptions:{headers:{Authorization:`Bearer ${t}`}}}).then(L=>{if(L.data==null){o("login");return}return Qo({cwd:e,serverUrl:n,token:t})}).then(L=>{if(L==null){o("select-project");return}a(L),o("complete")}).catch(L=>{l(L instanceof Error?L.message:String(L)),o("login")})},[e,t,n]);let T=Vn(()=>{p||(u(!0),l(void 0),Ld({cwd:e,onDeviceCode:h,url:n}).then(O=>(d(O),Qo({cwd:e,serverUrl:n,token:O}))).then(O=>{O==null?o("select-project"):(a(O),o("complete"))}).catch(O=>{u(!1),h(void 0),l(O instanceof Error?O.message:String(O))}))},[e,p,n]),x=Vn(O=>{let L=i?.token??c??t;if(L==null){l("No authentication token available");return}o("scaffolding"),l(void 0),db({cwd:e,onStep:w,projectId:O.id}).then(async V=>{b(V);let Xe=await Qo({cwd:e,serverUrl:n,token:L});a(Xe??sb({projectId:O.id,serverUrl:n,token:L})),o(V.length>0?"scaffolding-warnings":"complete")}).catch(V=>{l(V instanceof Error?V.message:String(V)),o("select-project")})},[i,e,t,n,c]),v=Vn(()=>{o("complete")},[]),U=Vn(()=>{a(void 0),o("login"),u(!1),h(void 0),l(void 0)},[]);return{config:i,deviceCodeInfo:m,dismissScaffoldWarnings:v,error:s,loginStarted:p,resetToLogin:U,scaffoldingStep:S,scaffoldWarnings:k,selectProject:x,setStage:o,stage:r,startLogin:T,token:c}}function sb({projectId:e,serverUrl:t,token:n}){return{appUrl:"",engineUrl:"",projectId:e,ripploServerUrl:t,token:n,webhookSecret:""}}async function Qo({cwd:e,serverUrl:t,token:n}){let r=await I(e);if(r.ok)return{...r.result.config,ripploServerUrl:t,token:n}}async function db({cwd:e,onStep:t,projectId:n}){t("Scaffolding project files..."),Sb({cwd:e,projectId:n}),t("Updating .gitignore..."),Lb(e),t("Marking ripplo.lock as generated..."),fb(e),t("Installing dependencies...");let r=await pb(e),o=[];if(r.ok||o.push({manualCommand:r.cmd,message:`Couldn't auto-install dev dependencies (${r.reason}). Run this manually, then re-run \`npx ripplo\`.`}),r.ok){t("Compiling initial lockfile...");let i=await ub(e);i!=null&&o.push({manualCommand:void 0,message:i})}return t("Setting up browser..."),await vr(),o}async function pb(e){let t=yb({cwd:e,pm:kb(e)});A.info("Installing dependencies: %s",t);try{return await cb(t,{cwd:e}),{ok:!0}}catch(n){let r=n instanceof Error?n.message.split(`
319
319
  `)[0]??n.message:String(n);return A.warn("Install failed (%s): %s",t,r),{cmd:t,ok:!1,reason:r}}}async function ub(e){try{await mb(e);return}catch(t){let n=t instanceof Error?t.message:String(t);return A.warn("Initial lockfile compile failed: %s",n),`Couldn't compile initial lockfile: ${n}. You can run \`npx ripplo\` again after fixing the DSL.`}}async function mb(e){let t=await I(e);if(!t.ok){A.warn("Initial compile failed, skipping lockfile write: %s",t.error);return}await fe({cwd:e,result:t.result})}function fb(e){let t=M.join(e,".gitattributes"),n=J.existsSync(t)?J.readFileSync(t,"utf8"):"";if(n.includes(Pd))return;let r=n.length===0||n.endsWith(`
320
320
  `)?"":`
321
321
  `;J.writeFileSync(t,`${n}${r}${Pd}
@@ -519,7 +519,7 @@ Results: ${String(o)} passed, ${String(i)} failed out of ${String(t)} runs
519
519
  Failure patterns:
520
520
  `),a.forEach((l,c)=>{process.stdout.write(` ${String(l)}x: ${c}
521
521
  `)})),i>0&&process.stderr.write(`${N("flake-detect","parallel safety + race conditions")}
522
- `),process.exit(i>0?1:0)}import Ro from"fs";import Tl from"path";import*as Nl from"remeda";import{z as ve}from"zod";var So=["src/**","app/**","apps/**","pages/**","routes/**","components/**","server/**","api/**","backend/**","features/**","modules/**","views/**","ui/**","hooks/**","contexts/**","providers/**","controllers/**","handlers/**","resolvers/**","services/**","middleware/**","lib/**"],wo=["**/*.gen.*","**/generated/**","**/*.d.ts","**/*.test.*","**/*.spec.*","**/node_modules/**","**/dist/**","**/build/**",".ripplo/**","**/*.md","**/.next/**","**/.turbo/**","**/.vercel/**","**/.svelte-kit/**","**/.nuxt/**","**/.astro/**","**/coverage/**","**/storybook-static/**","**/*.stories.*","**/*.story.*","**/__tests__/**","**/__mocks__/**","**/__fixtures__/**","**/*.config.*","**/*.setup.*","**/public/**","**/static/**","**/assets/**","**/migrations/**","**/prisma/migrations/**","**/scripts/**"],yh=ve.object({appUrl:ve.string(),engineUrl:ve.string(),ignorePaths:ve.array(ve.string()).optional(),projectId:ve.string(),watchPaths:ve.array(ve.string()).optional(),webhookSecret:ve.string()}),gh=yh.extend({webhookSecret:ve.string().optional()});Zn();Mt();var bl=ri(no(),1);import{z as B}from"zod";var px=B.object({preconditions:B.array(B.string().min(1))}),ux=B.object({data:B.record(B.string(),B.record(B.string(),B.string())),preconditions:B.array(B.string().min(1))}),mx=B.object({observer:B.string().min(1).max(200),params:B.record(B.string().max(200),B.string())}),hh=B.discriminatedUnion("kind",[B.object({kind:B.literal("pass")}),B.object({kind:B.literal("retry"),reason:B.string()}),B.object({kind:B.literal("fail"),reason:B.string()})]),fx=B.object({error:B.string().optional(),outcome:hh.optional(),success:B.boolean()});function dt(e){let t=[];return e.tests.forEach(n=>{let r=kh(n),o=i=>{t.push({...i,test:n.slug})};Vh.forEach(i=>{i(r,n,o)})}),{diagnostics:t}}function kh(e){let t=[],n=e.spec.entryNode,r=new Set;for(;n!=null&&!r.has(n);){r.add(n);let o=e.spec.nodes[n];if(o==null)break;t.push(o),n=o.next}return t}function Sh(e,t,n){e.forEach(r=>{r.type==="assertText"&&"operator"in r&&r.operator!=="equals"&&n({message:`${r.type} uses operator "${r.operator}" \u2014 only "equals" is allowed for determinism`,rule:"exact-text-match",step:r.label??r.id})})}function wh(e,t,n){Object.keys(t.spec.variables??{}).length>0&&e.forEach(o=>{if(o.type==="fill"&&Rl(o.value)){let i=o.value.value;!vl(i)&&Ch(i)&&n({message:`fill() uses hardcoded value "${i}" \u2014 consider using precondition data via {{namespace.key}}`,rule:"no-hardcoded-data",step:o.label??o.id})}})}function bh(e,t,n){if(Object.keys(t.spec.variables??{}).length===0)return;e.some(i=>JSON.stringify(i).includes("{{"))||n({message:"Test requires preconditions but steps() never references precondition data \u2014 destructure and use it",rule:"prefer-precondition-data",step:void 0})}function Ah(e,t,n){e.forEach(r=>{(r.label==null||r.label.length===0)&&n({message:`Step "${r.id}" lacks .as("...") label \u2014 every step must be labeled`,rule:"missing-label",step:r.id})})}function Rh(e,t,n){let r=new Map;e.forEach(o=>{if(o.label==null)return;let i=r.get(o.label);i==null?r.set(o.label,o.id):n({message:`Duplicate label "${o.label}" \u2014 also used by ${i}`,rule:"no-duplicate-labels",step:o.label})})}function vh(e,t,n){let r=0;e.forEach(o=>{pt(o)?r=0:(r++,r===3&&n({message:"3+ consecutive actions without an assertion \u2014 add verification between actions",rule:"assert-after-action",step:o.label??o.id}))})}function Th(e,t,n){if(e.length===0)return;let r=e.at(-1);r!=null&&!pt(r)&&n({message:"Last step is an action, not an assertion \u2014 expectedOutcome should be verified by assertions at the end",rule:"assert-matches-outcome",step:r.label??r.id})}function Nh(e,t,n){t.implemented&&e.length===0&&n({message:"Test has zero steps",rule:"no-empty-steps",step:void 0})}function Rl(e){return!(typeof e!="object"||e==null||!("type"in e)||e.type!=="static"||!("value"in e)||typeof e.value!="string")}function vl(e){return e.includes("{{")}function Ch(e){return e.includes("@")||/\b[a-f0-9]{8,}\b/.test(e)||/^(test|ripplo|example|sample|demo)/i.test(e)}function pt(e){return e.type.startsWith("assert")}var Lh=new Set(["assertText","assertValue","assertCount","assertUrl","assertNotVisible","assertChecked","assertNotChecked","assertAttribute","assertDisabled","assertEnabled"]);function xh(e){return Lh.has(e.type)}function Ph(e,t,n){!t.implemented||e.length===0||e.some(r=>pt(r))||n({message:"Test has zero assertion steps \u2014 cannot verify expectedOutcome. Add assert.* steps.",rule:"no-assertions",step:void 0})}function Eh(e,t,n){if(!t.implemented||e.length<=3)return;let r=e.filter(i=>pt(i)).length;if(r===0)return;let o=r/e.length;if(o<.15){let i=Math.round(o*100);n({message:`Only ${String(r)}/${String(e.length)} steps are assertions (${String(i)}%) \u2014 add more verification between actions`,rule:"low-assertion-ratio",step:void 0})}}function bo(e){if(!("locator"in e)||e.locator==null)return;let t=e.locator;return t.by==="role"?`role:${t.role}:${t.name??""}`:`testId:${t.value}`}function Oh(e,t,n){e.forEach((r,o)=>{if(r.type!=="click")return;let i=bo(r);if(i==null)return;let a=e.slice(o+1,o+4);a.find(c=>c.type==="assertVisible"&&bo(c)===i)==null||a.some(c=>xh(c)||pt(c)&&bo(c)!==i)||n({message:`click "${r.label??r.id}" is followed only by assert.visible on the same locator \u2014 verifies nothing about the click's effect`,rule:"tautological-post-click-assert",step:r.label??r.id})})}var Ih=new Set(["the","is","a","an","and","or","of","to","in","on","at","for","that","this","with","be","are","was","were","it","its","as","by","from","after","before","should","will","can","still","but","not","no","so","if","then","than","user","users","page","view","shows","show","see","click","clicks","clicked","clickable","visible","appears","appear","displayed","stays","remains"]);function Ao(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(t=>t.length>=3&&!Ih.has(t))}function _h(e){let t=e.label==null?[]:Ao(e.label);if(!("locator"in e)||e.locator==null)return t;let n=e.locator,r=n.by==="role"?n.name??"":n.value;return[...t,...Ao(r)]}function Uh(e,t,n){if(!t.implemented||e.length===0)return;let r=new Set(Ao(t.expectedOutcome));if(r.size===0)return;let o=e.filter(a=>pt(a));if(o.length===0)return;o.some(a=>_h(a).some(s=>r.has(s)))||n({message:`No assertion references any keyword from expectedOutcome (${[...r].join(", ")}) \u2014 the outcome may not actually be verified`,rule:"expected-outcome-keyword-coverage",step:void 0})}var jh=/save|submit|create|delete|remove|send|invite|update|confirm|publish|apply|pay|subscribe|upgrade|cancel|archive|rename/i;function Al(e){if(e.type==="upload"||e.type==="handleDialog"&&e.action==="accept")return!0;if(e.type!=="click")return!1;let t=e.locator,n=t.by==="role"?t.name??"":t.value;return jh.test(n)}function $h(e,t,n){!t.implemented||t.spec.uiOnly===!0||e.forEach((r,o)=>{if(!Al(r)||"uiOnly"in r&&r.uiOnly===!0)return;let i=e.slice(o+1),a=i.findIndex(d=>Al(d)),s=a===-1?i.length:a;i.slice(0,s).some(d=>d.type==="assertObserver")||n({message:`"${r.label??r.id}" looks like it mutates backend state but no assert.backend(...) observer verifies it \u2014 the test can pass while the server is broken. You need to add an observer that checks the persisted result (see .ripplo/observers/ for examples). Only if this action truly does NOT change any server state (pure client-side interaction, presentation toggle, etc.) should you fall back to marking the step { uiOnly: true } to silence this rule.`,rule:"mutation-without-observer-coverage",step:r.label??r.id})})}function Dh(e,t,n){Object.keys(t.spec.variables??{}).length!==0&&e.forEach(o=>{if(o.type!=="assertObserver")return;let i=Object.entries(o.params);i.length===0||i.some(([,s])=>Rl(s)&&vl(s.value))||n({message:`assert.backend "${o.label??o.id}" passes only hardcoded params while the test defines precondition variables \u2014 observer assertions should reference {{namespace.key}} so they match the actual precondition data`,rule:"observer-params-reference-variables",step:o.label??o.id})})}function Bh(e,t,n){let r=new Set(Object.keys(t.spec.variables??{})),o=/\{\{([^{}]+?)\}\}/g;e.forEach(i=>{let s=[...JSON.stringify(i).matchAll(o)],l=[...new Set(s.map(p=>p[1]).filter(p=>p!=null&&!r.has(p)))];if(l.length===0)return;let c=l.map(p=>`{{${p}}}`).join(", "),d=[...new Set(l.map(p=>p.split(".")[0]??p))].join(", ");n({message:`"${i.label??i.id}" contains literal template string(s) ${c} \u2014 destructure the proxy in .steps(({ ${d} }) => \u2026) and pass the proxy value (e.g. \`${l[0]??""}\`) directly. Writing the template as a string bypasses type-checking and silently accepts typos.`,rule:"no-literal-template-strings",step:i.label??i.id})})}var Vh=[Sh,wh,Bh,bh,Ah,Rh,vh,Th,Nh,Ph,Eh,Oh,Uh,$h,Dh];_e();K();async function Cl(e){let{ids:t,requireImplemented:n}=e,r=process.cwd(),o=await I(r);o.ok||(process.stderr.write(`Compilation failed: ${o.error}
522
+ `),process.exit(i>0?1:0)}import Ro from"fs";import Tl from"path";import*as Nl from"remeda";import{z as ve}from"zod";var So=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],wo=["**/*.gen.*","**/generated/**","**/*.d.ts","**/*.test.*","**/*.spec.*","**/node_modules/**","**/dist/**","**/build/**",".ripplo/**","**/*.md","**/.next/**","**/.turbo/**","**/.vercel/**","**/.svelte-kit/**","**/.nuxt/**","**/.astro/**","**/coverage/**","**/storybook-static/**","**/*.stories.*","**/*.story.*","**/__tests__/**","**/__mocks__/**","**/__fixtures__/**","**/*.config.*","**/*.setup.*","**/public/**","**/static/**","**/assets/**","**/migrations/**","**/prisma/migrations/**","**/scripts/**"],yh=ve.object({appUrl:ve.string(),engineUrl:ve.string(),ignorePaths:ve.array(ve.string()).optional(),projectId:ve.string(),watchPaths:ve.array(ve.string()).optional(),webhookSecret:ve.string()}),gh=yh.extend({webhookSecret:ve.string().optional()});Zn();Mt();var bl=ri(no(),1);import{z as B}from"zod";var px=B.object({preconditions:B.array(B.string().min(1))}),ux=B.object({data:B.record(B.string(),B.record(B.string(),B.string())),preconditions:B.array(B.string().min(1))}),mx=B.object({observer:B.string().min(1).max(200),params:B.record(B.string().max(200),B.string())}),hh=B.discriminatedUnion("kind",[B.object({kind:B.literal("pass")}),B.object({kind:B.literal("retry"),reason:B.string()}),B.object({kind:B.literal("fail"),reason:B.string()})]),fx=B.object({error:B.string().optional(),outcome:hh.optional(),success:B.boolean()});function dt(e){let t=[];return e.tests.forEach(n=>{let r=kh(n),o=i=>{t.push({...i,test:n.slug})};Vh.forEach(i=>{i(r,n,o)})}),{diagnostics:t}}function kh(e){let t=[],n=e.spec.entryNode,r=new Set;for(;n!=null&&!r.has(n);){r.add(n);let o=e.spec.nodes[n];if(o==null)break;t.push(o),n=o.next}return t}function Sh(e,t,n){e.forEach(r=>{r.type==="assertText"&&"operator"in r&&r.operator!=="equals"&&n({message:`${r.type} uses operator "${r.operator}" \u2014 only "equals" is allowed for determinism`,rule:"exact-text-match",step:r.label??r.id})})}function wh(e,t,n){Object.keys(t.spec.variables??{}).length>0&&e.forEach(o=>{if(o.type==="fill"&&Rl(o.value)){let i=o.value.value;!vl(i)&&Ch(i)&&n({message:`fill() uses hardcoded value "${i}" \u2014 consider using precondition data via {{namespace.key}}`,rule:"no-hardcoded-data",step:o.label??o.id})}})}function bh(e,t,n){if(Object.keys(t.spec.variables??{}).length===0)return;e.some(i=>JSON.stringify(i).includes("{{"))||n({message:"Test requires preconditions but steps() never references precondition data \u2014 destructure and use it",rule:"prefer-precondition-data",step:void 0})}function Ah(e,t,n){e.forEach(r=>{(r.label==null||r.label.length===0)&&n({message:`Step "${r.id}" lacks .as("...") label \u2014 every step must be labeled`,rule:"missing-label",step:r.id})})}function Rh(e,t,n){let r=new Map;e.forEach(o=>{if(o.label==null)return;let i=r.get(o.label);i==null?r.set(o.label,o.id):n({message:`Duplicate label "${o.label}" \u2014 also used by ${i}`,rule:"no-duplicate-labels",step:o.label})})}function vh(e,t,n){let r=0;e.forEach(o=>{pt(o)?r=0:(r++,r===3&&n({message:"3+ consecutive actions without an assertion \u2014 add verification between actions",rule:"assert-after-action",step:o.label??o.id}))})}function Th(e,t,n){if(e.length===0)return;let r=e.at(-1);r!=null&&!pt(r)&&n({message:"Last step is an action, not an assertion \u2014 expectedOutcome should be verified by assertions at the end",rule:"assert-matches-outcome",step:r.label??r.id})}function Nh(e,t,n){t.implemented&&e.length===0&&n({message:"Test has zero steps",rule:"no-empty-steps",step:void 0})}function Rl(e){return!(typeof e!="object"||e==null||!("type"in e)||e.type!=="static"||!("value"in e)||typeof e.value!="string")}function vl(e){return e.includes("{{")}function Ch(e){return e.includes("@")||/\b[a-f0-9]{8,}\b/.test(e)||/^(test|ripplo|example|sample|demo)/i.test(e)}function pt(e){return e.type.startsWith("assert")}var Lh=new Set(["assertText","assertValue","assertCount","assertUrl","assertNotVisible","assertChecked","assertNotChecked","assertAttribute","assertDisabled","assertEnabled"]);function xh(e){return Lh.has(e.type)}function Ph(e,t,n){!t.implemented||e.length===0||e.some(r=>pt(r))||n({message:"Test has zero assertion steps \u2014 cannot verify expectedOutcome. Add assert.* steps.",rule:"no-assertions",step:void 0})}function Eh(e,t,n){if(!t.implemented||e.length<=3)return;let r=e.filter(i=>pt(i)).length;if(r===0)return;let o=r/e.length;if(o<.15){let i=Math.round(o*100);n({message:`Only ${String(r)}/${String(e.length)} steps are assertions (${String(i)}%) \u2014 add more verification between actions`,rule:"low-assertion-ratio",step:void 0})}}function bo(e){if(!("locator"in e)||e.locator==null)return;let t=e.locator;return t.by==="role"?`role:${t.role}:${t.name??""}`:`testId:${t.value}`}function Oh(e,t,n){e.forEach((r,o)=>{if(r.type!=="click")return;let i=bo(r);if(i==null)return;let a=e.slice(o+1,o+4);a.find(c=>c.type==="assertVisible"&&bo(c)===i)==null||a.some(c=>xh(c)||pt(c)&&bo(c)!==i)||n({message:`click "${r.label??r.id}" is followed only by assert.visible on the same locator \u2014 verifies nothing about the click's effect`,rule:"tautological-post-click-assert",step:r.label??r.id})})}var Ih=new Set(["the","is","a","an","and","or","of","to","in","on","at","for","that","this","with","be","are","was","were","it","its","as","by","from","after","before","should","will","can","still","but","not","no","so","if","then","than","user","users","page","view","shows","show","see","click","clicks","clicked","clickable","visible","appears","appear","displayed","stays","remains"]);function Ao(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(t=>t.length>=3&&!Ih.has(t))}function _h(e){let t=e.label==null?[]:Ao(e.label);if(!("locator"in e)||e.locator==null)return t;let n=e.locator,r=n.by==="role"?n.name??"":n.value;return[...t,...Ao(r)]}function Uh(e,t,n){if(!t.implemented||e.length===0)return;let r=new Set(Ao(t.expectedOutcome));if(r.size===0)return;let o=e.filter(a=>pt(a));if(o.length===0)return;o.some(a=>_h(a).some(s=>r.has(s)))||n({message:`No assertion references any keyword from expectedOutcome (${[...r].join(", ")}) \u2014 the outcome may not actually be verified`,rule:"expected-outcome-keyword-coverage",step:void 0})}var jh=/save|submit|create|delete|remove|send|invite|update|confirm|publish|apply|pay|subscribe|upgrade|cancel|archive|rename/i;function Al(e){if(e.type==="upload"||e.type==="handleDialog"&&e.action==="accept")return!0;if(e.type!=="click")return!1;let t=e.locator,n=t.by==="role"?t.name??"":t.value;return jh.test(n)}function $h(e,t,n){!t.implemented||t.spec.uiOnly===!0||e.forEach((r,o)=>{if(!Al(r)||"uiOnly"in r&&r.uiOnly===!0)return;let i=e.slice(o+1),a=i.findIndex(d=>Al(d)),s=a===-1?i.length:a;i.slice(0,s).some(d=>d.type==="assertObserver")||n({message:`"${r.label??r.id}" looks like it mutates backend state but no assert.backend(...) observer verifies it \u2014 the test can pass while the server is broken. You need to add an observer that checks the persisted result (see .ripplo/observers/ for examples). Only if this action truly does NOT change any server state (pure client-side interaction, presentation toggle, etc.) should you fall back to marking the step { uiOnly: true } to silence this rule.`,rule:"mutation-without-observer-coverage",step:r.label??r.id})})}function Dh(e,t,n){Object.keys(t.spec.variables??{}).length!==0&&e.forEach(o=>{if(o.type!=="assertObserver")return;let i=Object.entries(o.params);i.length===0||i.some(([,s])=>Rl(s)&&vl(s.value))||n({message:`assert.backend "${o.label??o.id}" passes only hardcoded params while the test defines precondition variables \u2014 observer assertions should reference {{namespace.key}} so they match the actual precondition data`,rule:"observer-params-reference-variables",step:o.label??o.id})})}function Bh(e,t,n){let r=new Set(Object.keys(t.spec.variables??{})),o=/\{\{([^{}]+?)\}\}/g;e.forEach(i=>{let s=[...JSON.stringify(i).matchAll(o)],l=[...new Set(s.map(p=>p[1]).filter(p=>p!=null&&!r.has(p)))];if(l.length===0)return;let c=l.map(p=>`{{${p}}}`).join(", "),d=[...new Set(l.map(p=>p.split(".")[0]??p))].join(", ");n({message:`"${i.label??i.id}" contains literal template string(s) ${c} \u2014 destructure the proxy in .steps(({ ${d} }) => \u2026) and pass the proxy value (e.g. \`${l[0]??""}\`) directly. Writing the template as a string bypasses type-checking and silently accepts typos.`,rule:"no-literal-template-strings",step:i.label??i.id})})}var Vh=[Sh,wh,Bh,bh,Ah,Rh,vh,Th,Nh,Ph,Eh,Oh,Uh,$h,Dh];_e();K();async function Cl(e){let{ids:t,requireImplemented:n}=e,r=process.cwd(),o=await I(r);o.ok||(process.stderr.write(`Compilation failed: ${o.error}
523
523
  `),process.stderr.write(`${N("create","DSL authoring + lint rules")}
524
524
  `),process.exit(1)),await fe({cwd:r,result:o.result});let i=dt(o.result),a=t.length===0?i.diagnostics:i.diagnostics.filter(u=>t.includes(u.test)),s=Nl.groupBy(a,u=>u.test);Object.entries(s).forEach(([u,m])=>{process.stderr.write(`
525
525
  ${u}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripplo",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "CLI for Ripplo — AI-powered end-to-end testing",
5
5
  "type": "module",
6
6
  "homepage": "https://ripplo.ai",
@@ -55,11 +55,11 @@
55
55
  "eslint-plugin-react-hooks": "^5.0.0",
56
56
  "tsup": "^8.5.1",
57
57
  "typescript": "catalog:",
58
- "@ripplo/eslint-config": "0.0.0",
59
58
  "@ripplo/graphql": "^0.0.0",
60
- "@ripplo/runtime": "^0.0.0",
61
- "@ripplo/testing": "^0.3.4",
62
- "@ripplo/spec": "^0.0.0"
59
+ "@ripplo/eslint-config": "0.0.0",
60
+ "@ripplo/testing": "^0.3.5",
61
+ "@ripplo/spec": "^0.0.0",
62
+ "@ripplo/runtime": "^0.0.0"
63
63
  },
64
64
  "scripts": {
65
65
  "dev": "tsx watch src/index.ts",