momentic 1.0.52 → 1.0.53

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.
package/bin/cli.js CHANGED
@@ -2716,7 +2716,7 @@ ${i.stack}`),a.status(500).send("Internal Server Error")}),n){let i=gy.static(n)
2716
2716
 
2717
2717
  Using Command Prompt on Windows:
2718
2718
  for /f "tokens=5" %a in ('netstat -ano ^| findstr :58888') do taskkill /PID %a /F
2719
- `)}import BI from"events";import mS from"fs";import jI from"open";import Ma from"path";import{fileURLToPath as $I}from"url";import QR from"diff-lines";import{gt as eI}from"semver";import{execSync as DR}from"child_process";import{platform as FR}from"os";function dd(){return by()?(v.info("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),v.info("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),v.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(v.info("Setting device pixel ratio to 1."),v.info("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),v.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function by(){return FR()==="darwin"&&DR("system_profiler SPDisplaysDataType").toString().includes("Retina")}function ud(n){by()&&n===1&&(v.warn("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),v.warn("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import UR from"@actions/exec";import zR from"@actions/io";import BR from"quote";import jR from"string-argv";async function vy(n,e=!0){let t=jR(n),r=await zR.which(t[0],!0),o=t.slice(1),i=UR.exec(BR(r),o,{delay:100});if(e)return i}import{existsSync as $R,statSync as HR}from"fs";var pd=!!process.env.CI||!process.stdout.isTTY;function Ci(n){try{return $R(n)&&HR(n).isDirectory()}catch(e){return v.error({err:e},`Error reading path ${n} during directory existence check`),!1}}import WR from"csv-parser";import{createReadStream as GR}from"fs";function md(n){return new Promise((e,t)=>{let r=[];GR(n).pipe(WR()).on("data",o=>r.push(o)).on("end",()=>e(r)).on("error",o=>t(o))})}import Sa from"semver";import{z as wa}from"zod";var Ai="1.0.52",VR="https://registry.npmjs.org/momentic",qR=wa.object({versions:wa.record(wa.string(),wa.unknown()).optional()});async function Ty(n){if(!Ai){n.warn("Unable to check CLI version because CLI_VERSION is not set");return}let e;for(let r=0;r<2;r++)try{let o=await G(fetch(VR),{milliseconds:2e3});if(!o.ok)throw new Error(`Got error status code ${o.statusText}`);let i=await o.json();e=qR.parse(i).versions;break}catch(o){n.warn({err:o},"Failed to fetch npm registry data")}if(!e){n.warn("Failed to fetch npm registry data. Skipping version check.");return}let t;for(let r of Object.keys(e))Sa.valid(r)&&(!t||Sa.gt(r,t))&&Sa.gt(r,Ai)&&Sa.lt(r,"2.0.0")&&!r.includes("alpha")&&(t=r);t&&(v.warn(`Update available: v${Ai} -> v${t}`),v.warn("This version may be missing critical fixes, features, and security updates."),v.warn(`Run "npx momentic@${t} -V" to update`))}import{existsSync as YR,mkdirSync as JR,statSync as XR}from"fs";import{dirname as ZR}from"path";import Ey from"readline/promises";import{hostname as KR}from"os";var B=ha({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:KR(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0}).child({version:"1.0.52"});var hd=!1,Cy=(()=>{try{return XR("/.dockerenv"),!0}catch{return!1}})();async function Ge(n,e){if(pd||hd||Cy)return!0;B.flush(),await new Promise(s=>setTimeout(s,500));let t=Ey.createInterface({input:process.stdin,output:process.stdout}),r=n.split("."),o;if(r.length===1)o=n;else{let s=`${r.slice(0,r.length-1).join(".").trim()}.`;e?v.warn(s):v.log(s),o=r[r.length-1].trim()}let i=await t.question(`${o} ('y' for yes / n for no / 'A' to accept all) `);return t.close(),i==="A"?(hd=!0,setTimeout(()=>{hd=!1},3e3),!0):i.toLowerCase()==="y"}async function fd(n){let e=ZR(n);return Ci(e)?YR(n)?Ge(`File '${xy(n)}' already exists. Overwrite existing content?`,!0):!0:await Ge(`Directory '${xy(e)}' doesn't exist. Create it now?`,!0)?(JR(e,{recursive:!0}),!0):!1}function xy(n){return n.replace(/(\s+)/g,"\\$1")}async function Ay(n,e){if(pd||Cy)return e;let t=Ey.createInterface({input:process.stdin,output:process.stdout}),r=await t.question(`${n} `);return t.close(),r.trim()||e}async function Ry({test:n,fragment:e,entities:t,logger:r,yes:o}){eI(e.schemaVersion,nt)&&(v.error(`This version of the CLI does not support the schema version of the fragment (${e.schemaVersion}). Please update to the latest version of the CLI and retry this command.`),process.exit(1)),um(e.steps).forEach(p=>{t.modules[p]||(v.error(`The test patch contains a module with id ${p} that could not be found in the current project. The patch may be out of date, or you may need to pull the module locally before applying.`),process.exit(1))}),e.createdAt.getTime()<Date.now()-7*24*60*60*1e3&&!o&&await Ge("The test patch you are applying is more than 7 days old. Are you sure you want to continue?",!0)&&process.exit(1);let s=n.lastModified.getTime();e.createdAt.getTime()+60*60*1e3<s&&!o&&await Ge("The test patch you are applying was created before the test was last updated. Are you sure you want to continue?",!0)&&process.exit(1);let a,l;if(e.schemaVersion!==nt){let{steps:p,newVersion:h}=await Vo({metadata:{id:e.id,schemaVersion:e.schemaVersion},steps:e.steps,logger:v});a=h,l=me.array().parse(p)}else l=me.array().parse(e.steps);let{stepsToSave:c,moduleUpdates:u}=await wt({steps:l}),d=Ic(n.fullFilePath,r,t).steps;if(!Array.isArray(d))throw new Error(`Test ${n.fullFilePath} is missing steps array`);let m=QR(JSON.stringify(d,void 0,2),JSON.stringify(c,void 0,2),{n_surrounding:5});v.dimmed("=".repeat(30)),v.dimmed(m),v.dimmed("=".repeat(30)),v.dimmed(""),a&&v.warn(`If this patch is applied, your test will also be automatically upgraded to the latest schema version (${a}). Schema upgrades have no impact on functionality, although you may notice minor differences in the test YAML.`),!o&&!await Ge("Do you want to apply this patch?")&&(v.dimmed("Cancelled."),process.exit(1)),oi(n.relativePath,c,a??e.schemaVersion,t.project),v.success("Patch applied successfully.")}import{cloneDeep as tI}from"lodash-es";async function ba({client:n,skipPrompts:e,project:t}){let r=await n.getAllEnvironments(),o=tI(t.config);o.environments||(o.environments=[]);for(let i of r){let s=o.environments?.find(a=>a.name===i.name);if(s)!e&&!await Ge(`Environment ${i.name} already exists in the project configuration file. Would you like to overwrite its variables?`)&&process.exit(1),s.baseUrl=i.variables[ue],delete i.variables[ue],s.envVariables=i.variables;else{let a=i.variables[ue];delete i.variables[ue],o.environments.push({name:i.name,baseUrl:a,envVariables:i.variables})}}lo(o,t.configFilePath),v.success(`Pulled ${r.length} environments successfully! Please make sure to commit any changes to your project configuration file.`)}import{createHash as Iy}from"crypto";import Py from"fs";async function Ly({testsToFetch:n,client:e,all:t,yes:r}){let{tests:o,modules:i}=await e.getTestYAMLExport({paths:n,all:t}),s=0;for(let[l,c]of Object.entries(o)){let u=Oy(l,pe.TEST);!r&&!await fd(u)||(s+=1,Py.writeFileSync(u,c,"utf-8"),B.info({checksum:Iy("md5").update(c).digest("hex")},`Wrote '${u}'`))}let a=0;for(let[l,c]of Object.entries(i)){let u=Oy(l,pe.MODULE);!r&&!await fd(u)||(a+=1,Py.writeFileSync(u,c,"utf-8"),B.info({checksum:Iy("md5").update(c).digest("hex")},`Wrote '${u}'`))}s===0?v.success("Pulled 0 tests."):v.success(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}function Oy(n,e){switch(e){case pe.TEST:return`${Le(n)}.${un.TEST}`;case pe.MODULE:return`${Le(n)}.${un.MODULE}`;default:throw new Error(`Unknown entity type ${e}`)}}async function Ny(n){let{project:e,client:t,skipPrompts:r}=n;v.info("Welcome to the Momentic Cloud importer wizard! \u{1F636}\u200D\u{1F32B}\uFE0F"),v.info("Importing environments from Momentic Cloud."),v.info(`This command will overwrite all local environment configuration in ${e.configFilePath} with environments from Momentic Cloud.`),await Ge("Are you sure you want to proceed?",!0)||(v.info("Aborting..."),process.exit(1)),await ba({client:t,project:e,skipPrompts:r}),v.success(`Successfully imported environments from Momentic Cloud. We recommend pulling secrets out of ${e.configFilePath} into .env files or dynamically set environment variables for security.`),v.info("Importing tests and modules from Momentic Cloud."),await Ly({testsToFetch:[],client:t,all:!0,yes:r}),v.success("Successfully imported tests and modules from Momentic Cloud. You can move them to the desired location in your project. We recommend committing these files to version control so you can share them with team members and run Momentic tests in CI.")}import xt from"fs";import fr from"path";import nI from"yaml";function My(){Ci("momentic")||(v.error(`The migration command should be ran from the v0 root Momentic directory, which should contain a folder called 'momentic'. No folder named 'momentic' was found in the current working directory (${process.cwd()}).`),process.exit(1));let n={name:"default",include:Ti,environments:[]};v.info("Migrating environments");let e=_y(["momentic/environments"],rI);for(let r of e){let o=nI.parse(xt.readFileSync(r,"utf-8"));try{let i=Fi.parse(o),s=i.variables[ue]??"";delete i.variables[ue],n.environments?.push({name:i.name,baseUrl:s,envVariables:i.variables})}catch(i){v.error(`${r} failed to parse as a valid environment file.`),v.error(i),process.exit(1)}}v.info("Migrating tests");let t=Rc("./momentic",v);for(let r of t){let o=fr.join(...r.fullPathSegments),i=fr.join(fr.dirname(o),`${r.fileName.slice(0,-5)}.test.yaml`);v.info(`Moving test ${o} to ${i}`),xt.renameSync(o,i)}if(Ci("momentic/modules")){v.info("Migrating modules");for(let r of xt.readdirSync("./momentic/modules")){if(!r.endsWith(".yaml"))continue;let o=fr.resolve(fr.join("./momentic/modules",r));if(!xt.readFileSync(o,"utf-8").includes("schemaVersion")){v.warn(`Skipping file ${o} since it does not have valid Momentic module contents`);continue}let s=`${o.slice(0,-5)}.module.yaml`;v.info(`Moving module ${o} to ${s}`),xt.renameSync(o,s)}}return v.info("Writing new project configuration file"),lo(n,"momentic.config.yaml"),xt.rmSync("./momentic/environments",{recursive:!0,force:!0}),xt.rmSync("./momentic/fixtures",{recursive:!0,force:!0}),v.success("Migration succeeded!"),v.info("Going forward:"),v.info(` - You can store test and module files anywhere under the project root (${process.cwd()})`),v.info(" - Environment details and other common options are tracked in the root momentic.config.yaml file"),n}function _y(n,e,t=new Set){for(let r of n){let o=fr.resolve(r),i=!1;try{i=xt.existsSync(o)&&xt.statSync(o).isDirectory()}catch(s){v.error({err:s},`Error reading path ${o} during collect paths`)}if(o&&i){let s=xt.readdirSync(o).map(a=>fr.join(o,a));_y(s,e,t);continue}if(o.endsWith(".yaml")){try{if(!xt.existsSync(o)||!xt.statSync(o).isFile()){v.warn(`File not found or unreadable: ${o}`);continue}}catch(s){v.error({err:s},`Error reading file ${o} during collect paths`);continue}if(!e(o))continue;t.add(o)}}return t}function rI(n){return n.endsWith(".yaml")?xt.readFileSync(n,"utf8").includes("momentic/environment")?!0:(v.warn(`Skipping YAML that is not a Momentic environment: ${n}`),!1):!1}import{Argument as va,Option as xe}from"@commander-js/extra-typings";import{validateHeaderValue as oI}from"http";import{cpus as ky}from"os";import{parse as iI}from"yaml";import{z as j}from"zod";var Ta=58888,yd=30*60*1e3,Bn=new xe("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var."),gr=new xe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise."),jn=new xe("-y, --yes","Skip all confirmation prompts."),Sd=new xe("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),wd=new xe("--wait-timeout <waitTimeout>",`The maximum number of seconds to wait for tests to complete. Only applicable when the --wait option is specified. Defaults to ${yd/1e3} seconds.`),xa=new xe("--custom-headers <customHeaders...>","Specify custom headers in the form HEADER=VALUE to be sent with each request during the test. Multiple entries can be provided."),bd=new xe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),Dy=new xe("--reporter <reporter>","Output report files in a standardized format to a local directory. See the --reporter-dir flag for information on the output directory.").choices(Object.values(os)),Fy=new xe("--reporter-dir <reporterDir>","Output directory to store report files. Relative paths are resolved relative to the project root, which is defined by the detected momentic.config.yaml. Defaults to 'reports' if unset."),Uy=new xe("--include <includePatterns...>","Only include tests that match the provided regex patterns. Multiple patterns can be provided. The patterns will be matched against the test file paths and the pattern only needs to match a part of the path for the test to be included."),zy=new xe("--exclude <excludePatterns...>","The inverted version of --include: a test that matches any of the provided exclusion patterns will be excluded from running."),vd=new xe("--pixel-ratio <pixelRatio>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio."),By=new xe("--port <port>",`Port to run the app on. Defaults to ${Ta}.`),Td=new xe("--input-csv <inputCsv>","Path to a CSV file on disk where each row represents a set of inputs that will be made available to all tests that are ran. The first line of the CSV must be the input names."),Ea=new xe("--env <env>","Name of the environment to use when running tests."),Ca=new xe("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),jy=new xe("--shard-index <shardIndex>","The index of the shard to run tests for. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),mo=new xe("-c, --config <configPath>","Absolute or relative path to a Momentic configuration file (*.momentic.config.yaml)"),xd=new xe("-f, --filter <filter>","Run tests within the project that has a name equal to the filter provided. This option cannot be used together with file path or directory arguments, but substring matches are allowed."),$y=new xe("--shard-count <shardCount>","The number of shards that tests are being run on. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),Hy=new va("<tests...>",`One or more test paths to queue on Momentic Cloud.
2719
+ `)}import BI from"events";import mS from"fs";import jI from"open";import Ma from"path";import{fileURLToPath as $I}from"url";import QR from"diff-lines";import{gt as eI}from"semver";import{execSync as DR}from"child_process";import{platform as FR}from"os";function dd(){return by()?(v.info("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),v.info("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),v.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(v.info("Setting device pixel ratio to 1."),v.info("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),v.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function by(){return FR()==="darwin"&&DR("system_profiler SPDisplaysDataType").toString().includes("Retina")}function ud(n){by()&&n===1&&(v.warn("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),v.warn("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import UR from"@actions/exec";import zR from"@actions/io";import BR from"quote";import jR from"string-argv";async function vy(n,e=!0){let t=jR(n),r=await zR.which(t[0],!0),o=t.slice(1),i=UR.exec(BR(r),o,{delay:100});if(e)return i}import{existsSync as $R,statSync as HR}from"fs";var pd=!!process.env.CI||!process.stdout.isTTY;function Ci(n){try{return $R(n)&&HR(n).isDirectory()}catch(e){return v.error({err:e},`Error reading path ${n} during directory existence check`),!1}}import WR from"csv-parser";import{createReadStream as GR}from"fs";function md(n){return new Promise((e,t)=>{let r=[];GR(n).pipe(WR()).on("data",o=>r.push(o)).on("end",()=>e(r)).on("error",o=>t(o))})}import Sa from"semver";import{z as wa}from"zod";var Ai="1.0.53",VR="https://registry.npmjs.org/momentic",qR=wa.object({versions:wa.record(wa.string(),wa.unknown()).optional()});async function Ty(n){if(!Ai){n.warn("Unable to check CLI version because CLI_VERSION is not set");return}let e;for(let r=0;r<2;r++)try{let o=await G(fetch(VR),{milliseconds:2e3});if(!o.ok)throw new Error(`Got error status code ${o.statusText}`);let i=await o.json();e=qR.parse(i).versions;break}catch(o){n.warn({err:o},"Failed to fetch npm registry data")}if(!e){n.warn("Failed to fetch npm registry data. Skipping version check.");return}let t;for(let r of Object.keys(e))Sa.valid(r)&&(!t||Sa.gt(r,t))&&Sa.gt(r,Ai)&&Sa.lt(r,"2.0.0")&&!r.includes("alpha")&&(t=r);t&&(v.warn(`Update available: v${Ai} -> v${t}`),v.warn("This version may be missing critical fixes, features, and security updates."),v.warn(`Run "npx momentic@${t} -V" to update`))}import{existsSync as YR,mkdirSync as JR,statSync as XR}from"fs";import{dirname as ZR}from"path";import Ey from"readline/promises";import{hostname as KR}from"os";var B=ha({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:KR(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0}).child({version:"1.0.53"});var hd=!1,Cy=(()=>{try{return XR("/.dockerenv"),!0}catch{return!1}})();async function Ge(n,e){if(pd||hd||Cy)return!0;B.flush(),await new Promise(s=>setTimeout(s,500));let t=Ey.createInterface({input:process.stdin,output:process.stdout}),r=n.split("."),o;if(r.length===1)o=n;else{let s=`${r.slice(0,r.length-1).join(".").trim()}.`;e?v.warn(s):v.log(s),o=r[r.length-1].trim()}let i=await t.question(`${o} ('y' for yes / n for no / 'A' to accept all) `);return t.close(),i==="A"?(hd=!0,setTimeout(()=>{hd=!1},3e3),!0):i.toLowerCase()==="y"}async function fd(n){let e=ZR(n);return Ci(e)?YR(n)?Ge(`File '${xy(n)}' already exists. Overwrite existing content?`,!0):!0:await Ge(`Directory '${xy(e)}' doesn't exist. Create it now?`,!0)?(JR(e,{recursive:!0}),!0):!1}function xy(n){return n.replace(/(\s+)/g,"\\$1")}async function Ay(n,e){if(pd||Cy)return e;let t=Ey.createInterface({input:process.stdin,output:process.stdout}),r=await t.question(`${n} `);return t.close(),r.trim()||e}async function Ry({test:n,fragment:e,entities:t,logger:r,yes:o}){eI(e.schemaVersion,nt)&&(v.error(`This version of the CLI does not support the schema version of the fragment (${e.schemaVersion}). Please update to the latest version of the CLI and retry this command.`),process.exit(1)),um(e.steps).forEach(p=>{t.modules[p]||(v.error(`The test patch contains a module with id ${p} that could not be found in the current project. The patch may be out of date, or you may need to pull the module locally before applying.`),process.exit(1))}),e.createdAt.getTime()<Date.now()-7*24*60*60*1e3&&!o&&await Ge("The test patch you are applying is more than 7 days old. Are you sure you want to continue?",!0)&&process.exit(1);let s=n.lastModified.getTime();e.createdAt.getTime()+60*60*1e3<s&&!o&&await Ge("The test patch you are applying was created before the test was last updated. Are you sure you want to continue?",!0)&&process.exit(1);let a,l;if(e.schemaVersion!==nt){let{steps:p,newVersion:h}=await Vo({metadata:{id:e.id,schemaVersion:e.schemaVersion},steps:e.steps,logger:v});a=h,l=me.array().parse(p)}else l=me.array().parse(e.steps);let{stepsToSave:c,moduleUpdates:u}=await wt({steps:l}),d=Ic(n.fullFilePath,r,t).steps;if(!Array.isArray(d))throw new Error(`Test ${n.fullFilePath} is missing steps array`);let m=QR(JSON.stringify(d,void 0,2),JSON.stringify(c,void 0,2),{n_surrounding:5});v.dimmed("=".repeat(30)),v.dimmed(m),v.dimmed("=".repeat(30)),v.dimmed(""),a&&v.warn(`If this patch is applied, your test will also be automatically upgraded to the latest schema version (${a}). Schema upgrades have no impact on functionality, although you may notice minor differences in the test YAML.`),!o&&!await Ge("Do you want to apply this patch?")&&(v.dimmed("Cancelled."),process.exit(1)),oi(n.relativePath,c,a??e.schemaVersion,t.project),v.success("Patch applied successfully.")}import{cloneDeep as tI}from"lodash-es";async function ba({client:n,skipPrompts:e,project:t}){let r=await n.getAllEnvironments(),o=tI(t.config);o.environments||(o.environments=[]);for(let i of r){let s=o.environments?.find(a=>a.name===i.name);if(s)!e&&!await Ge(`Environment ${i.name} already exists in the project configuration file. Would you like to overwrite its variables?`)&&process.exit(1),s.baseUrl=i.variables[ue],delete i.variables[ue],s.envVariables=i.variables;else{let a=i.variables[ue];delete i.variables[ue],o.environments.push({name:i.name,baseUrl:a,envVariables:i.variables})}}lo(o,t.configFilePath),v.success(`Pulled ${r.length} environments successfully! Please make sure to commit any changes to your project configuration file.`)}import{createHash as Iy}from"crypto";import Py from"fs";async function Ly({testsToFetch:n,client:e,all:t,yes:r}){let{tests:o,modules:i}=await e.getTestYAMLExport({paths:n,all:t}),s=0;for(let[l,c]of Object.entries(o)){let u=Oy(l,pe.TEST);!r&&!await fd(u)||(s+=1,Py.writeFileSync(u,c,"utf-8"),B.info({checksum:Iy("md5").update(c).digest("hex")},`Wrote '${u}'`))}let a=0;for(let[l,c]of Object.entries(i)){let u=Oy(l,pe.MODULE);!r&&!await fd(u)||(a+=1,Py.writeFileSync(u,c,"utf-8"),B.info({checksum:Iy("md5").update(c).digest("hex")},`Wrote '${u}'`))}s===0?v.success("Pulled 0 tests."):v.success(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}function Oy(n,e){switch(e){case pe.TEST:return`${Le(n)}.${un.TEST}`;case pe.MODULE:return`${Le(n)}.${un.MODULE}`;default:throw new Error(`Unknown entity type ${e}`)}}async function Ny(n){let{project:e,client:t,skipPrompts:r}=n;v.info("Welcome to the Momentic Cloud importer wizard! \u{1F636}\u200D\u{1F32B}\uFE0F"),v.info("Importing environments from Momentic Cloud."),v.info(`This command will overwrite all local environment configuration in ${e.configFilePath} with environments from Momentic Cloud.`),await Ge("Are you sure you want to proceed?",!0)||(v.info("Aborting..."),process.exit(1)),await ba({client:t,project:e,skipPrompts:r}),v.success(`Successfully imported environments from Momentic Cloud. We recommend pulling secrets out of ${e.configFilePath} into .env files or dynamically set environment variables for security.`),v.info("Importing tests and modules from Momentic Cloud."),await Ly({testsToFetch:[],client:t,all:!0,yes:r}),v.success("Successfully imported tests and modules from Momentic Cloud. You can move them to the desired location in your project. We recommend committing these files to version control so you can share them with team members and run Momentic tests in CI.")}import xt from"fs";import fr from"path";import nI from"yaml";function My(){Ci("momentic")||(v.error(`The migration command should be ran from the v0 root Momentic directory, which should contain a folder called 'momentic'. No folder named 'momentic' was found in the current working directory (${process.cwd()}).`),process.exit(1));let n={name:"default",include:Ti,environments:[]};v.info("Migrating environments");let e=_y(["momentic/environments"],rI);for(let r of e){let o=nI.parse(xt.readFileSync(r,"utf-8"));try{let i=Fi.parse(o),s=i.variables[ue]??"";delete i.variables[ue],n.environments?.push({name:i.name,baseUrl:s,envVariables:i.variables})}catch(i){v.error(`${r} failed to parse as a valid environment file.`),v.error(i),process.exit(1)}}v.info("Migrating tests");let t=Rc("./momentic",v);for(let r of t){let o=fr.join(...r.fullPathSegments),i=fr.join(fr.dirname(o),`${r.fileName.slice(0,-5)}.test.yaml`);v.info(`Moving test ${o} to ${i}`),xt.renameSync(o,i)}if(Ci("momentic/modules")){v.info("Migrating modules");for(let r of xt.readdirSync("./momentic/modules")){if(!r.endsWith(".yaml"))continue;let o=fr.resolve(fr.join("./momentic/modules",r));if(!xt.readFileSync(o,"utf-8").includes("schemaVersion")){v.warn(`Skipping file ${o} since it does not have valid Momentic module contents`);continue}let s=`${o.slice(0,-5)}.module.yaml`;v.info(`Moving module ${o} to ${s}`),xt.renameSync(o,s)}}return v.info("Writing new project configuration file"),lo(n,"momentic.config.yaml"),xt.rmSync("./momentic/environments",{recursive:!0,force:!0}),xt.rmSync("./momentic/fixtures",{recursive:!0,force:!0}),v.success("Migration succeeded!"),v.info("Going forward:"),v.info(` - You can store test and module files anywhere under the project root (${process.cwd()})`),v.info(" - Environment details and other common options are tracked in the root momentic.config.yaml file"),n}function _y(n,e,t=new Set){for(let r of n){let o=fr.resolve(r),i=!1;try{i=xt.existsSync(o)&&xt.statSync(o).isDirectory()}catch(s){v.error({err:s},`Error reading path ${o} during collect paths`)}if(o&&i){let s=xt.readdirSync(o).map(a=>fr.join(o,a));_y(s,e,t);continue}if(o.endsWith(".yaml")){try{if(!xt.existsSync(o)||!xt.statSync(o).isFile()){v.warn(`File not found or unreadable: ${o}`);continue}}catch(s){v.error({err:s},`Error reading file ${o} during collect paths`);continue}if(!e(o))continue;t.add(o)}}return t}function rI(n){return n.endsWith(".yaml")?xt.readFileSync(n,"utf8").includes("momentic/environment")?!0:(v.warn(`Skipping YAML that is not a Momentic environment: ${n}`),!1):!1}import{Argument as va,Option as xe}from"@commander-js/extra-typings";import{validateHeaderValue as oI}from"http";import{cpus as ky}from"os";import{parse as iI}from"yaml";import{z as j}from"zod";var Ta=58888,yd=30*60*1e3,Bn=new xe("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var."),gr=new xe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise."),jn=new xe("-y, --yes","Skip all confirmation prompts."),Sd=new xe("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),wd=new xe("--wait-timeout <waitTimeout>",`The maximum number of seconds to wait for tests to complete. Only applicable when the --wait option is specified. Defaults to ${yd/1e3} seconds.`),xa=new xe("--custom-headers <customHeaders...>","Specify custom headers in the form HEADER=VALUE to be sent with each request during the test. Multiple entries can be provided."),bd=new xe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),Dy=new xe("--reporter <reporter>","Output report files in a standardized format to a local directory. See the --reporter-dir flag for information on the output directory.").choices(Object.values(os)),Fy=new xe("--reporter-dir <reporterDir>","Output directory to store report files. Relative paths are resolved relative to the project root, which is defined by the detected momentic.config.yaml. Defaults to 'reports' if unset."),Uy=new xe("--include <includePatterns...>","Only include tests that match the provided regex patterns. Multiple patterns can be provided. The patterns will be matched against the test file paths and the pattern only needs to match a part of the path for the test to be included."),zy=new xe("--exclude <excludePatterns...>","The inverted version of --include: a test that matches any of the provided exclusion patterns will be excluded from running."),vd=new xe("--pixel-ratio <pixelRatio>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio."),By=new xe("--port <port>",`Port to run the app on. Defaults to ${Ta}.`),Td=new xe("--input-csv <inputCsv>","Path to a CSV file on disk where each row represents a set of inputs that will be made available to all tests that are ran. The first line of the CSV must be the input names."),Ea=new xe("--env <env>","Name of the environment to use when running tests."),Ca=new xe("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),jy=new xe("--shard-index <shardIndex>","The index of the shard to run tests for. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),mo=new xe("-c, --config <configPath>","Absolute or relative path to a Momentic configuration file (*.momentic.config.yaml)"),xd=new xe("-f, --filter <filter>","Run tests within the project that has a name equal to the filter provided. This option cannot be used together with file path or directory arguments, but substring matches are allowed."),$y=new xe("--shard-count <shardCount>","The number of shards that tests are being run on. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),Hy=new va("<tests...>",`One or more test paths to queue on Momentic Cloud.
2720
2720
 
2721
2721
  A test path is a lowercased version of your test name where spaces are replaced with dashes: 'npx momentic@latest pull hello-world'.`),Wy=new va("<tests...>","One or more test file path or folders that exist on the local machine: 'npx momentic@latest run hello-world.test.yaml'.").argOptional(),Gy=new va("<suites...>",`One or more suite paths that exist on Momentic Cloud.
2722
2722
 
@@ -2732,6 +2732,6 @@ ${n.map(d=>` - ${d}`).join(`
2732
2732
  `)}`),Object.values(e.tests).forEach(d=>{n.some(m=>d.relativePath.includes(m))&&a.add(d.fullFilePath)}))}else{!r&&!await Ge("No test paths or substrings were provided. Do you want to run all tests?")&&(v.error("Cancelled by user."),process.exit(1));let c=Object.values(e.tests);v.info(`Reading all ${c.length} tests in the project from local disk.`),c.forEach(u=>{a.add(u.fullFilePath)})}for(let c of a){let u=Od.relative(t.rootDir,c);o&&!o.some(d=>new RegExp(d).test(u))&&a.delete(c),i&&i.some(d=>new RegExp(d).test(u))&&a.delete(c)}let l=Array.from(a).map(async c=>{try{let u=await qs(c,B,e);if(RI.gt(u.schemaVersion,nt)&&v.warn(`Test ${c} has schema version ${u.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`),u.disabled)return null;if(s&&s.length>0){let d=u.labels||[];if(!s.some(p=>d.includes(p)))return null}return{...u,fullFilePath:c,relativeFilePath:Od.relative(t.rootDir,c)}}catch(u){v.error(`Failed to read and resolve test at '${c}': ${u}`),process.exit(1)}});return Promise.all(l).then(CI)}import II from"simple-git";async function lS(){let n=II(),e={};e.gitCommitSha=await Ii(n.revparse(["HEAD"]))??process.env.GITHUB_SHA??process.env.CI_COMMIT_SHA,e.gitCommitShaShort=await Ii(n.revparse(["--short","HEAD"]))??process.env.CI_COMMIT_SHORT_SHA??e.gitCommitSha?.slice(0,6)??void 0,e.gitBranchName=await Ii(n.branchLocal().then(r=>r.current.trim()))??process.env.GITHUB_REF_NAME??process.env.CI_COMMIT_REF_NAME??void 0,e.gitOriginUrl=await Ii(n.listRemote(["--get-url","origin"]))??(process.env.GITHUB_SERVER_URL?`${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`:void 0)??process.env.CI_REPOSITORY_URL;let t=await Ii(n.show(["--no-patch","--format=%ci"]));return e.gitCommitTimestamp=t?new Date(t):void 0,e}async function Ii(n){try{return(await n).trim()}catch(e){B.error({err:e},"Failed to run git command");return}}import{diff as PI}from"deep-object-diff";import{cloneDeep as Na}from"lodash-es";import{debounce as OI}from"ts-debounce";import{v4 as dS}from"uuid";async function cS({orgId:n,codeEvalTools:e,logger:t,outputDefinitions:r,testContext:o}){let i={};for(let s of r){let{name:a,value:l}=s;i[a]=await zt({orgId:n,s:l,localTools:e,logger:t,context:o})}return i}async function uS(n){let e=new Date,{testDefinition:t}=n;try{return await LI(n)}catch(r){let o="Fatal error running test";return v.error(`${o}: ${r.message}`),B.error({err:r},o),{parameters:n,failureReason:"UnknownError",failureDetails:{errorMessage:r.message,errorStack:r.stack},status:"FAILED",attempts:0,test:t,filePath:t.relativeFilePath,startedAt:e,finishedAt:new Date,outputs:{}}}}async function LI({testDefinition:n,project:e,apiClient:t,billingReporter:r,generator:o,orgId:i,retriesOverride:s,noReport:a,envName:l,urlOverride:c,devicePixelRatio:u,customHeaders:d,testInputs:m,regenerateGoldenFiles:p,logUpdate:h,runGroupId:f}){let g=new Date,y={envName:l,urlOverride:c,customHeaders:d,testInputs:m},S=new Dr(t,i),T=new uo(e,p),w=new Fr(t,i),E=new Rn({httpClient:new en({baseUrl:t.baseUrl,apiKey:t.apiKey})}),C=n.steps,A=Na(n.steps);try{await S.resolveStepCacheEntries({organizationId:i,testId:n.id,steps:A,schemaVersion:n.schemaVersion,logger:B})}catch($){throw B.error({err:$},"Failed to resolve step cache entries"),new Error(`Failed to resolve step cache entries. Please ensure you are running using a supported version of Momentic. If you believe this is an error, please contact Momentic Support with the following error: ${$}`)}if(!l){for(let $ of n.envs??[])if($.default){l=$.name;break}}let P,O={};if(l){try{P=ii(l,e,B)}catch($){let tt=`Failed to resolve environment ${l} for test ${n.name}: ${$}`;throw new Error(tt)}O=P.variables}let D=n.baseUrl;if(c)D=c;else if(!D){let $=O[ue];typeof $=="string"&&(D=$)}if(!D){let $=`Cannot run test with no base URL and no ${ue} variable defined in its environment`;throw new Error($)}let _;if(a)_=dS();else try{_=(await t.createRun({stepsSnapshot:C,runGroupId:f,testId:n.id,testName:n.name,trigger:"CLI",resolvedBaseUrl:D,schemaVersion:n.schemaVersion})).id}catch($){throw B.error({err:$,testId:n.id},"Error creating run on Momentic Cloud"),new Error(`Error creating run on Momentic Cloud. Please ensure Momentic is accessible from your environment.
2733
2733
  If you believe this is an error, please contact Momentic Support with the following error message: ${$}`)}_&&(O[uh]=_);let N=B.child({testId:n.id,runId:_,orgId:i}),ee=async $=>{if(!a)try{await t.updateRun(_,$)}catch(tt){N.warn({err:tt},`Failed to update run status for test ${_}. You may see stale data on the Momentic Cloud Server.`)}},Ee,L=Math.max(s??n.retries,0),et=[];for(let $=0;$<=L;$++){let tt=dS(),Ce={...n,steps:Na(A)};if(!a)try{tt=(await t.createRunAttempt(_)).id}catch(oe){throw B.error({err:oe,testId:Ce.id,runId:_},"Error creating run attempt on Momentic Cloud"),new Error("Error creating run attempt on Momentic Cloud. Please ensure Momentic is accessible from your environment or pass the --no-report flag.")}$!==0&&h("RETRYING",`(attempt ${$+1}/${L+1})`);let ve=async oe=>{if(!a)try{await t.updateRunAttempt(_,tt,oe)}catch(M){N.warn({err:M},`Failed to update run attempt for attempt ${tt}. You may see stale data on the Momentic Cloud Server.`)}};try{await ee({status:"RUNNING",attempts:$+1});let{controller:oe,context:M,flagStore:ce}=await NI({baseUrl:D,envName:l,apiClient:t,devicePixelRatio:u,logger:N,storageClient:S,codeEvalTools:E,test:Ce,generator:o,orgId:i,variables:O,customHeaders:d,testInputs:m,localBrowserConfigFromEnv:P?.browser,visualDiffScreenshotStorage:T});Ee=await MI({attemptNumber:$+1,logger:N,storageClient:S,billingReporter:r,debugDataClient:w,codeEvalTools:E,flagStore:ce,apiClient:t,test:Ce,orgId:i,runId:_,context:M,controller:oe,noReport:a}),await ve({status:Ee.status,finishedAt:new Date}),et.unshift(Ee.status);let ze=await cS({orgId:i,codeEvalTools:E,logger:N,outputDefinitions:n.outputs??[],testContext:M});if(Ee.status!=="FAILED")return await ee({status:Ee.status,finishedAt:new Date,flake:Uu(et)}),{...Ee,parameters:y,runId:_,test:Ce,filePath:Ce.relativeFilePath,startedAt:g,finishedAt:new Date,attempts:$+1,baseUrl:D,outputs:ze};let Ct=Ee.failedStepResult,Ae=Ct?.message||"Unknown failure";if($<L){N.warn({errResult:Ct},`Test '${Ce.name}' failed attempt ${$+1} of ${L+1}, retrying...`);continue}N.error({errResult:Ct},`Test '${Ce.name}' failed after all exhausting ${L+1} attempts: ${Ae}
2734
2734
  `),await ee({status:"FAILED",failureReason:Ct?.failureReason,finishedAt:new Date});let Vt={errorMessage:Ae},yo=Ct?.failureReason??wp(Ae)??"UnknownError";return await ee({status:"FAILED",finishedAt:new Date,failureDetails:Vt,failureReason:yo}),{...Ee,parameters:y,runId:_,failureDetails:Vt,failureReason:yo,test:Ce,filePath:Ce.relativeFilePath,startedAt:g,finishedAt:new Date,attempts:$+1,baseUrl:D,outputs:ze}}catch(oe){let M=`Encountered fatal platform error while running test '${Ce.name}': ${oe}`;N.error({err:oe},M),v.error(M);let ce={errorMessage:oe.message,errStack:oe.stack},ze={status:"FAILED",failureDetails:ce,failureReason:"InternalPlatformError",finishedAt:new Date};return await Promise.all([ve(ze),ee(ze)]),{...ze,parameters:y,runId:_,test:Ce,filePath:Ce.relativeFilePath,startedAt:g,finishedAt:new Date,attempts:$+1,baseUrl:D,outputs:{}}}}throw new Error("This code should not be reachable")}async function NI({baseUrl:n,envName:e,devicePixelRatio:t,apiClient:r,test:o,storageClient:i,codeEvalTools:s,generator:a,orgId:l,variables:c,logger:u,customHeaders:d,testInputs:m,localBrowserConfigFromEnv:p,visualDiffScreenshotStorage:h}){let f=await Ls({advanced:{...p,...o.advanced},customHeaders:d,envVariables:c,envName:e,baseUrl:n,logger:u,localTools:s,orgId:l}),g=await $r.init(l),y={baseUrl:r.baseUrl,apiKey:r.apiKey};f.browserType==="Google Chrome"&&await yr(["chrome"]);let S;try{S=await rn.init({baseUrl:n,logger:u,userBrowserSettings:f,storage:i,featureFlagStore:g,enricher:new cr(y),timeout:f.pageLoadTimeoutMs,contextArgs:{viewport:o.advanced.viewport??Dt,deviceScaleFactor:t}})}catch(E){let C=E.message;if(C.includes("Executable doesn't exist")||C.includes("install your dependencies"))v.error("The headless browser used by Momentic is not installed correctly. Re-installing the necessary dependencies before starting the test. If this issue persists, pleases contact Momentic Support."),await yr(Ed,!0),S=await rn.init({baseUrl:n,logger:u,userBrowserSettings:f,storage:i,featureFlagStore:g,enricher:new cr(y),timeout:f.pageLoadTimeoutMs,contextArgs:{viewport:o.advanced.viewport??Dt,deviceScaleFactor:t}});else throw E}let T=new Kr({browser:S,generator:a,config:Ws,logger:u,orgId:l,storage:i,flagStore:g,localCodeEvalTools:s,visualDiffScreenshotStorage:h}),w=new pt({baseUrl:n,currentUrl:T.browser.url(),variablesFromEnvironment:c,envName:e});return o.parameters&&await Promise.all(o.parameters.map(async E=>{let{name:C,defaultValue:A,required:P}=E,O=m?.[C];P&&O===void 0&&(v.error(`Required parameter '${C}' is required by test '${o.name}' but not provided`),process.exit(1));let D=await zt({orgId:l,s:O??A,localTools:s,logger:u,context:pt.dummyContext(w.getEnvName())});w.setMomenticSystemVariable(C,D)})),{controller:T,context:w,flagStore:g}}async function MI({attemptNumber:n,storageClient:e,debugDataClient:t,apiClient:r,billingReporter:o,codeEvalTools:i,flagStore:s,test:a,orgId:l,runId:c,context:u,controller:d,noReport:m,logger:p}){B.info(`Running test '${a.name}' locally${m?"":` and reporting results to https://app.momentic.ai/runs/${c}`}`);let h=Na(a.steps),f=async A=>{try{if(A.results){let P=Na(A.results);ys(P,B),A.results=P}await r.updateRun(c,A)}catch(P){p.warn({err:P},"Failed to update run data. You may see stale data on the Momentic Cloud Server.")}},g=OI(async A=>{m||await f(A)},1e4,{maxWait:3e4}),y=async A=>{try{await g(A)}catch{}},S={controller:d,storage:e,debugDataStorage:t,billingReporter:o,context:u,logger:p,codeEvalTools:i},T={orgId:l,runId:c,testMetadata:a,steps:a.steps},w={collectDebugData:!0,reinitializeBrowser:!0,disableHealing:!await _I({currentAttempt:n,testId:a.id,flagStore:s,apiClient:r,testAdvancedSettings:a.advanced,logger:p})},E={step:{},test:{onTestComplete:async()=>{await d.browser.cleanup()},onSaveScreenshot:async A=>{if(m)return"";let{key:P}=await r.uploadScreenshot({screenshot:A.toString("base64")});return P},onUpdateRun:A=>{y(A)},onSaveFinalRunResults:async A=>{g.cancel(),await f(A)},onProposedTestSteps:async A=>r.uploadProposedSteps(A,p),onTestSuccess:async A=>{let P=PI(h,A);if(Object.keys(P).length===0){p.debug("No changes to test steps after success");return}p.debug({changes:P},"Updating steps post-run success in worker");try{let{cachesToSave:O}=await wt({steps:A,cacheCreationParams:{testId:a.id,orgId:l}});await r.updateStepCaches({testId:a.id,entries:O})}catch(O){p.error({err:O},"Failed to save step caches after successful execution. This is not critical, but can impact future performance.")}}}};return await o.reportBillableEvent(p,"test-run",{eventId:c,testId:a.id}),await Os({fixtures:S,inputs:T,options:w,callbacks:E})}async function _I({testId:n,flagStore:e,apiClient:t,testAdvancedSettings:r,logger:o}){if(r.disableFailureRecovery)return o.debug("Test advanced options has failure recovery explicitly disabled"),!1;if(!e.isBooleanFlagEnabled("failure_recovery_enabled"))return o.debug("Test is not eligible for recovery as the feature flag is disabled"),!1;let i;try{i=await t.getPastTestResults(n,{afterTime:Date.now()-7*24*60*60*1e3})}catch(a){return o.error({err:a},"Test is not eligible for recovery since we failed to fetch the recent test results"),!1}return i.some(a=>a.status==="PASSED")?!0:(o.debug({recentRuns:i},"Test is not eligible for recovery because there are only failures in the past 7 days"),!1)}async function pS(n){let{tests:e,yes:t,start:r,waitOn:o,client:i,billingReporter:s,project:a,report:l,retriesOverride:c,urlOverride:u,envName:d,orgId:m,devicePixelRatio:p,customHeaders:h,testInputMatrix:f,reporter:g,include:y,exclude:S,labels:T,reporterDir:w="reports",waitOnTimeout:E=60,parallel:C=1,shardIndex:A=1,shardCount:P=1,regenerateGoldenFiles:O}=n;r&&(B.info({orgId:m},`Executing start command: ${r}`),await vy(r,!1)),o&&(B.info({orgId:m},`Waiting for url: ${o} with timeout: ${E} seconds.`),await DI({resources:[o],interval:2500,timeout:E*1e3,headers:{Accept:"*/*"},followRedirect:!0,verbose:!1,log:!0,strictSSL:!1}));let D=new Yr({baseUrl:i.baseUrl,apiKey:i.apiKey}),_=at(a,v),N=await aS({tests:e,momenticFiles:_,yes:t,project:a,include:y,exclude:S,labels:T}),ee=[];N.forEach((ce,ze)=>{f?f.forEach((Ct,Ae)=>{ee.push({testIndex:ze,inputs:Ct,inputIndex:Ae})}):ee.push({testIndex:ze,inputs:void 0,inputIndex:void 0})}),P&&P>1&&(ee=FI(ee,A,P));let Ee=`Running ${ee.length} tests with ${C} workers`;B.info({allTestsToRunWithInputs:ee,shardCount:P,shardIndex:A,orgId:m},Ee),v.dimmed(Ee),ee.forEach(ce=>{v.dimmed(` - ${[N[ce.testIndex].relativeFilePath]}${typeof ce.inputIndex=="number"?` with input set ${ce.inputIndex}`:""}`)}),v.log("");let L=[],et=Date.now(),$=new Set,tt=()=>{let ce=i.getAppUrl(),ze=ho({results:L,startTime:et,onFailed:Ae=>{Oa(Ae,Ae.filePath)},getDisplayLine:Ae=>{let Vt=` - ${Ae.proposedTest?"[AUTO-HEALED] ":""}${Ae.filePath}`;return l&&Ae.runId&&(Vt+=` (${ce}/runs/${Ae.runId})`),Vt},entity:"test"}),Ct=L.filter(Ae=>Ae.proposedTest);return Ct.length>0&&v.warn(`${Ct.length} tests passed with auto-healing. Please use the run links printed above to review proposed changes and apply them locally.`),ze},{id:Ce}=await i.createRunGroup({...await lS(),trigger:Jt.CLI,startedAt:new Date,status:"RUNNING"}),ve=async()=>{v.warn("SIGINT received. Stopping tests and printing the existing results..."),await i.updateRunGroup(Ce,{status:"CANCELLED",finishedAt:new Date}),tt(),process.exit()};process.on("SIGINT",ve);let oe={};for(let ce=0;ce<ee.length;ce++){let ze=Object.values(oe);ze.length===C&&await Promise.race(ze.map(Vt=>Vt.promise));let Ct=ee[ce],Ae=`test-${ce}`;oe[Ae]={done:!1,promise:(async({testIndex:Vt,inputs:yo})=>{let So=N[Vt];$.add({testIndex:Vt,inputs:yo});let fS=So.relativeFilePath.includes("..")?So.fullFilePath:So.relativeFilePath,Pi=(Ve,Ld)=>{Ve=Ve.toUpperCase();let ln=Ve;Ve.includes("FAIL")?ln=go.bgRed.white(" FAILED "):Ve.includes("PASS")?ln=go.bgGreen.white(" PASSED "):Ve.includes("START")?ln=go.bgBlue.white("STARTING"):Ve.includes("CANCEL")?ln=go.bgYellow.white(" CANCEL "):Ve.includes("RETRY")?ln=go.bgYellow.white("RETRYING"):Ve.includes("RUN")||Ve.includes("PROG")?ln=go.bgMagenta.white("PROGRESS"):B.warn(`Unknown status tried to be logged in run test locally: ${Ve}`),kI||(ln=`[${ln}]`),v.log(`${ln} ${fS} ${Ld?`${Ld} `:""}(${$.size}/${ee.length})`)};Pi("START");let gS=setInterval(()=>Pi("RUN"),5*60*1e3);try{let Ve=await uS({testDefinition:So,project:a,testInputs:yo,orgId:m,devicePixelRatio:p,apiClient:i,billingReporter:s,generator:D,retriesOverride:c,urlOverride:u,envName:d,noReport:!l,customHeaders:h,regenerateGoldenFiles:O,logUpdate:Pi,runGroupId:Ce});Pi(Ve.status),L.push(Ve)}catch(Ve){v.error(`Encountered unexpected fatal error when running test '${So.name}': ${Ve.message}`)}finally{clearInterval(gS),oe[Ae].done=!0,delete oe[Ae]}})(Ct)}}await Promise.allSettled(Object.values(oe).map(ce=>ce.promise));let M="PASSED";return L.some(ce=>ce.status==="FAILED")&&(M="FAILED"),await i.updateRunGroup(Ce,{status:M,finishedAt:new Date}),process.off("SIGINT",ve),g&&await sS(g,{suiteName:`on-demand-suite-${et}`,startedAt:new Date(et),finishedAt:new Date,runs:L},w??"reports"),tt()}function FI(n,e,t){if(t>n.length&&(v.warn(`Shard count ${t} is greater than the number of tests ${n.length}! Some workers won't have any tests to run.`),t=Math.max(t,n.length),e>t))return[];let r=Math.floor((e-1)*(n.length/t)),o=Math.floor(e*(n.length/t));return n.sort().filter((s,a)=>a>=r&&a<o)}ka||B.warn("Sentry is not enabled in this environment due to unsupported node version");await Ty(B);var Et=new UI;Et.name("momentic").description("CLI").version(Ai||"unknown");Et.command("install-browsers").option("-f, --force","Force reinstallation even if the browser executables already exist on disk.").argument("[browsers...]","Browsers to install",["chromium"]).action(async(n,e)=>{await yr(Yy(n),e.force)});Et.addOption(new kt("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",n=>{B.setMinLevel(n.toLowerCase()),v.setMinLevel(n.toLowerCase())});Et.addOption(new kt("--verbose","enable verbose logging")).on("option:verbose",()=>{B.enableConsoleLogs(),v.setMinLevel(0)});Et.command("check-config").addOption(mo).action(async n=>{wn({configFilePath:n.config})});var HI=Et.command("migrate").description("Migrate and upgrade tooling");HI.command("v0-v1").addOption(jn).addOption(Bn).action(async n=>{let e=await $n(n);if(!e.yes&&!await Ge("This command will migrate and then delete your previous Momentic files. All members of your team should transition to the V1 CLI at the same time. Please backup your local directory for safety before proceeding. Continue?",!0)&&process.exit(1),!My().environments?.length&&await Ge("In the V1 CLI, all environment configuration should be committed to a central `momentic.config.yaml` file, which you should commit to your source control repository. Sensitive data can be managed through `.env` files or by injecting the secret into the environment and then referencing them in `envVariables` with ${VAR} syntax. Would you like to pull the latest environments from Momentic Cloud into your `momentic.config.yaml` file?",!0)){let{apiKey:r,server:o}=e,i=wn({}),s=new Je({baseUrl:o,apiKey:r,logger:B});await ba({client:s,project:i,skipPrompts:e.yes}),v.success("Successfully imported environments from Momentic Cloud.")}});Et.command("import-from-cloud").addOption(Bn).addOption(gr).addOption(mo).addOption(jn).action(async n=>{let e=await $n(n),{apiKey:t,server:r,config:o,yes:i}=e,s=wn({configFilePath:o}),a=new Je({baseUrl:r,apiKey:t,logger:B});await Ny({client:a,project:s,skipPrompts:i}),process.exit(0)});Et.command("init").description("Initialize an empty Momentic project in the current working directory").addOption(new kt("--name <name>","Name of the project")).action(async n=>{v.info(`Welcome to the Momentic project setup wizard! \u{1F680}
2735
- `),v.info("This wizard will help you bootstrap a new Momentic project. If need to import existing assets from Momentic Cloud, you can call the 'import-from-cloud' command after initialization."),mS.existsSync(ao)&&(v.error("A momentic.config.yaml file already exists in this directory. Please rename or remove it to initialize a new project."),process.exit(1));let t={name:n.name??await Ay("Choose an identifier for your project, such as a service, product, or team name (default: 'app'):","app"),include:Ti};lo(t,ao),v.success(`Initialized Momentic project file at ${Ma.resolve(ao)}`)});Et.command("app").addOption(Bn).addOption(gr).addOption(jn).addOption(vd).addOption(By).addOption(mo).action(async n=>{let e=await $n(n),{apiKey:t,port:r=Ta,yes:o,server:i,pixelRatio:s}=e,a=new Je({baseUrl:i,apiKey:t,logger:B}),l=await Cd({client:a,skipPrompts:o,installBrowsers:!0});Hr.capture({event:"app started",distinctId:l,properties:{organizationId:l}});let c=$I(import.meta.url),u=Ma.dirname(c),d=Ma.resolve(u,"..","static"),m=Ma.resolve(u,"..","assets"),p=s??dd();ud(p),wo(),await Sy({momenticServerUrl:i,apiKey:t,serverPort:r,appPort:r,staticDir:d,assetsDir:m,devicePixelRatio:p,version:"1.0.52"});let h=`http://localhost:${r}`;await jI(h)});var hS=Et.command("queue").description("Queue tests or suites to run on Momentic Cloud");hS.command("suites").description("Run one or more suites on Momentic Cloud").addOption(Bn).addOption(gr).addOption(Sd).addOption(wd).addOption(jn).addArgument(Gy).addOption(Ca).addOption(Ea).addOption(xa).action(async(n,e)=>{let{apiKey:t,server:r,wait:o,waitTimeout:i,env:s,urlOverride:a,customHeaders:l}=await $n(e),c=new Je({baseUrl:r,apiKey:t,logger:B});(!n||!Array.isArray(n)||!n.length)&&(v.error("Must pass at least one suite to run."),process.exit(1));let u=await c.getOrgId();Hr.capture({event:"queue suites remotely",distinctId:u,properties:{organizationId:u,numSuites:n.length}}),await Qy({client:c,orgId:u,wait:o,suitePaths:n,waitTimeout:i,env:s,urlOverride:a,customHeaders:Aa(l)})});hS.command("tests").description("Run one or more tests on Momentic Cloud").addOption(Bn).addOption(gr).addOption(jn).addOption(xa).addOption(Td).addOption(Ca).addOption(Ea).addOption(Sd).addOption(wd).addArgument(Hy).action(async(n,e)=>{let t=await $n(e),{all:r,apiKey:o,customHeaders:i,env:s,server:a,inputCsv:l,urlOverride:c,wait:u,waitTimeout:d,yes:m}=t,p=Aa(i);for(let y of n)(y.endsWith(".yaml")||mS.existsSync(y))&&v.warn("Are you trying to run a test on your local machine? If so, please use the 'run' command instead of the 'queue' command");let h=new Je({baseUrl:a,apiKey:o,logger:B}),f=await h.getOrgId();Hr.capture({event:"queue tests remotely",distinctId:f,properties:{organizationId:f,numTests:n.length}});let g;l&&(g=await md(l)),await eS({client:h,orgId:f,tests:n,all:r,customHeaders:p,env:s,urlOverride:c,wait:u,waitTimeout:d,testInputMatrix:g,yes:m}),process.exit(0)});var WI=Et.command("run").alias("test").description("Run tests on the local machine");WI.addOption(Bn).addOption(gr).addOption(mo).addOption(xd).addOption(jn).addOption(xa).addOption(Td).addOption(Ea).addOption(Ca).addOption(bd).addOption(vd).addOption(new kt("--start <start>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new kt("--wait-on <waitOn>","URL to wait to become accessible before Momentic tests begin.")).addOption(new kt("--wait-on-timeout <waitOnTimeout>","Max time in seconds to wait for the --wait-on URL to become accessible.")).addOption(new kt("--retries <retries>","Number of retries to attempt when running tests locally. Defaults to each test's own retry configuration.")).addOption(new kt("-p, --parallel <parallel>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.")).addOption(new kt("--labels <labels...>","Only run tests with the specified label(s).")).addOption(new kt("--update-golden-files","Update locally stored golden files for steps that this is enabled for.")).addOption(bd).addOption(Dy).addOption(Fy).addOption(jy).addOption($y).addOption(Uy).addOption(zy).addArgument(Wy).action(async(n,e)=>{let t=await $n(e),r=Aa(t.customHeaders),o=wn({configFilePath:t.config,nameFilter:t.filter}),i=new Je({baseUrl:t.server,apiKey:t.apiKey,logger:B}),s=new xs(i),a;t.inputCsv&&(a=await md(t.inputCsv));let l=await Cd({client:i,skipPrompts:t.yes,installBrowsers:!0});Hr.capture({event:"run tests locally",distinctId:l,properties:{organizationId:l}});let c=t.pixelRatio??dd();ud(c);try{(await pS({...t,retriesOverride:t.retries,devicePixelRatio:c,tests:n,project:o,client:i,billingReporter:s,customHeaders:r,envName:t.env,orgId:l,testInputMatrix:a,regenerateGoldenFiles:t.updateGoldenFiles})).failed>0?process.exit(1):process.exit(0)}catch(u){v.error("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),v.error(u),process.exit(1)}});var GI=Et.command("apply").description("Apply an operation to local resources");GI.command("patch").addOption(Bn).addOption(gr).addOption(mo).addOption(xd).addOption(jn).addOption(new kt("--from <from>","Name or ID of the patch to apply").makeOptionMandatory()).addOption(new kt("--to <to>","Name or ID of the test to apply the patch to").makeOptionMandatory()).action(async n=>{let e=await $n(n),{apiKey:t,server:r,config:o,yes:i}=e,s=wn({configFilePath:o}),a=new Je({baseUrl:r,apiKey:t,logger:B}),l=at(s,v),c=l.tests[n.to]??Object.values(l.tests).find(d=>d.name===n.to);c||(v.error(`No test matching '${n.to}' could be found in the current project.`),process.exit(1));let u=await a.fetchTestFragment(n.from);await Ry({test:c,fragment:u,yes:i,entities:l,logger:B}),process.exit(0)});async function VI(){try{await Et.parseAsync(process.argv),wo()}catch(n){let e={};try{e.playwrightVersion=zI("npx playwright --version").toString()}catch(t){B.error({err:t},"Error fetching debug information")}B.error({err:n,debugInfo:e},"Uncaught error in CLI"),B.flush(),_a(n,e),v.error(n),wo(),process.exit(1)}}BI.setMaxListeners(50);process.on("warning",n=>{B.warn({err:n},`Node warning received on CLI: ${n.message}`)});VI();
2735
+ `),v.info("This wizard will help you bootstrap a new Momentic project. If need to import existing assets from Momentic Cloud, you can call the 'import-from-cloud' command after initialization."),mS.existsSync(ao)&&(v.error("A momentic.config.yaml file already exists in this directory. Please rename or remove it to initialize a new project."),process.exit(1));let t={name:n.name??await Ay("Choose an identifier for your project, such as a service, product, or team name (default: 'app'):","app"),include:Ti};lo(t,ao),v.success(`Initialized Momentic project file at ${Ma.resolve(ao)}`)});Et.command("app").addOption(Bn).addOption(gr).addOption(jn).addOption(vd).addOption(By).addOption(mo).action(async n=>{let e=await $n(n),{apiKey:t,port:r=Ta,yes:o,server:i,pixelRatio:s}=e,a=new Je({baseUrl:i,apiKey:t,logger:B}),l=await Cd({client:a,skipPrompts:o,installBrowsers:!0});Hr.capture({event:"app started",distinctId:l,properties:{organizationId:l}});let c=$I(import.meta.url),u=Ma.dirname(c),d=Ma.resolve(u,"..","static"),m=Ma.resolve(u,"..","assets"),p=s??dd();ud(p),wo(),await Sy({momenticServerUrl:i,apiKey:t,serverPort:r,appPort:r,staticDir:d,assetsDir:m,devicePixelRatio:p,version:"1.0.53"});let h=`http://localhost:${r}`;await jI(h)});var hS=Et.command("queue").description("Queue tests or suites to run on Momentic Cloud");hS.command("suites").description("Run one or more suites on Momentic Cloud").addOption(Bn).addOption(gr).addOption(Sd).addOption(wd).addOption(jn).addArgument(Gy).addOption(Ca).addOption(Ea).addOption(xa).action(async(n,e)=>{let{apiKey:t,server:r,wait:o,waitTimeout:i,env:s,urlOverride:a,customHeaders:l}=await $n(e),c=new Je({baseUrl:r,apiKey:t,logger:B});(!n||!Array.isArray(n)||!n.length)&&(v.error("Must pass at least one suite to run."),process.exit(1));let u=await c.getOrgId();Hr.capture({event:"queue suites remotely",distinctId:u,properties:{organizationId:u,numSuites:n.length}}),await Qy({client:c,orgId:u,wait:o,suitePaths:n,waitTimeout:i,env:s,urlOverride:a,customHeaders:Aa(l)})});hS.command("tests").description("Run one or more tests on Momentic Cloud").addOption(Bn).addOption(gr).addOption(jn).addOption(xa).addOption(Td).addOption(Ca).addOption(Ea).addOption(Sd).addOption(wd).addArgument(Hy).action(async(n,e)=>{let t=await $n(e),{all:r,apiKey:o,customHeaders:i,env:s,server:a,inputCsv:l,urlOverride:c,wait:u,waitTimeout:d,yes:m}=t,p=Aa(i);for(let y of n)(y.endsWith(".yaml")||mS.existsSync(y))&&v.warn("Are you trying to run a test on your local machine? If so, please use the 'run' command instead of the 'queue' command");let h=new Je({baseUrl:a,apiKey:o,logger:B}),f=await h.getOrgId();Hr.capture({event:"queue tests remotely",distinctId:f,properties:{organizationId:f,numTests:n.length}});let g;l&&(g=await md(l)),await eS({client:h,orgId:f,tests:n,all:r,customHeaders:p,env:s,urlOverride:c,wait:u,waitTimeout:d,testInputMatrix:g,yes:m}),process.exit(0)});var WI=Et.command("run").alias("test").description("Run tests on the local machine");WI.addOption(Bn).addOption(gr).addOption(mo).addOption(xd).addOption(jn).addOption(xa).addOption(Td).addOption(Ea).addOption(Ca).addOption(bd).addOption(vd).addOption(new kt("--start <start>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new kt("--wait-on <waitOn>","URL to wait to become accessible before Momentic tests begin.")).addOption(new kt("--wait-on-timeout <waitOnTimeout>","Max time in seconds to wait for the --wait-on URL to become accessible.")).addOption(new kt("--retries <retries>","Number of retries to attempt when running tests locally. Defaults to each test's own retry configuration.")).addOption(new kt("-p, --parallel <parallel>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.")).addOption(new kt("--labels <labels...>","Only run tests with the specified label(s).")).addOption(new kt("--update-golden-files","Update locally stored golden files for steps that this is enabled for.")).addOption(bd).addOption(Dy).addOption(Fy).addOption(jy).addOption($y).addOption(Uy).addOption(zy).addArgument(Wy).action(async(n,e)=>{let t=await $n(e),r=Aa(t.customHeaders),o=wn({configFilePath:t.config,nameFilter:t.filter}),i=new Je({baseUrl:t.server,apiKey:t.apiKey,logger:B}),s=new xs(i),a;t.inputCsv&&(a=await md(t.inputCsv));let l=await Cd({client:i,skipPrompts:t.yes,installBrowsers:!0});Hr.capture({event:"run tests locally",distinctId:l,properties:{organizationId:l}});let c=t.pixelRatio??dd();ud(c);try{(await pS({...t,retriesOverride:t.retries,devicePixelRatio:c,tests:n,project:o,client:i,billingReporter:s,customHeaders:r,envName:t.env,orgId:l,testInputMatrix:a,regenerateGoldenFiles:t.updateGoldenFiles})).failed>0?process.exit(1):process.exit(0)}catch(u){v.error("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),v.error(u),process.exit(1)}});var GI=Et.command("apply").description("Apply an operation to local resources");GI.command("patch").addOption(Bn).addOption(gr).addOption(mo).addOption(xd).addOption(jn).addOption(new kt("--from <from>","Name or ID of the patch to apply").makeOptionMandatory()).addOption(new kt("--to <to>","Name or ID of the test to apply the patch to").makeOptionMandatory()).action(async n=>{let e=await $n(n),{apiKey:t,server:r,config:o,yes:i}=e,s=wn({configFilePath:o}),a=new Je({baseUrl:r,apiKey:t,logger:B}),l=at(s,v),c=l.tests[n.to]??Object.values(l.tests).find(d=>d.name===n.to);c||(v.error(`No test matching '${n.to}' could be found in the current project.`),process.exit(1));let u=await a.fetchTestFragment(n.from);await Ry({test:c,fragment:u,yes:i,entities:l,logger:B}),process.exit(0)});async function VI(){try{await Et.parseAsync(process.argv),wo()}catch(n){let e={};try{e.playwrightVersion=zI("npx playwright --version").toString()}catch(t){B.error({err:t},"Error fetching debug information")}B.error({err:n,debugInfo:e},"Uncaught error in CLI"),B.flush(),_a(n,e),v.error(n),wo(),process.exit(1)}}BI.setMaxListeners(50);process.on("warning",n=>{B.warn({err:n},`Node warning received on CLI: ${n.message}`)});VI();
2736
2736
  //# sourceMappingURL=cli.js.map
2737
2737
  //# debugId=fc8e8fe2-dfb0-5008-af54-858340bd6ed1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momentic",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "bin": {
5
5
  "momentic": "./bin/cli.js"
6
6
  },