@taqwright/lime-cli 0.7.9 → 0.7.11

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 +15 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var R=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(r){throw t=0,r}};var Pe=R((ds,Ir)=>{Ir.exports={name:"@taqwright/lime-cli",version:"0.7.9",description:"Thin CLI client for the LIME server \u2014 fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",license:"SEE LICENSE IN LICENSE",author:"Taqwright",homepage:"https://www.taqwright.ai",bugs:{url:"https://www.taqwright.ai/contact"},keywords:["appium","mobile-testing","e2e","qa","test-automation","taqwright","lime","cli"],bin:{"lime-cli":"dist/index.js"},main:"dist/index.js",files:["dist"],publishConfig:{access:"public"},scripts:{"lime-cli":"node src/index.js",start:"node src/index.js",test:"node --test test/*.test.js",bundle:"esbuild src/index.js --bundle --platform=node --target=node18 --minify --outfile=dist/index.js",prepack:"npm run bundle",prepublishOnly:"npm test"},engines:{node:">=18"},devDependencies:{esbuild:"^0.28.1"}}});var D=R((ps,De)=>{"use strict";var _r="https://www.taqwright.ai";De.exports={POLL_INTERVAL_MS:1e3,POLL_TIMEOUT_MS:18e5,BOOT_POLL_INTERVAL_MS:5e3,BOOT_TIMEOUT_MS:3e5,CONNECT_POLL_INTERVAL_MS:2e3,CONNECT_TIMEOUT_MS:3e5,DEFAULT_SERVER:_r,HTTP_TIMEOUT_MS:3e5,MAX_RESPONSE_BYTES:52428800}});var z=R((hs,Fe)=>{"use strict";var{URL:kr}=require("url"),{HTTP_TIMEOUT_MS:qe,MAX_RESPONSE_BYTES:Me}=D();function Ue(e){let t=String(e||"").replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t.endsWith(".localhost")}var Rr=["just a moment","attention required! | cloudflare","checking your browser","/cdn-cgi/challenge-platform","cf-error-details","cf-browser-verification","_incapsula_resource"];function Be(e,t){if(!t||typeof t!="string")return null;let r=t.slice(0,4e3).toLowerCase();return Rr.some(n=>r.includes(n))?`the LIME server is behind a bot challenge that blocked this request (HTTP ${e}). The request never reached LIME \u2014 it was stopped at the edge, so your token and arguments are probably fine. CI runners use datacenter IPs, which Cloudflare's Bot Fight Mode challenges by default; it cannot be excepted with a WAF skip rule, so the server's operator has to turn it off or move to Super Bot Fight Mode (which supports skip rules) for the /api/ paths.`:null}var de=class{constructor(t,r){this.server=t,this.token=r}_url(t){return/^https?:\/\//i.test(t)?t:`${this.server}${t}`}json(t,r,n,i={}){let s=n?JSON.stringify(n):null,o={...i};return s&&(o["Content-Type"]="application/json",o["Content-Length"]=Buffer.byteLength(s)),this._send(t,this._url(r),o,s)}raw(t,r,n,i={}){let s={...i,"Content-Type":"application/octet-stream","Content-Length":n.length};return this._send(t,this._url(r),s,n)}_send(t,r,n,i){let s=new kr(r);if(s.protocol==="http:"&&!Ue(s.hostname))return Promise.reject(new Error(`refusing to send your LIME_CI_TOKEN over cleartext http to ${s.hostname} \u2014 use https (http is allowed only for localhost).`));let o=s.protocol==="https:"?require("https"):require("http"),a={method:t,hostname:s.hostname,port:s.port||(s.protocol==="https:"?443:80),path:s.pathname+(s.search||""),headers:{...n,Authorization:`Bearer ${this.token}`,Origin:`${s.protocol}//${s.host}`}};return new Promise((l,p)=>{let c=o.request(a,u=>{let d="",g=0;u.setEncoding("utf8"),u.on("data",f=>{if(g+=Buffer.byteLength(f),g>Me){c.destroy(new Error(`Server response exceeded ${Me} bytes`));return}d+=f}),u.on("end",()=>{let f;try{f=d?JSON.parse(d):{}}catch{let w=Be(u.statusCode,d);return p(new Error(w||`Server returned non-JSON (${u.statusCode}): ${d.substring(0,200)}`))}l({status:u.statusCode,body:f})})});c.setTimeout(qe,()=>{c.destroy(new Error(`request timed out after ${qe/1e3}s`))}),c.on("error",p),i&&c.write(i),c.end()})}};function $r(e,t){if(!e.body||!e.body.success)throw new Error(`${t} \u2014 ${e.body&&e.body.error||"unknown"}`);return e.body}Fe.exports={isLoopbackHost:Ue,HttpClient:de,expectSuccess:$r,describeEdgeBlock:Be}});var q=R((fs,Ke)=>{"use strict";var Or={browserstack:{label:"BrowserStack",envUsername:"BROWSERSTACK_USERNAME",envAccessKey:"BROWSERSTACK_ACCESS_KEY",hubHost:"hub.browserstack.com"},lambdatest:{label:"LambdaTest",envUsername:"LT_USERNAME",envAccessKey:"LT_ACCESS_KEY",hubHost:"mobile-hub.lambdatest.com"}};function Lr({provider:e,platform:t,device:r,os:n,appUrl:i,username:s,accessKey:o,capabilities:a}){let l={connectionMode:"cloud",cloudProvider:e||"browserstack",cloudUsername:s,cloudAccessKey:o,platform:t,deviceName:r,deviceOsVersion:n,cloudAppUrl:i,automationName:t==="ios"?"XCUITest":"UiAutomator2"};return a&&(l.cloudCapabilities=a),l}Ke.exports={CLOUD_PROVIDERS:Or,buildCloudSettingsPayload:Lr}});var fe=R((ms,He)=>{"use strict";var Z=require("fs"),pe=require("path"),Cr=["taqwright","webdriverio-js","appium-java-junit5","appium-python-pytest","appium-ruby-rspec","appium-csharp-nunit"],xr={taqwright:"deterministic","webdriverio-js":"gemini","appium-java-junit5":"gemini","appium-python-pytest":"gemini","appium-ruby-rspec":"gemini","appium-csharp-nunit":"gemini"},j={taqwright:"taqwright (native)","webdriverio-js":"WebdriverIO (JavaScript)","appium-java-junit5":"Appium Java (JUnit 5)","appium-python-pytest":"Appium Python (pytest)","appium-ruby-rspec":"Appium Ruby (RSpec)","appium-csharp-nunit":"Appium C# (NUnit)"},Nr=5*1024*1024,he={exists:e=>Z.existsSync(e),readText:e=>{try{return Z.statSync(e).size>Nr?null:Z.readFileSync(e,"utf8")}catch{return null}},list:e=>{try{return Z.readdirSync(e)}catch{return[]}}};function jr(e,t){let r=e.readText(pe.join(t,"package.json"));if(!r)return{};try{let n=JSON.parse(r);return{...n.dependencies||{},...n.devDependencies||{}}}catch{return{}}}var Pr=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage"]);function Dr(e,t){let r=new Set([""]),n=[];try{n=e.list(t)}catch{n=[]}for(let i of n)i.startsWith(".")||Pr.has(i)||r.add(i);return[...r]}function qr(e,t,r){let n=r?pe.join(t,r):t,i=u=>pe.join(n,u),s=u=>r?`${r}/${u}`:u,o=[],a=jr(e,n),l=[];try{l=e.list(n)}catch{l=[]}(a["@taqwright/taqwright"]||a.taqwright||e.exists(i("taqwright.config.ts"))||e.exists(i("taqwright.config.js")))&&o.push({id:"taqwright",label:j.taqwright,reason:"taqwright dependency/config found",manifest:s("package.json")});let p=l.some(u=>/^wdio\.conf\.(js|ts|mjs|cjs)$/.test(u));(a.webdriverio||a["@wdio/cli"]||p)&&o.push({id:"webdriverio-js",label:j["webdriverio-js"],reason:p?"wdio.conf.* found":"webdriverio dependency found",manifest:s("package.json")});for(let u of["pom.xml","build.gradle","build.gradle.kts"]){let d=e.readText(i(u));if(d&&/io\.appium|java-client/.test(d)){o.push({id:"appium-java-junit5",label:j["appium-java-junit5"],reason:`io.appium:java-client in ${s(u)}`,manifest:s(u)});break}}for(let u of["requirements.txt","pyproject.toml"]){let d=e.readText(i(u));if(d&&/appium-python-client/i.test(d)){o.push({id:"appium-python-pytest",label:j["appium-python-pytest"],reason:`Appium-Python-Client in ${s(u)}`,manifest:s(u)});break}}let c=e.readText(i("Gemfile"));c&&/appium_lib(_core)?/.test(c)&&o.push({id:"appium-ruby-rspec",label:j["appium-ruby-rspec"],reason:"appium_lib in Gemfile",manifest:s("Gemfile")});for(let u of l){if(!/\.csproj$/i.test(u))continue;let d=e.readText(i(u));if(d&&/Appium\.WebDriver/i.test(d)){o.push({id:"appium-csharp-nunit",label:j["appium-csharp-nunit"],reason:`Appium.WebDriver in ${s(u)}`,manifest:s(u)});break}}return o}function Ye(e,t=he){let r=new Map;for(let n of Dr(t,e))for(let i of qr(t,e,n))r.has(i.id)||r.set(i.id,i);return[...r.values()]}function Mr(e,t=he){let r=Ye(e,t);return r.some(n=>n.id==="taqwright")||r.push({id:"taqwright",label:j.taqwright,reason:"default output"}),r}function Ur(e){let{requested:t,configured:r,remembered:n,offeredIds:i,detectedRealIds:s,taqwrightDetected:o,javaSignal:a}=e,l=p=>!!p&&i.includes(p);return l(t)?t:l(r)?r:l(n)&&(s.includes(n)||n==="taqwright"&&o)?n:s.length?s[0]:o?"taqwright":a?"appium-java-junit5":"taqwright"}He.exports={FRAMEWORK_IDS:Cr,FRAMEWORK_ENGINES:xr,FRAMEWORK_LABELS:j,nodeFs:he,detectFrameworks:Ye,rankFrameworks:Mr,pickDefaultFramework:Ur}});var ve=R((gs,Xe)=>{"use strict";var me=require("path"),{nodeFs:Br}=fe(),Ve={taqwright:"tests","webdriverio-js":"test/specs","appium-java-junit5":"src/test/java","appium-python-pytest":"tests","appium-ruby-rspec":"spec","appium-csharp-nunit":"Tests"},Fr="tests",Kr={taqwright:".spec.ts","webdriverio-js":".spec.ts","appium-java-junit5":".java","appium-python-pytest":".py","appium-ruby-rspec":"_spec.rb","appium-csharp-nunit":".cs"},Yr=new Set(["appium-python-pytest","appium-ruby-rspec"]);function ge(e){return Ve[e]||Fr}function we(e){let t=(e||"").split(/[^A-Za-z0-9]+/).filter(Boolean);return t.length?t.map(r=>/^[A-Z0-9]+$/.test(r)?r[0]+r.slice(1).toLowerCase():r[0].toUpperCase()+r.slice(1)).join(""):""}function ye(e){return(e||"").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split(/[^A-Za-z0-9]+/).filter(Boolean).join("_").toLowerCase()}function Hr(e){return ye(e).split("_").filter(Boolean).join("-")}var Vr=6,Wr=new Set(["and","or","the","a","an","to","of","in","on","at","as","with","for","then","into","from","by","that","is"]),Gr=/@|\d{6,}/;function Jr(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean).filter(r=>!Gr.test(r)).slice(0,Vr);for(;t.length>1&&Wr.has(t[t.length-1].toLowerCase());)t.pop();return we(t.join(" "))||"RecordedTest"}function Xr(e){let t=String(e||"").trim();return t?/[A-Za-z]/.test(t)?t.length>80?"Keep the name under 80 characters.":/[/\\]/.test(t)?"Use the destination field for the folder.":null:"Use at least one letter.":"Enter a test name."}function Qr(e){if(e==null||e==="")return null;let t=String(e);return t!==t.trim()?"must not have leading or trailing spaces.":t.length>120?"is longer than any key LIME mints (120 characters).":/^[a-z0-9][a-z0-9._/:-]*$/.test(t)?t.includes("..")?'must not contain "..".':null:"is not a scenario key \u2014 expected lower-case letters, digits and . _ - / : only."}function We(e,t){let r;return Yr.has(e)?r=ye(t):r=we(t),`${r}${Kr[e]||""}`}function K(e){return String(e||"").split(/[/\\]+/).filter(t=>t&&t!=="."&&t!=="..").join("/")}function zr(e){if(e==null)return null;let t=String(e);return t.trim()===""?null:/^([A-Za-z]:)?[/\\]/.test(t)?"must be a path inside the project, not an absolute path.":t.split(/[/\\]+/).includes("..")?'must not contain ".." \u2014 the test belongs inside the project.':null}function be(e){let t=K(e).split("/");return t.pop(),t.join("/")}function Ge(e){let t=/(?:^|\/)src\/(?:test|main)\/java\/(.+)$/.exec(K(e));return t&&t[1].split("/").filter(Boolean).join(".")||void 0}function Zr(e){let t=K(e),r=t.search(/\/src\/(?:test|main)\/java(?:\/|$)/);return r>0?t.slice(0,r):""}function en(e){let t=new Map;for(let n of e){let i=be(n);t.set(i,(t.get(i)||0)+1)}if(!t.size)return null;let r=[...t.entries()].sort((n,i)=>i[1]-n[1]||n[0].localeCompare(i[0]));return{dir:r[0][0],count:r[0][1]}}var Je={taqwright:{segment:"tests",file:/\.spec\.(ts|js)$/},"webdriverio-js":{segment:"test/specs",file:/\.(ts|js)$/},"appium-java-junit5":{segment:"src/test/java",file:/\.java$/},"appium-python-pytest":{segment:"tests",file:/^(test_.+|.+_test)\.py$/},"appium-ruby-rspec":{segment:"spec",file:/_spec\.rb$/},"appium-csharp-nunit":{segment:"",file:/Tests\.cs$/}},tn=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage",".git"]),rn=2e4;function nn(e,t,r=Br){let n=Je[t];if(!n)return[];let i=[],s=rn,o=a=>{if(s<=0)return;let l=a?me.join(e,a):e,p=[];try{p=r.list(l)}catch{return}for(let c of p){if(s--<=0)return;if(c.startsWith(".")||tn.has(c))continue;let u=a?`${a}/${c}`:c;if(r.isDir?r.isDir(me.join(e,u)):r.list(me.join(e,u)).length>0){o(u);continue}n.file.test(c)&&(n.segment&&!`/${be(u)}/`.includes(`/${n.segment}/`)||i.push(u))}};return o(""),i.sort()}function sn(e){let{framework:t,testName:r}=e,n=We(t,r),i=(a,l,p=0)=>{let c=K(a),u=t==="appium-java-junit5"?Ge(c):void 0;return{dir:c,fileName:n,...u?{packageName:u}:{},source:l,neighbourCount:p}};if(e.remembered!=null)return i(e.remembered,"remembered");let s=en(e.existingTests||[]);if(s&&s.dir)return i(s.dir,"existing",s.count);let o=e.detection&&e.detection.manifest?be(e.detection.manifest):"";return o?i(`${o}/${ge(t)}`,"module"):i(ge(t),"convention")}Xe.exports={CONVENTIONAL_DIRS:Ve,TEST_PATTERNS:Je,conventionalDir:ge,pascalCase:we,snakeCase:ye,kebabCase:Hr,testNameFromGoal:Jr,validateTestName:Xr,validateDest:zr,validateScenarioKey:Qr,fileNameFor:We,normalizeDir:K,javaPackageOf:Ge,moduleRootOf:Zr,findExistingTests:nn,resolveExportTarget:sn}});var et=R((ws,Ze)=>{"use strict";var{CLOUD_PROVIDERS:ee}=q(),{validateScenarioKey:on}=ve();function an(e){let t=Object.create(null);for(let r=0;r<e.length;r++){let n=e[r];if(!n.startsWith("--"))continue;let i=n.slice(2),s=e[r+1];s===void 0||s.startsWith("--")?t[i]=!0:(t[i]=s,r++)}return t}var ze=["export","adapt","verify","check-locators","disconnect","report"];function cn(e){let t=Array.isArray(e)?e:[],r=[];for(let n=0;n<t.length;n+=1){let i=String(t[n]==null?"":t[n]);!i||i.startsWith("-")||(n>0?String(t[n-1]||""):"").startsWith("--")||r.push(i)}return r}function ln(e){let t=e[0];return t&&ze.includes(t)?{command:t,rest:e.slice(1)}:{command:"run",rest:e}}function un(e="taqwright"){return e==="json"?"recorded-steps.json":"RecordedTest.spec.ts"}function dn(e,t=process.env){let r=[],n=!!e["list-devices"],i=!!e["keep-session"],s=!!e["no-boot"],o=e["lime-dir"]===!0?".lime":typeof e["lime-dir"]=="string"&&e["lime-dir"].trim()?e["lime-dir"].trim():void 0,a=e.format?String(e.format).toLowerCase():"taqwright";if(["taqwright","json"].includes(a)||r.push(`--format must be taqwright or json (got "${e.format}")`),n)return{errors:r,listDevices:n,keepSession:i,noBoot:s,format:a,limeDir:o};(!e.goal||typeof e.goal!="string"||!e.goal.trim())&&r.push("--goal is required");let l;e.project!==void 0&&(typeof e.project!="string"||!e.project.trim()?r.push("--project requires a project name"):l=e.project.trim());let p;if(e.scenario!==void 0){let h=typeof e.scenario=="string"?e.scenario.trim():"",T=h?on(h):"requires a scenario key";T?r.push(`--scenario ${T}`):p=h}let c,u;e["gemini-key"]!==void 0&&(typeof e["gemini-key"]!="string"||!e["gemini-key"].trim()?r.push("--gemini-key requires a value (or set GEMINI_API_KEY)"):(c=e["gemini-key"].trim(),u="flag")),!c&&typeof t.GEMINI_API_KEY=="string"&&t.GEMINI_API_KEY.trim()&&(c=t.GEMINI_API_KEY.trim(),u="env");let d,g;if(e["qwen-key"]!==void 0&&(typeof e["qwen-key"]!="string"||!e["qwen-key"].trim()?r.push("--qwen-key requires a value (or set QWEN_API_KEY)"):(d=e["qwen-key"].trim(),g="flag")),!d&&typeof t.QWEN_API_KEY=="string"&&t.QWEN_API_KEY.trim()&&(d=t.QWEN_API_KEY.trim(),g="env"),e["max-steps"]!==void 0){let h=Number(e["max-steps"]);(!Number.isFinite(h)||h<1||h>200)&&r.push("--max-steps must be a number between 1 and 200")}let f;e.platform&&(f=String(e.platform).toLowerCase(),["android","ios"].includes(f)||r.push(`--platform must be android or ios (got "${e.platform}")`));let v=!!e.cloud,w=typeof e.os=="string"?e.os:void 0,y=typeof e["app-url"]=="string"?e["app-url"]:void 0,I=typeof e.app=="string"?e.app:void 0,b=typeof e.caps=="string"&&e.caps.trim()?e.caps.trim():void 0,E,A,S;if(v&&(E=e.cloud===!0?"browserstack":String(e.cloud).toLowerCase(),ee[E]||r.push(`--cloud must be ${Object.keys(ee).join(" or ")} (got "${e.cloud}")`)),v){(!e.device||typeof e.device!="string")&&r.push('--cloud requires --device (the cloud device name, e.g. "Google Pixel 8")'),f||r.push("--cloud requires --platform (android or ios)"),w||r.push("--cloud requires --os (the device OS version, e.g. 14.0)"),I?r.push("--app is not supported for cloud runs. Upload the build to your provider \u2014 LambdaTest: POST https://manual-api.lambdatest.com/app/upload/realDevice (-F appFile=@<file>), BrowserStack: POST https://api-cloud.browserstack.com/app-automate/upload (-F file=@<file>) \u2014 then pass the returned id with --app-url. (--app is still supported for local runs, where it clean-reinstalls on the agent device.)"):y||r.push("--cloud requires --app-url <bs://\u2026 | lt://\u2026> (the id from your provider\u2019s app upload API)"),e["app-url"]!==void 0&&(typeof e["app-url"]!="string"||!e["app-url"].trim())&&r.push("--app-url requires a bs:// or lt:// app id"),y&&ee[E]&&(E==="browserstack"&&y.startsWith("lt://")?r.push(`--app-url "${y}" is a LambdaTest app id, but the provider is browserstack`):E==="lambdatest"&&y.startsWith("bs://")&&r.push(`--app-url "${y}" is a BrowserStack app id, but the provider is lambdatest`)),e["cloud-username"]!==void 0&&(typeof e["cloud-username"]!="string"||!e["cloud-username"].trim())&&r.push("--cloud-username requires a value"),e["cloud-access-key"]!==void 0&&(typeof e["cloud-access-key"]!="string"||!e["cloud-access-key"].trim())&&r.push("--cloud-access-key requires a value"),e.caps!==void 0&&(typeof e.caps!="string"||!e.caps.trim())&&r.push("--caps requires a path to a JSON file");let h=ee[E];h&&(A=typeof e["cloud-username"]=="string"&&e["cloud-username"].trim()||t[h.envUsername]||void 0,S=typeof e["cloud-access-key"]=="string"&&e["cloud-access-key"].trim()||t[h.envAccessKey]||void 0,A||r.push(`cloud runs require a username \u2014 pass --cloud-username or set ${h.envUsername} (credentials are never read from your LIME account for CLI runs)`),S||r.push(`cloud runs require an access key \u2014 pass --cloud-access-key or set ${h.envAccessKey}`))}else e.app!==void 0&&(typeof e.app!="string"||!e.app.trim()?r.push("--app requires a path to an APK"):e.device?f&&f!=="android"&&r.push("--app is Android only"):r.push("--app requires --device (lime-cli must connect to a device to reinstall the app)")),e["app-url"]!==void 0&&r.push("--app-url requires --cloud"),e.os!==void 0&&r.push("--os requires --cloud"),e["cloud-username"]!==void 0&&r.push("--cloud-username requires --cloud"),e["cloud-access-key"]!==void 0&&r.push("--cloud-access-key requires --cloud"),e.caps!==void 0&&r.push("--caps requires --cloud");return{errors:r,listDevices:n,keepSession:i,noBoot:s,limeDir:o,device:e.device,platform:f,projectName:l,scenarioKey:p,geminiKey:c,geminiKeySource:u,qwenKey:d,qwenKeySource:g,app:I,cloud:v,cloudProvider:E,cloudUsername:A,cloudAccessKey:S,os:w,appUrl:y,capsPath:b,format:a}}var Qe=["browserstack","lambdatest","appium"];function pn(e){let t=[],r=[];for(let[n,i]of Object.entries(e||{}))!i||typeof i!="object"||Array.isArray(i)?t.push(`--caps block "${n}" must be a JSON object, e.g. { "${n}": { "someCap": true } }`):Qe.includes(n)||r.push(`--caps block "${n}" is not one of ${Qe.join("/")} \u2014 it will be ignored (typo?)`);return{errors:t,warnings:r}}Ze.exports={parseArgs:an,parseCommand:ln,positionalPaths:cn,validateArgs:dn,extToFilename:un,validateCapsObject:pn,COMMANDS:ze}});var nt=R((ys,rt)=>{"use strict";var{DEFAULT_SERVER:hn}=D(),tt={export:`lime-cli export \u2014 work out what this repo is, and where the test belongs.
2
+ "use strict";var R=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(r){throw t=0,r}};var Pe=R((ds,Ir)=>{Ir.exports={name:"@taqwright/lime-cli",version:"0.7.11",description:"Thin CLI client for the LIME server \u2014 fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",license:"SEE LICENSE IN LICENSE",author:"Taqwright",homepage:"https://www.taqwright.ai",bugs:{url:"https://www.taqwright.ai/contact"},keywords:["appium","mobile-testing","e2e","qa","test-automation","taqwright","lime","cli"],bin:{"lime-cli":"dist/index.js"},main:"dist/index.js",files:["dist"],publishConfig:{access:"public"},scripts:{"lime-cli":"node src/index.js",start:"node src/index.js",test:"node --test test/*.test.js",bundle:"esbuild src/index.js --bundle --platform=node --target=node18 --minify --outfile=dist/index.js",prepack:"npm run bundle",prepublishOnly:"npm test"},engines:{node:">=18"},devDependencies:{esbuild:"^0.28.1"}}});var D=R((ps,De)=>{"use strict";var _r="https://www.taqwright.ai";De.exports={POLL_INTERVAL_MS:1e3,POLL_TIMEOUT_MS:18e5,BOOT_POLL_INTERVAL_MS:5e3,BOOT_TIMEOUT_MS:3e5,CONNECT_POLL_INTERVAL_MS:2e3,CONNECT_TIMEOUT_MS:3e5,DEFAULT_SERVER:_r,HTTP_TIMEOUT_MS:3e5,MAX_RESPONSE_BYTES:52428800}});var z=R((hs,Fe)=>{"use strict";var{URL:kr}=require("url"),{HTTP_TIMEOUT_MS:qe,MAX_RESPONSE_BYTES:Me}=D();function Ue(e){let t=String(e||"").replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t.endsWith(".localhost")}var Rr=["just a moment","attention required! | cloudflare","checking your browser","/cdn-cgi/challenge-platform","cf-error-details","cf-browser-verification","_incapsula_resource"];function Be(e,t){if(!t||typeof t!="string")return null;let r=t.slice(0,4e3).toLowerCase();return Rr.some(n=>r.includes(n))?`the LIME server is behind a bot challenge that blocked this request (HTTP ${e}). The request never reached LIME \u2014 it was stopped at the edge, so your token and arguments are probably fine. CI runners use datacenter IPs, which Cloudflare's Bot Fight Mode challenges by default; it cannot be excepted with a WAF skip rule, so the server's operator has to turn it off or move to Super Bot Fight Mode (which supports skip rules) for the /api/ paths.`:null}var de=class{constructor(t,r){this.server=t,this.token=r}_url(t){return/^https?:\/\//i.test(t)?t:`${this.server}${t}`}json(t,r,n,i={}){let s=n?JSON.stringify(n):null,o={...i};return s&&(o["Content-Type"]="application/json",o["Content-Length"]=Buffer.byteLength(s)),this._send(t,this._url(r),o,s)}raw(t,r,n,i={}){let s={...i,"Content-Type":"application/octet-stream","Content-Length":n.length};return this._send(t,this._url(r),s,n)}_send(t,r,n,i){let s=new kr(r);if(s.protocol==="http:"&&!Ue(s.hostname))return Promise.reject(new Error(`refusing to send your LIME_CI_TOKEN over cleartext http to ${s.hostname} \u2014 use https (http is allowed only for localhost).`));let o=s.protocol==="https:"?require("https"):require("http"),a={method:t,hostname:s.hostname,port:s.port||(s.protocol==="https:"?443:80),path:s.pathname+(s.search||""),headers:{...n,Authorization:`Bearer ${this.token}`,Origin:`${s.protocol}//${s.host}`}};return new Promise((l,p)=>{let c=o.request(a,u=>{let d="",g=0;u.setEncoding("utf8"),u.on("data",f=>{if(g+=Buffer.byteLength(f),g>Me){c.destroy(new Error(`Server response exceeded ${Me} bytes`));return}d+=f}),u.on("end",()=>{let f;try{f=d?JSON.parse(d):{}}catch{let w=Be(u.statusCode,d);return p(new Error(w||`Server returned non-JSON (${u.statusCode}): ${d.substring(0,200)}`))}l({status:u.statusCode,body:f})})});c.setTimeout(qe,()=>{c.destroy(new Error(`request timed out after ${qe/1e3}s`))}),c.on("error",p),i&&c.write(i),c.end()})}};function $r(e,t){if(!e.body||!e.body.success)throw new Error(`${t} \u2014 ${e.body&&e.body.error||"unknown"}`);return e.body}Fe.exports={isLoopbackHost:Ue,HttpClient:de,expectSuccess:$r,describeEdgeBlock:Be}});var q=R((fs,Ke)=>{"use strict";var Or={browserstack:{label:"BrowserStack",envUsername:"BROWSERSTACK_USERNAME",envAccessKey:"BROWSERSTACK_ACCESS_KEY",hubHost:"hub.browserstack.com"},lambdatest:{label:"LambdaTest",envUsername:"LT_USERNAME",envAccessKey:"LT_ACCESS_KEY",hubHost:"mobile-hub.lambdatest.com"}};function Lr({provider:e,platform:t,device:r,os:n,appUrl:i,username:s,accessKey:o,capabilities:a}){let l={connectionMode:"cloud",cloudProvider:e||"browserstack",cloudUsername:s,cloudAccessKey:o,platform:t,deviceName:r,deviceOsVersion:n,cloudAppUrl:i,automationName:t==="ios"?"XCUITest":"UiAutomator2"};return a&&(l.cloudCapabilities=a),l}Ke.exports={CLOUD_PROVIDERS:Or,buildCloudSettingsPayload:Lr}});var fe=R((ms,He)=>{"use strict";var Z=require("fs"),pe=require("path"),Cr=["taqwright","webdriverio-js","appium-java-junit5","appium-python-pytest","appium-ruby-rspec","appium-csharp-nunit"],xr={taqwright:"deterministic","webdriverio-js":"gemini","appium-java-junit5":"gemini","appium-python-pytest":"gemini","appium-ruby-rspec":"gemini","appium-csharp-nunit":"gemini"},j={taqwright:"taqwright (native)","webdriverio-js":"WebdriverIO (JavaScript)","appium-java-junit5":"Appium Java (JUnit 5)","appium-python-pytest":"Appium Python (pytest)","appium-ruby-rspec":"Appium Ruby (RSpec)","appium-csharp-nunit":"Appium C# (NUnit)"},Nr=5*1024*1024,he={exists:e=>Z.existsSync(e),readText:e=>{try{return Z.statSync(e).size>Nr?null:Z.readFileSync(e,"utf8")}catch{return null}},list:e=>{try{return Z.readdirSync(e)}catch{return[]}}};function jr(e,t){let r=e.readText(pe.join(t,"package.json"));if(!r)return{};try{let n=JSON.parse(r);return{...n.dependencies||{},...n.devDependencies||{}}}catch{return{}}}var Pr=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage"]);function Dr(e,t){let r=new Set([""]),n=[];try{n=e.list(t)}catch{n=[]}for(let i of n)i.startsWith(".")||Pr.has(i)||r.add(i);return[...r]}function qr(e,t,r){let n=r?pe.join(t,r):t,i=u=>pe.join(n,u),s=u=>r?`${r}/${u}`:u,o=[],a=jr(e,n),l=[];try{l=e.list(n)}catch{l=[]}(a["@taqwright/taqwright"]||a.taqwright||e.exists(i("taqwright.config.ts"))||e.exists(i("taqwright.config.js")))&&o.push({id:"taqwright",label:j.taqwright,reason:"taqwright dependency/config found",manifest:s("package.json")});let p=l.some(u=>/^wdio\.conf\.(js|ts|mjs|cjs)$/.test(u));(a.webdriverio||a["@wdio/cli"]||p)&&o.push({id:"webdriverio-js",label:j["webdriverio-js"],reason:p?"wdio.conf.* found":"webdriverio dependency found",manifest:s("package.json")});for(let u of["pom.xml","build.gradle","build.gradle.kts"]){let d=e.readText(i(u));if(d&&/io\.appium|java-client/.test(d)){o.push({id:"appium-java-junit5",label:j["appium-java-junit5"],reason:`io.appium:java-client in ${s(u)}`,manifest:s(u)});break}}for(let u of["requirements.txt","pyproject.toml"]){let d=e.readText(i(u));if(d&&/appium-python-client/i.test(d)){o.push({id:"appium-python-pytest",label:j["appium-python-pytest"],reason:`Appium-Python-Client in ${s(u)}`,manifest:s(u)});break}}let c=e.readText(i("Gemfile"));c&&/appium_lib(_core)?/.test(c)&&o.push({id:"appium-ruby-rspec",label:j["appium-ruby-rspec"],reason:"appium_lib in Gemfile",manifest:s("Gemfile")});for(let u of l){if(!/\.csproj$/i.test(u))continue;let d=e.readText(i(u));if(d&&/Appium\.WebDriver/i.test(d)){o.push({id:"appium-csharp-nunit",label:j["appium-csharp-nunit"],reason:`Appium.WebDriver in ${s(u)}`,manifest:s(u)});break}}return o}function Ye(e,t=he){let r=new Map;for(let n of Dr(t,e))for(let i of qr(t,e,n))r.has(i.id)||r.set(i.id,i);return[...r.values()]}function Mr(e,t=he){let r=Ye(e,t);return r.some(n=>n.id==="taqwright")||r.push({id:"taqwright",label:j.taqwright,reason:"default output"}),r}function Ur(e){let{requested:t,configured:r,remembered:n,offeredIds:i,detectedRealIds:s,taqwrightDetected:o,javaSignal:a}=e,l=p=>!!p&&i.includes(p);return l(t)?t:l(r)?r:l(n)&&(s.includes(n)||n==="taqwright"&&o)?n:s.length?s[0]:o?"taqwright":a?"appium-java-junit5":"taqwright"}He.exports={FRAMEWORK_IDS:Cr,FRAMEWORK_ENGINES:xr,FRAMEWORK_LABELS:j,nodeFs:he,detectFrameworks:Ye,rankFrameworks:Mr,pickDefaultFramework:Ur}});var ve=R((gs,Xe)=>{"use strict";var me=require("path"),{nodeFs:Br}=fe(),Ve={taqwright:"tests","webdriverio-js":"test/specs","appium-java-junit5":"src/test/java","appium-python-pytest":"tests","appium-ruby-rspec":"spec","appium-csharp-nunit":"Tests"},Fr="tests",Kr={taqwright:".spec.ts","webdriverio-js":".spec.ts","appium-java-junit5":".java","appium-python-pytest":".py","appium-ruby-rspec":"_spec.rb","appium-csharp-nunit":".cs"},Yr=new Set(["appium-python-pytest","appium-ruby-rspec"]);function ge(e){return Ve[e]||Fr}function we(e){let t=(e||"").split(/[^A-Za-z0-9]+/).filter(Boolean);return t.length?t.map(r=>/^[A-Z0-9]+$/.test(r)?r[0]+r.slice(1).toLowerCase():r[0].toUpperCase()+r.slice(1)).join(""):""}function ye(e){return(e||"").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split(/[^A-Za-z0-9]+/).filter(Boolean).join("_").toLowerCase()}function Hr(e){return ye(e).split("_").filter(Boolean).join("-")}var Vr=6,Wr=new Set(["and","or","the","a","an","to","of","in","on","at","as","with","for","then","into","from","by","that","is"]),Gr=/@|\d{6,}/;function Jr(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean).filter(r=>!Gr.test(r)).slice(0,Vr);for(;t.length>1&&Wr.has(t[t.length-1].toLowerCase());)t.pop();return we(t.join(" "))||"RecordedTest"}function Xr(e){let t=String(e||"").trim();return t?/[A-Za-z]/.test(t)?t.length>80?"Keep the name under 80 characters.":/[/\\]/.test(t)?"Use the destination field for the folder.":null:"Use at least one letter.":"Enter a test name."}function Qr(e){if(e==null||e==="")return null;let t=String(e);return t!==t.trim()?"must not have leading or trailing spaces.":t.length>120?"is longer than any key LIME mints (120 characters).":/^[a-z0-9][a-z0-9._/:-]*$/.test(t)?t.includes("..")?'must not contain "..".':null:"is not a scenario key \u2014 expected lower-case letters, digits and . _ - / : only."}function We(e,t){let r;return Yr.has(e)?r=ye(t):r=we(t),`${r}${Kr[e]||""}`}function K(e){return String(e||"").split(/[/\\]+/).filter(t=>t&&t!=="."&&t!=="..").join("/")}function zr(e){if(e==null)return null;let t=String(e);return t.trim()===""?null:/^([A-Za-z]:)?[/\\]/.test(t)?"must be a path inside the project, not an absolute path.":t.split(/[/\\]+/).includes("..")?'must not contain ".." \u2014 the test belongs inside the project.':null}function be(e){let t=K(e).split("/");return t.pop(),t.join("/")}function Ge(e){let t=/(?:^|\/)src\/(?:test|main)\/java\/(.+)$/.exec(K(e));return t&&t[1].split("/").filter(Boolean).join(".")||void 0}function Zr(e){let t=K(e),r=t.search(/\/src\/(?:test|main)\/java(?:\/|$)/);return r>0?t.slice(0,r):""}function en(e){let t=new Map;for(let n of e){let i=be(n);t.set(i,(t.get(i)||0)+1)}if(!t.size)return null;let r=[...t.entries()].sort((n,i)=>i[1]-n[1]||n[0].localeCompare(i[0]));return{dir:r[0][0],count:r[0][1]}}var Je={taqwright:{segment:"tests",file:/\.spec\.(ts|js)$/},"webdriverio-js":{segment:"test/specs",file:/\.(ts|js)$/},"appium-java-junit5":{segment:"src/test/java",file:/\.java$/},"appium-python-pytest":{segment:"tests",file:/^(test_.+|.+_test)\.py$/},"appium-ruby-rspec":{segment:"spec",file:/_spec\.rb$/},"appium-csharp-nunit":{segment:"",file:/Tests\.cs$/}},tn=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage",".git"]),rn=2e4;function nn(e,t,r=Br){let n=Je[t];if(!n)return[];let i=[],s=rn,o=a=>{if(s<=0)return;let l=a?me.join(e,a):e,p=[];try{p=r.list(l)}catch{return}for(let c of p){if(s--<=0)return;if(c.startsWith(".")||tn.has(c))continue;let u=a?`${a}/${c}`:c;if(r.isDir?r.isDir(me.join(e,u)):r.list(me.join(e,u)).length>0){o(u);continue}n.file.test(c)&&(n.segment&&!`/${be(u)}/`.includes(`/${n.segment}/`)||i.push(u))}};return o(""),i.sort()}function sn(e){let{framework:t,testName:r}=e,n=We(t,r),i=(a,l,p=0)=>{let c=K(a),u=t==="appium-java-junit5"?Ge(c):void 0;return{dir:c,fileName:n,...u?{packageName:u}:{},source:l,neighbourCount:p}};if(e.remembered!=null)return i(e.remembered,"remembered");let s=en(e.existingTests||[]);if(s&&s.dir)return i(s.dir,"existing",s.count);let o=e.detection&&e.detection.manifest?be(e.detection.manifest):"";return o?i(`${o}/${ge(t)}`,"module"):i(ge(t),"convention")}Xe.exports={CONVENTIONAL_DIRS:Ve,TEST_PATTERNS:Je,conventionalDir:ge,pascalCase:we,snakeCase:ye,kebabCase:Hr,testNameFromGoal:Jr,validateTestName:Xr,validateDest:zr,validateScenarioKey:Qr,fileNameFor:We,normalizeDir:K,javaPackageOf:Ge,moduleRootOf:Zr,findExistingTests:nn,resolveExportTarget:sn}});var et=R((ws,Ze)=>{"use strict";var{CLOUD_PROVIDERS:ee}=q(),{validateScenarioKey:on}=ve();function an(e){let t=Object.create(null);for(let r=0;r<e.length;r++){let n=e[r];if(!n.startsWith("--"))continue;let i=n.slice(2),s=e[r+1];s===void 0||s.startsWith("--")?t[i]=!0:(t[i]=s,r++)}return t}var ze=["export","adapt","verify","check-locators","disconnect","report"];function cn(e){let t=Array.isArray(e)?e:[],r=[];for(let n=0;n<t.length;n+=1){let i=String(t[n]==null?"":t[n]);!i||i.startsWith("-")||(n>0?String(t[n-1]||""):"").startsWith("--")||r.push(i)}return r}function ln(e){let t=e[0];return t&&ze.includes(t)?{command:t,rest:e.slice(1)}:{command:"run",rest:e}}function un(e="taqwright"){return e==="json"?"recorded-steps.json":"RecordedTest.spec.ts"}function dn(e,t=process.env){let r=[],n=!!e["list-devices"],i=!!e["keep-session"],s=!!e["no-boot"],o=e["lime-dir"]===!0?".lime":typeof e["lime-dir"]=="string"&&e["lime-dir"].trim()?e["lime-dir"].trim():void 0,a=e.format?String(e.format).toLowerCase():"taqwright";if(["taqwright","json"].includes(a)||r.push(`--format must be taqwright or json (got "${e.format}")`),n)return{errors:r,listDevices:n,keepSession:i,noBoot:s,format:a,limeDir:o};(!e.goal||typeof e.goal!="string"||!e.goal.trim())&&r.push("--goal is required");let l;e.project!==void 0&&(typeof e.project!="string"||!e.project.trim()?r.push("--project requires a project name"):l=e.project.trim());let p;if(e.scenario!==void 0){let h=typeof e.scenario=="string"?e.scenario.trim():"",T=h?on(h):"requires a scenario key";T?r.push(`--scenario ${T}`):p=h}let c,u;e["gemini-key"]!==void 0&&(typeof e["gemini-key"]!="string"||!e["gemini-key"].trim()?r.push("--gemini-key requires a value (or set GEMINI_API_KEY)"):(c=e["gemini-key"].trim(),u="flag")),!c&&typeof t.GEMINI_API_KEY=="string"&&t.GEMINI_API_KEY.trim()&&(c=t.GEMINI_API_KEY.trim(),u="env");let d,g;if(e["qwen-key"]!==void 0&&(typeof e["qwen-key"]!="string"||!e["qwen-key"].trim()?r.push("--qwen-key requires a value (or set QWEN_API_KEY)"):(d=e["qwen-key"].trim(),g="flag")),!d&&typeof t.QWEN_API_KEY=="string"&&t.QWEN_API_KEY.trim()&&(d=t.QWEN_API_KEY.trim(),g="env"),e["max-steps"]!==void 0){let h=Number(e["max-steps"]);(!Number.isFinite(h)||h<1||h>200)&&r.push("--max-steps must be a number between 1 and 200")}let f;e.platform&&(f=String(e.platform).toLowerCase(),["android","ios"].includes(f)||r.push(`--platform must be android or ios (got "${e.platform}")`));let v=!!e.cloud,w=typeof e.os=="string"?e.os:void 0,y=typeof e["app-url"]=="string"?e["app-url"]:void 0,I=typeof e.app=="string"?e.app:void 0,b=typeof e.caps=="string"&&e.caps.trim()?e.caps.trim():void 0,E,A,S;if(v&&(E=e.cloud===!0?"browserstack":String(e.cloud).toLowerCase(),ee[E]||r.push(`--cloud must be ${Object.keys(ee).join(" or ")} (got "${e.cloud}")`)),v){(!e.device||typeof e.device!="string")&&r.push('--cloud requires --device (the cloud device name, e.g. "Google Pixel 8")'),f||r.push("--cloud requires --platform (android or ios)"),w||r.push("--cloud requires --os (the device OS version, e.g. 14.0)"),I?r.push("--app is not supported for cloud runs. Upload the build to your provider \u2014 LambdaTest: POST https://manual-api.lambdatest.com/app/upload/realDevice (-F appFile=@<file>), BrowserStack: POST https://api-cloud.browserstack.com/app-automate/upload (-F file=@<file>) \u2014 then pass the returned id with --app-url. (--app is still supported for local runs, where it clean-reinstalls on the agent device.)"):y||r.push("--cloud requires --app-url <bs://\u2026 | lt://\u2026> (the id from your provider\u2019s app upload API)"),e["app-url"]!==void 0&&(typeof e["app-url"]!="string"||!e["app-url"].trim())&&r.push("--app-url requires a bs:// or lt:// app id"),y&&ee[E]&&(E==="browserstack"&&y.startsWith("lt://")?r.push(`--app-url "${y}" is a LambdaTest app id, but the provider is browserstack`):E==="lambdatest"&&y.startsWith("bs://")&&r.push(`--app-url "${y}" is a BrowserStack app id, but the provider is lambdatest`)),e["cloud-username"]!==void 0&&(typeof e["cloud-username"]!="string"||!e["cloud-username"].trim())&&r.push("--cloud-username requires a value"),e["cloud-access-key"]!==void 0&&(typeof e["cloud-access-key"]!="string"||!e["cloud-access-key"].trim())&&r.push("--cloud-access-key requires a value"),e.caps!==void 0&&(typeof e.caps!="string"||!e.caps.trim())&&r.push("--caps requires a path to a JSON file");let h=ee[E];h&&(A=typeof e["cloud-username"]=="string"&&e["cloud-username"].trim()||t[h.envUsername]||void 0,S=typeof e["cloud-access-key"]=="string"&&e["cloud-access-key"].trim()||t[h.envAccessKey]||void 0,A||r.push(`cloud runs require a username \u2014 pass --cloud-username or set ${h.envUsername} (credentials are never read from your LIME account for CLI runs)`),S||r.push(`cloud runs require an access key \u2014 pass --cloud-access-key or set ${h.envAccessKey}`))}else e.app!==void 0&&(typeof e.app!="string"||!e.app.trim()?r.push("--app requires a path to an APK"):e.device?f&&f!=="android"&&r.push("--app is Android only"):r.push("--app requires --device (lime-cli must connect to a device to reinstall the app)")),e["app-url"]!==void 0&&r.push("--app-url requires --cloud"),e.os!==void 0&&r.push("--os requires --cloud"),e["cloud-username"]!==void 0&&r.push("--cloud-username requires --cloud"),e["cloud-access-key"]!==void 0&&r.push("--cloud-access-key requires --cloud"),e.caps!==void 0&&r.push("--caps requires --cloud");return{errors:r,listDevices:n,keepSession:i,noBoot:s,limeDir:o,device:e.device,platform:f,projectName:l,scenarioKey:p,geminiKey:c,geminiKeySource:u,qwenKey:d,qwenKeySource:g,app:I,cloud:v,cloudProvider:E,cloudUsername:A,cloudAccessKey:S,os:w,appUrl:y,capsPath:b,format:a}}var Qe=["browserstack","lambdatest","appium"];function pn(e){let t=[],r=[];for(let[n,i]of Object.entries(e||{}))!i||typeof i!="object"||Array.isArray(i)?t.push(`--caps block "${n}" must be a JSON object, e.g. { "${n}": { "someCap": true } }`):Qe.includes(n)||r.push(`--caps block "${n}" is not one of ${Qe.join("/")} \u2014 it will be ignored (typo?)`);return{errors:t,warnings:r}}Ze.exports={parseArgs:an,parseCommand:ln,positionalPaths:cn,validateArgs:dn,extToFilename:un,validateCapsObject:pn,COMMANDS:ze}});var nt=R((ys,rt)=>{"use strict";var{DEFAULT_SERVER:hn}=D(),tt={export:`lime-cli export \u2014 work out what this repo is, and where the test belongs.
3
3
 
4
4
  Detects the test framework from the project's own manifests (pytest, JUnit, RSpec, WebdriverIO,
5
5
  C#, taqwright), picks the directory its existing tests live in, and writes the adaptation brief.
@@ -49,6 +49,10 @@ Usage:
49
49
  repository's own HEAD pairs with nothing.
50
50
  --actor <who> Who set the run off.
51
51
  --run-id <id> Default: $GITHUB_RUN_ID.
52
+ --device <name> What it ran on, e.g. "Galaxy S24 \xB7 Android 16". Shown as a column
53
+ in the pull request summary; omitted leaves the column out.
54
+ --app-version <v> Which build was under test \u2014 a version like "1.4.2 (88)", or the
55
+ provider's binary id. Shown beside the device on the board.
52
56
  --pr <number> The pull request these results are about. Default: read from
53
57
  $GITHUB_REF on a pull_request workflow. It is what lets the board
54
58
  still link the pull request after the branch is merged.
@@ -358,14 +362,14 @@ ${at(e)}`);if(r&&o.platform!==r)throw new Error(`Device "${t}" matched on ${o.pl
358
362
  `),1):(h&&h.unused.length&&process.stdout.write(`lime-cli: ${h.unused.length} recorded locator${h.unused.length===1?"":"s"} the generated test does not use.
359
363
  `),ae(r,{...c,adaptedFiles:S.files,adaptSummary:S.summary||null,conservation:h}),process.stdout.write(`${H}: adapted
360
364
  `),0)}async function yi(e={}){let{cwd:t=process.cwd(),limeDir:r=k.join(t,".lime"),files:n=[],diff:i}=e,s=k.join(r,x(M)),o=Xt(s);if(!o)return process.stderr.write(`lime-cli: no readable ${x(M)} in ${r} \u2014 nothing to check the generated test against.
361
- `),2;let l=(V(r)||{}).framework||"taqwright",p;if(i){let u;try{u=$.readFileSync(k.resolve(t,i),"utf8")}catch{return process.stderr.write(`lime-cli: could not read the diff at ${i}.
362
- `),2}let d=di(u);if(!d.length)return process.stdout.write(`lime-cli: the change adds no lines, so there is no new locator to check.
363
- `),0;p=Bt({steps:o,framework:l,files:d})}else{let u=n.filter(Boolean);if(!u.length)return process.stderr.write(`lime-cli: name at least one generated file to check, or pass --diff.
364
- `),2;p=Qt(t,u,{steps:o,framework:l})}if(!p)return process.stderr.write(`lime-cli: none of the named files could be read, so nothing was checked.
365
- `),2;if(!p.ok)return process.stderr.write(`${Ft(p)}
366
- `),1;if(p.abstained)return process.stdout.write(`lime-cli: ${p.reason}.
367
- `),0;let c=p.unused.length;return process.stdout.write(`lime-cli: every locator in the generated test came from the recording${c?` (${c} recorded locator${c===1?"":"s"} unused)`:""}.
368
- `),0}function Xt(e){try{return JSON.parse($.readFileSync(e,"utf8"))}catch{return null}}function Qt(e,t,{steps:r,framework:n}){let i=[];for(let s of Array.isArray(t)?t:[])try{i.push({path:s,source:$.readFileSync(k.resolve(e,s),"utf8")})}catch{}return i.length?Bt({steps:r,framework:n,files:i}):null}async function bi(e={}){let{cwd:t=process.cwd(),limeDir:r=k.join(t,".lime"),agent:n="auto",spec:i,testId:s,appDir:o,env:a=process.env,runAgent:l=Mt,resolveBin:p=Ut}=e,c=V(r)||{},u=c.target==="cloud"?"cloud":"local",d=c.framework||"taqwright",g=Wt(c.adaptedFiles),f=i?[i]:g.length?g:[c.output||"RecordedTest.spec.ts"],v={};if(u==="cloud"){let S=c.cloud||{},h=jt[S.provider]||jt.browserstack,T=(a[h.envUsername]||"").trim(),L=(a[h.envAccessKey]||"").trim();if(!T||!L)return process.stderr.write(`lime-cli: verifying on ${h.label} needs ${h.envUsername} and ${h.envAccessKey} in the environment.
365
+ `),2;let l=(V(r)||{}).framework||"taqwright",p;if(i){let c;try{c=$.readFileSync(k.resolve(t,i),"utf8")}catch{return process.stderr.write(`lime-cli: could not read the diff at ${i}.
366
+ `),2}let u=di(c);if(!u.length)return process.stdout.write(`lime-cli: the change adds no lines, so there is no new locator to check.
367
+ `),0;p=Bt({steps:o,framework:l,files:u})}else{let c=n.filter(Boolean);if(!c.length)return process.stderr.write(`lime-cli: name at least one generated file to check, or pass --diff.
368
+ `),2;p=Qt(t,c,{steps:o,framework:l})}return p?p.ok?p.abstained?(process.stdout.write(`lime-cli: ${p.reason}.
369
+ `),0):(process.stdout.write(`lime-cli: every locator in this change was recorded on the device.
370
+ `),0):(process.stderr.write(`${Ft(p)}
371
+ `),1):(process.stderr.write(`lime-cli: none of the named files could be read, so nothing was checked.
372
+ `),2)}function Xt(e){try{return JSON.parse($.readFileSync(e,"utf8"))}catch{return null}}function Qt(e,t,{steps:r,framework:n}){let i=[];for(let s of Array.isArray(t)?t:[])try{i.push({path:s,source:$.readFileSync(k.resolve(e,s),"utf8")})}catch{}return i.length?Bt({steps:r,framework:n,files:i}):null}async function bi(e={}){let{cwd:t=process.cwd(),limeDir:r=k.join(t,".lime"),agent:n="auto",spec:i,testId:s,appDir:o,env:a=process.env,runAgent:l=Mt,resolveBin:p=Ut}=e,c=V(r)||{},u=c.target==="cloud"?"cloud":"local",d=c.framework||"taqwright",g=Wt(c.adaptedFiles),f=i?[i]:g.length?g:[c.output||"RecordedTest.spec.ts"],v={};if(u==="cloud"){let S=c.cloud||{},h=jt[S.provider]||jt.browserstack,T=(a[h.envUsername]||"").trim(),L=(a[h.envAccessKey]||"").trim();if(!T||!L)return process.stderr.write(`lime-cli: verifying on ${h.label} needs ${h.envUsername} and ${h.envAccessKey} in the environment.
369
373
  `),2;$.mkdirSync(r,{recursive:!0}),$.writeFileSync(k.join(r,x(oi)),JSON.stringify({provider:S.provider||"browserstack",hubHost:S.hubHost||h.hubHost,appUrl:S.appUrl||null,device:S.device||null,os:S.os||null,platform:c.platform||null,region:S.region||null},null,2)+`
370
374
  `,"utf8"),v={LIME_CLOUD_USER:T,LIME_CLOUD_KEY:L}}let w=ci({frameworkLabel:c.frameworkLabel||d,framework:d,testPaths:f,testId:s||c.testId||null,target:u,recordedPlatform:c.platform||null}),y=k.join(r,x(si));$.writeFileSync(y,w,"utf8"),process.stdout.write(`lime-cli: wrote ${k.relative(t,y)||y}
371
375
  `);let I=Gt(n,p);if(I.error)return process.stderr.write(`lime-cli: ${I.error}
@@ -390,12 +394,12 @@ lime-cli: cancelling\u2026
390
394
  `);try{await e.json("DELETE","/api/cli/run")}catch{}return 1}let d=await e.json("GET",`/api/cli/run?cursor=${c}`);if(!d.body||!d.body.success)return process.stderr.write(`lime-cli: status poll failed \u2014 ${d.body&&d.body.error||"unknown"}
391
395
  `),1;let g=d.body;for(let f of g.log||[])Ti(f);if(typeof g.nextCursor=="number"&&(c=g.nextCursor),u=g,g.running===!1)break;await new Promise(f=>setTimeout(f,vi))}}finally{process.off("SIGINT",l)}return Ai(u,r,n),i.limeDir&&u.status==="done"&&Si(i.limeDir,u,i.manifest||{},r,n),process.stdout.write(`
392
396
  Status: ${u.status}, ${u.stepCount||0} steps, ${u.totalTokens||0} tokens
393
- `),u.status==="done"?0:1}tr.exports={runAgentLoop:_i,preflightRun:Ii}});var cr=R((ks,ar)=>{"use strict";var ki="passed",Ri="skipped";function O(e){return typeof e=="string"?e:""}function sr(e){if(!e)return"";if(typeof e.body=="string")try{return Buffer.from(e.body,"base64").toString("utf8").trim()}catch{return""}return""}function $i(e){let t=e&&e.attachments||[];for(let r of t)if(r&&r.name==="taqwright-session"){let n=sr(r);if(n)return n}return""}var Ce="taqwright-scenario",Oi=120;function nr(e){let t=O(e).trim();return t&&t.length<=Oi?t:""}function Li(e,t,r){let n=[t&&t.annotations,e&&e.annotations,r&&r.annotations];for(let i of n)for(let s of Array.isArray(i)?i:[])if(s&&s.type===Ce){let o=nr(s.description);if(o)return o}for(let i of r&&r.attachments||[])if(i&&i.name===Ce){let s=nr(sr(i));if(s)return s}return""}function Ci(e){let t=[],r=i=>{if(i){for(let s of i.specs||[])n(s,i);for(let s of i.suites||[])r(s)}},n=(i,s)=>{for(let o of i.tests||[]){let a=o.results||[],l=a[a.length-1];if(!l||l.status===Ri)continue;let p=l.status===ki;t.push({file:O(i.file)||O(s&&s.title),specName:O(i.title),status:p?"passed":"failed",passed:p,durationMs:Number(l.duration)||0,error:O(l.error&&l.error.message),sessionId:$i(l),scenario:Li(i,o,l)})}};for(let i of e&&e.suites||[])r(i);return t}function ir(e){let t=O(e&&e.start_timestamp).trim()||O(e&&e.create_timestamp).trim();return Date.parse(t)}function xi(e,t){if(e===null)return null;let r=ir(e),n=ir(t);return!Number.isFinite(r)||!Number.isFinite(n)?null:n>r?t:e}function Ni(e,t){let r=Array.isArray(t)?t:[];if(!r.length)return e;let n=new Map;for(let i of r){let s=O(i&&i.name).trim();if(!s)continue;let o=n.get(s);if(o===void 0){n.set(s,i);continue}n.set(s,xi(o,i))}return e.map(i=>{if(i.sessionId)return i;let s=n.get(O(i.specName).trim()),o=s?O(s.session_id||s.test_id):"";return{...i,sessionId:o}})}function or(e={}){let t={},r=(n,i)=>{let s=O(i).trim();s&&(t[n]=s)};return r("id",e.runId),r("commit",e.commit),r("branch",e.branch),r("actor",e.actor),r("title",e.title),r("platform",e.platform),r("pr",e.pr),t}function ji(e={}){let t=e||{},r=O(t.GITHUB_REF).trim(),n=/^refs\/pull\/([0-9]+)\/(merge|head)$/.exec(r);if(n)return n[1];let i=/^([0-9]+)\/(merge|head)$/.exec(O(t.GITHUB_REF_NAME).trim());return i?i[1]:""}function Pi(e,t={}){let r=O(t.platform).trim().toLowerCase();return{run:or(t),results:e.map(n=>{let i={file:n.file,spec_name:n.specName,passed:!!n.passed,status:n.status||(n.passed?"passed":"failed"),duration_ms:Number(n.durationMs)||0};return(r==="android"||r==="ios")&&(i.platform=r),n.error&&(i.error=n.error),n.sessionId&&(i.session_id=n.sessionId),n.scenario&&(i.scenario=n.scenario),i})}}ar.exports={flattenResults:Ci,matchSessions:Ni,buildRunEnvelope:or,buildPayload:Pi,prNumberFrom:ji,SCENARIO_ANNOTATION:Ce}});var fr=R((Rs,hr)=>{"use strict";var lr=require("fs"),ur=require("path"),{flattenResults:Di,matchSessions:qi,buildPayload:Mi,prNumberFrom:Ui}=cr(),Bi="https://mobile-api.lambdatest.com/mobile-automation/api/v1/sessions",Fi="https://eu-mobile-api.lambdatest.com/mobile-automation/api/v1/sessions";function dr(e){process.stdout.write(e)}function P(e){process.stderr.write(e)}async function pr({runId:e,username:t,accessKey:r,region:n,fetchImpl:i}){let s=i||globalThis.fetch;if(!e||!t||!r)return[];let o=String(n||"").trim().toUpperCase()==="EU"?Fi:Bi;try{let a=Buffer.from(`${t}:${r}`).toString("base64"),l=await s(`${o}?limit=200`,{headers:{Authorization:`Basic ${a}`,Accept:"application/json"}});if(!l.ok)return P(`lime-cli: could not read sessions from the grid (HTTP ${l.status}); reporting without video links.
397
+ `),u.status==="done"?0:1}tr.exports={runAgentLoop:_i,preflightRun:Ii}});var cr=R((ks,ar)=>{"use strict";var ki="passed",Ri="skipped";function O(e){return typeof e=="string"?e:""}function sr(e){if(!e)return"";if(typeof e.body=="string")try{return Buffer.from(e.body,"base64").toString("utf8").trim()}catch{return""}return""}function $i(e){let t=e&&e.attachments||[];for(let r of t)if(r&&r.name==="taqwright-session"){let n=sr(r);if(n)return n}return""}var Ce="taqwright-scenario",Oi=120;function nr(e){let t=O(e).trim();return t&&t.length<=Oi?t:""}function Li(e,t,r){let n=[t&&t.annotations,e&&e.annotations,r&&r.annotations];for(let i of n)for(let s of Array.isArray(i)?i:[])if(s&&s.type===Ce){let o=nr(s.description);if(o)return o}for(let i of r&&r.attachments||[])if(i&&i.name===Ce){let s=nr(sr(i));if(s)return s}return""}function Ci(e){let t=[],r=i=>{if(i){for(let s of i.specs||[])n(s,i);for(let s of i.suites||[])r(s)}},n=(i,s)=>{for(let o of i.tests||[]){let a=o.results||[],l=a[a.length-1];if(!l||l.status===Ri)continue;let p=l.status===ki;t.push({file:O(i.file)||O(s&&s.title),specName:O(i.title),status:p?"passed":"failed",passed:p,durationMs:Number(l.duration)||0,error:O(l.error&&l.error.message),sessionId:$i(l),scenario:Li(i,o,l)})}};for(let i of e&&e.suites||[])r(i);return t}function ir(e){let t=O(e&&e.start_timestamp).trim()||O(e&&e.create_timestamp).trim();return Date.parse(t)}function xi(e,t){if(e===null)return null;let r=ir(e),n=ir(t);return!Number.isFinite(r)||!Number.isFinite(n)?null:n>r?t:e}function Ni(e,t){let r=Array.isArray(t)?t:[];if(!r.length)return e;let n=new Map;for(let i of r){let s=O(i&&i.name).trim();if(!s)continue;let o=n.get(s);if(o===void 0){n.set(s,i);continue}n.set(s,xi(o,i))}return e.map(i=>{if(i.sessionId)return i;let s=n.get(O(i.specName).trim()),o=s?O(s.session_id||s.test_id):"";return{...i,sessionId:o}})}function or(e={}){let t={},r=(n,i)=>{let s=O(i).trim();s&&(t[n]=s)};return r("id",e.runId),r("commit",e.commit),r("branch",e.branch),r("actor",e.actor),r("title",e.title),r("platform",e.platform),r("pr",e.pr),r("device",e.device),r("app_version",e.appVersion),t}function ji(e={}){let t=e||{},r=O(t.GITHUB_REF).trim(),n=/^refs\/pull\/([0-9]+)\/(merge|head)$/.exec(r);if(n)return n[1];let i=/^([0-9]+)\/(merge|head)$/.exec(O(t.GITHUB_REF_NAME).trim());return i?i[1]:""}function Pi(e,t={}){let r=O(t.platform).trim().toLowerCase();return{run:or(t),results:e.map(n=>{let i={file:n.file,spec_name:n.specName,passed:!!n.passed,status:n.status||(n.passed?"passed":"failed"),duration_ms:Number(n.durationMs)||0};return(r==="android"||r==="ios")&&(i.platform=r),n.error&&(i.error=n.error),n.sessionId&&(i.session_id=n.sessionId),n.scenario&&(i.scenario=n.scenario),i})}}ar.exports={flattenResults:Ci,matchSessions:Ni,buildRunEnvelope:or,buildPayload:Pi,prNumberFrom:ji,SCENARIO_ANNOTATION:Ce}});var fr=R((Rs,hr)=>{"use strict";var lr=require("fs"),ur=require("path"),{flattenResults:Di,matchSessions:qi,buildPayload:Mi,prNumberFrom:Ui}=cr(),Bi="https://mobile-api.lambdatest.com/mobile-automation/api/v1/sessions",Fi="https://eu-mobile-api.lambdatest.com/mobile-automation/api/v1/sessions";function dr(e){process.stdout.write(e)}function P(e){process.stderr.write(e)}async function pr({runId:e,username:t,accessKey:r,region:n,fetchImpl:i}){let s=i||globalThis.fetch;if(!e||!t||!r)return[];let o=String(n||"").trim().toUpperCase()==="EU"?Fi:Bi;try{let a=Buffer.from(`${t}:${r}`).toString("base64"),l=await s(`${o}?limit=200`,{headers:{Authorization:`Basic ${a}`,Accept:"application/json"}});if(!l.ok)return P(`lime-cli: could not read sessions from the grid (HTTP ${l.status}); reporting without video links.
394
398
  `),[];let p=await l.json(),c=p&&p.data||[],u=`CI ${e}`;return c.filter(d=>String(d&&d.build_name||"").includes(u))}catch(a){return P(`lime-cli: could not reach the grid (${a.message}); reporting without video links.
395
399
  `),[]}}async function Ki(e){let{http:t,args:r,env:n=process.env,fetchImpl:i}=e,s=ur.resolve(r.results||"results.json");if(!lr.existsSync(s))return P(`lime-cli: no results at ${s} \u2014 nothing to report.
396
400
  `),0;let o;try{o=JSON.parse(lr.readFileSync(s,"utf8"))}catch(f){return P(`lime-cli: ${ur.basename(s)} is not readable JSON (${f.message}).
397
401
  `),0}let a=Di(o);if(!a.length)return P(`lime-cli: the report contains no test results \u2014 nothing to report.
398
- `),0;let l=r["run-id"]||n.GITHUB_RUN_ID||"",p=r.pr||Ui(n),c=await pr({runId:l,username:n.LAMBDATEST_USERNAME,accessKey:n.LAMBDATEST_ACCESS_KEY,region:n.LAMBDATEST_REGION,fetchImpl:i});a=qi(a,c);let u=Mi(a,{runId:l,commit:r.commit,branch:r.branch,actor:r.actor,title:r.title,platform:r.platform,pr:p}),d=a.filter(f=>f.sessionId).length,g=a.filter(f=>!f.passed).length;try{let f=await t.json("POST","/api/ci/results",u),v=f&&f.body||{};if(!v.success)return P(`lime-cli: LIME refused the results (${v.error||`HTTP ${f&&f.status}`}).
402
+ `),0;let l=r["run-id"]||n.GITHUB_RUN_ID||"",p=r.pr||Ui(n),c=await pr({runId:l,username:n.LAMBDATEST_USERNAME,accessKey:n.LAMBDATEST_ACCESS_KEY,region:n.LAMBDATEST_REGION,fetchImpl:i});a=qi(a,c);let u=Mi(a,{runId:l,commit:r.commit,branch:r.branch,actor:r.actor,title:r.title,platform:r.platform,pr:p,device:r.device,appVersion:r["app-version"]}),d=a.filter(f=>f.sessionId).length,g=a.filter(f=>!f.passed).length;try{let f=await t.json("POST","/api/ci/results",u),v=f&&f.body||{};if(!v.success)return P(`lime-cli: LIME refused the results (${v.error||`HTTP ${f&&f.status}`}).
399
403
  `),0;dr(`lime-cli: reported ${v.saved} result(s), ${g} failing, ${d} with a cloud session. Run ${v.run_id}.
400
404
  `),v.scenarios_created&&dr(`lime-cli: registered ${v.scenarios_created} new scenario(s).
401
405
  `),v.unmatched&&P(`lime-cli: ${v.unmatched} result(s) could not be matched to a scenario.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taqwright/lime-cli",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "description": "Thin CLI client for the LIME server — fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Taqwright",