@lambdatest/smartui-cli 3.0.12 → 4.0.0

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.cjs +305 -68
  2. package/package.json +2 -1
package/dist/index.cjs CHANGED
@@ -16,6 +16,7 @@ var axios = require('axios');
16
16
  var child_process = require('child_process');
17
17
  var spawn = require('cross-spawn');
18
18
  var test = require('@playwright/test');
19
+ var sharp = require('sharp');
19
20
 
20
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
22
 
@@ -29,6 +30,7 @@ var addErrors__default = /*#__PURE__*/_interopDefault(addErrors);
29
30
  var FormData__default = /*#__PURE__*/_interopDefault(FormData);
30
31
  var axios__default = /*#__PURE__*/_interopDefault(axios);
31
32
  var spawn__default = /*#__PURE__*/_interopDefault(spawn);
33
+ var sharp__default = /*#__PURE__*/_interopDefault(sharp);
32
34
 
33
35
  var __defProp = Object.defineProperty;
34
36
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -128,6 +130,18 @@ var constants_default = {
128
130
  MOBILE_ORIENTATION_LANDSCAPE: "landscape",
129
131
  // CI
130
132
  GITHUB_API_HOST: "https://api.github.com",
133
+ // log file path
134
+ LOG_FILE_PATH: ".smartui.log",
135
+ // Disallowed file extension
136
+ FILE_EXTENSION_ZIP: ".zip",
137
+ FILE_EXTENSION_GIFS: "gif",
138
+ // Magic Numbers
139
+ MAGIC_NUMBERS: [
140
+ { ext: "jpg", magic: Buffer.from([255, 216, 255]) },
141
+ { ext: "jpeg", magic: Buffer.from([255, 216, 255]) },
142
+ { ext: "png", magic: Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]) },
143
+ { ext: "gif", magic: Buffer.from([71, 73, 70, 56]) }
144
+ ],
131
145
  SUPPORTED_MOBILE_DEVICES: {
132
146
  "Blackberry KEY2 LE": { os: "android", viewport: { width: 412, height: 618 } },
133
147
  "Galaxy A12": { os: "android", viewport: { width: 360, height: 800 } },
@@ -632,7 +646,15 @@ var validateFigmaDesignConfig = ajv.compile(FigmaDesignConfigSchema);
632
646
 
633
647
  // src/lib/server.ts
634
648
  var server_default = (ctx) => __async(void 0, null, function* () {
635
- const server = fastify__default.default({ logger: ctx.env.LT_SDK_DEBUG ? true : false, bodyLimit: 3e7 });
649
+ const server = fastify__default.default({
650
+ logger: {
651
+ level: "debug",
652
+ stream: { write: (message) => {
653
+ ctx.log.debug(message);
654
+ } }
655
+ },
656
+ bodyLimit: 3e7
657
+ });
636
658
  const opts = {};
637
659
  const SMARTUI_DOM = fs5.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
638
660
  server.get("/healthcheck", opts, (_, reply) => {
@@ -672,7 +694,6 @@ var env_default = () => {
672
694
  const {
673
695
  PROJECT_TOKEN = "",
674
696
  SMARTUI_CLIENT_API_URL = "https://api.lambdatest.com/visualui/1.0",
675
- LT_SDK_LOG_LEVEL,
676
697
  LT_SDK_DEBUG,
677
698
  SMARTUI_GIT_INFO_FILEPATH,
678
699
  HTTP_PROXY,
@@ -687,7 +708,6 @@ var env_default = () => {
687
708
  return {
688
709
  PROJECT_TOKEN,
689
710
  SMARTUI_CLIENT_API_URL,
690
- LT_SDK_LOG_LEVEL,
691
711
  LT_SDK_DEBUG,
692
712
  SMARTUI_GIT_INFO_FILEPATH,
693
713
  HTTP_PROXY,
@@ -700,37 +720,37 @@ var env_default = () => {
700
720
  CURRENT_BRANCH
701
721
  };
702
722
  };
703
- var logContext = { task: "smartui-cli" };
723
+ var logContext = {};
704
724
  function updateLogContext(newContext) {
705
725
  logContext = __spreadValues(__spreadValues({}, logContext), newContext);
706
726
  }
707
727
  var logLevel = () => {
708
728
  let env = env_default();
709
- let debug = env.LT_SDK_DEBUG === "true" ? "debug" : void 0;
710
- return debug || env.LT_SDK_LOG_LEVEL || "info";
729
+ return env.LT_SDK_DEBUG === "true" ? "debug" : "info";
711
730
  };
712
731
  var logger = winston.createLogger({
713
- level: logLevel(),
714
732
  format: winston.format.combine(
715
733
  winston.format.timestamp(),
716
734
  winston.format.printf((info) => {
717
735
  let contextString = Object.values(logContext).join(" | ");
718
- let message = typeof info.message === "object" ? JSON.stringify(info.message) : info.message;
736
+ let message = typeof info.message === "object" ? JSON.stringify(info.message).trim() : info.message.trim();
719
737
  switch (info.level) {
720
- case "debug":
721
- message = chalk7__default.default.blue(message);
722
- break;
723
738
  case "warn":
724
739
  message = chalk7__default.default.yellow(message);
725
740
  break;
726
- case "error":
727
- message = chalk7__default.default.red(message);
728
- break;
729
741
  }
730
742
  return info.level === "info" ? message : `[${contextString}:${info.level}] ` + message;
731
743
  })
732
744
  ),
733
- transports: [new winston.transports.Console(), new winston.transports.File({ filename: ".smartui.log" })]
745
+ transports: [
746
+ new winston.transports.Console({
747
+ level: logLevel()
748
+ }),
749
+ new winston.transports.File({
750
+ level: "debug",
751
+ filename: constants_default.LOG_FILE_PATH
752
+ })
753
+ ]
734
754
  });
735
755
  var logger_default = logger;
736
756
 
@@ -774,7 +794,7 @@ var auth_default = (ctx) => {
774
794
  };
775
795
 
776
796
  // package.json
777
- var version = "3.0.12";
797
+ var version = "4.0.0";
778
798
  var package_default = {
779
799
  name: "@lambdatest/smartui-cli",
780
800
  version,
@@ -814,6 +834,7 @@ var package_default = {
814
834
  fastify: "^4.24.3",
815
835
  "form-data": "^4.0.0",
816
836
  listr2: "^7.0.1",
837
+ sharp: "^0.33.4",
817
838
  tsup: "^7.2.0",
818
839
  which: "^4.0.0",
819
840
  winston: "^3.10.0"
@@ -829,11 +850,11 @@ var httpClient = class {
829
850
  headers: { "projectToken": PROJECT_TOKEN }
830
851
  });
831
852
  }
832
- request(config, log) {
853
+ request(config, log2) {
833
854
  return __async(this, null, function* () {
834
- log.debug(`http request: ${config.method} ${config.url}`);
855
+ log2.debug(`http request: ${config.method} ${config.url}`);
835
856
  return this.axiosInstance.request(config).then((resp) => {
836
- log.debug(`http response: ${JSON.stringify({
857
+ log2.debug(`http response: ${JSON.stringify({
837
858
  status: resp.status,
838
859
  headers: resp.headers,
839
860
  body: resp.data
@@ -842,7 +863,7 @@ var httpClient = class {
842
863
  }).catch((error) => {
843
864
  var _a;
844
865
  if (error.response) {
845
- log.debug(`http response: ${JSON.stringify({
866
+ log2.debug(`http response: ${JSON.stringify({
846
867
  status: error.response.status,
847
868
  headers: error.response.headers,
848
869
  body: error.response.data
@@ -850,21 +871,21 @@ var httpClient = class {
850
871
  throw new Error(((_a = error.response.data.error) == null ? void 0 : _a.message) || error.response.data.message);
851
872
  }
852
873
  if (error.request) {
853
- log.debug(`http request failed: ${error.toJSON()}`);
874
+ log2.debug(`http request failed: ${error.toJSON()}`);
854
875
  throw new Error(error.toJSON().message);
855
876
  }
856
- log.debug(`http request failed: ${error.message}`);
877
+ log2.debug(`http request failed: ${error.message}`);
857
878
  throw new Error(error.message);
858
879
  });
859
880
  });
860
881
  }
861
- auth(log) {
882
+ auth(log2) {
862
883
  return this.request({
863
884
  url: "/token/verify",
864
885
  method: "GET"
865
- }, log);
886
+ }, log2);
866
887
  }
867
- createBuild(git, config, log) {
888
+ createBuild(git, config, log2) {
868
889
  return this.request({
869
890
  url: "/build",
870
891
  method: "POST",
@@ -872,9 +893,9 @@ var httpClient = class {
872
893
  git,
873
894
  config
874
895
  }
875
- }, log);
896
+ }, log2);
876
897
  }
877
- finalizeBuild(buildId, totalSnapshots, log) {
898
+ finalizeBuild(buildId, totalSnapshots, log2) {
878
899
  let params = { buildId };
879
900
  if (totalSnapshots > -1)
880
901
  params.totalSnapshots = totalSnapshots;
@@ -882,7 +903,7 @@ var httpClient = class {
882
903
  url: "/build",
883
904
  method: "DELETE",
884
905
  params
885
- }, log);
906
+ }, log2);
886
907
  }
887
908
  uploadSnapshot(ctx, snapshot) {
888
909
  return this.request({
@@ -898,7 +919,7 @@ var httpClient = class {
898
919
  }
899
920
  }, ctx.log);
900
921
  }
901
- uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log) {
922
+ uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log2) {
902
923
  browserName = browserName === constants_default.SAFARI ? constants_default.WEBKIT : browserName;
903
924
  const file = fs5__default.default.readFileSync(ssPath);
904
925
  const form = new FormData__default.default();
@@ -915,7 +936,7 @@ var httpClient = class {
915
936
  headers: form.getHeaders(),
916
937
  data: form
917
938
  }).then(() => {
918
- log.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
939
+ log2.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
919
940
  }).catch((error) => {
920
941
  if (error.response) {
921
942
  throw new Error(error.response.data.error.message);
@@ -926,7 +947,7 @@ var httpClient = class {
926
947
  throw new Error(error.message);
927
948
  });
928
949
  }
929
- checkUpdate(log) {
950
+ checkUpdate(log2) {
930
951
  return this.request({
931
952
  url: `/packageinfo`,
932
953
  method: "GET",
@@ -935,9 +956,9 @@ var httpClient = class {
935
956
  packageName: package_default.name,
936
957
  packageVersion: package_default.version
937
958
  }
938
- }, log);
959
+ }, log2);
939
960
  }
940
- getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName, log) {
961
+ getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName, log2) {
941
962
  const requestBody = {
942
963
  figma_file_token: figmaFileToken,
943
964
  figma_token: figmaToken,
@@ -954,7 +975,34 @@ var httpClient = class {
954
975
  "Content-Type": "application/json"
955
976
  },
956
977
  data: JSON.stringify(requestBody)
957
- }, log);
978
+ }, log2);
979
+ }
980
+ getS3PreSignedURL(ctx) {
981
+ return this.request({
982
+ url: `/loguploadurl`,
983
+ method: "POST",
984
+ headers: { "Content-Type": "application/json" },
985
+ data: {
986
+ buildId: ctx.build.id
987
+ }
988
+ }, ctx.log);
989
+ }
990
+ uploadLogs(ctx, uploadURL) {
991
+ const fileStream = fs5__default.default.createReadStream(constants_default.LOG_FILE_PATH);
992
+ const { size } = fs5__default.default.statSync(constants_default.LOG_FILE_PATH);
993
+ return this.request({
994
+ url: uploadURL,
995
+ method: "PUT",
996
+ headers: {
997
+ "Content-Type": "text/plain",
998
+ "Content-Length": size
999
+ },
1000
+ data: fileStream,
1001
+ maxBodyLength: Infinity,
1002
+ // prevent axios from limiting the body size
1003
+ maxContentLength: Infinity
1004
+ // prevent axios from limiting the content size
1005
+ }, ctx.log);
958
1006
  }
959
1007
  };
960
1008
  var ctx_default = (options) => {
@@ -964,6 +1012,10 @@ var ctx_default = (options) => {
964
1012
  let mobileConfig;
965
1013
  let config = constants_default.DEFAULT_CONFIG;
966
1014
  let port;
1015
+ let resolutionOff;
1016
+ let extensionFiles;
1017
+ let ignoreStripExtension;
1018
+ let ignoreFilePattern;
967
1019
  try {
968
1020
  if (options.config) {
969
1021
  config = JSON.parse(fs5__default.default.readFileSync(options.config, "utf-8"));
@@ -979,6 +1031,10 @@ var ctx_default = (options) => {
979
1031
  if (isNaN(port) || port < 1 || port > 65535) {
980
1032
  throw new Error("Invalid port number. Port number must be an integer between 1 and 65535.");
981
1033
  }
1034
+ resolutionOff = options.ignoreResolutions || false;
1035
+ extensionFiles = options.files || ["png", "jpeg", "jpg"];
1036
+ ignoreStripExtension = options.removeExtensions || false;
1037
+ ignoreFilePattern = options.ignoreDir || [];
982
1038
  } catch (error) {
983
1039
  console.log(`[smartui] Error: ${error.message}`);
984
1040
  process.exit();
@@ -1007,6 +1063,7 @@ var ctx_default = (options) => {
1007
1063
  enableJavaScript: config.enableJavaScript || false,
1008
1064
  allowedHostnames: config.allowedHostnames || []
1009
1065
  },
1066
+ uploadFilePath: "",
1010
1067
  webStaticConfig: [],
1011
1068
  git: {
1012
1069
  branch: "",
@@ -1026,16 +1083,20 @@ var ctx_default = (options) => {
1026
1083
  parallel: options.parallel ? true : false,
1027
1084
  markBaseline: options.markBaseline ? true : false,
1028
1085
  buildName: options.buildName || "",
1029
- port
1086
+ port,
1087
+ ignoreResolutions: resolutionOff,
1088
+ fileExtension: extensionFiles,
1089
+ stripExtension: ignoreStripExtension,
1090
+ ignorePattern: ignoreFilePattern
1030
1091
  },
1031
1092
  cliVersion: version,
1032
1093
  totalSnapshots: -1
1033
1094
  };
1034
1095
  };
1035
- function executeCommand(command4) {
1096
+ function executeCommand(command5) {
1036
1097
  let dst = process.cwd();
1037
1098
  try {
1038
- return child_process.execSync(command4, {
1099
+ return child_process.execSync(command5, {
1039
1100
  cwd: dst,
1040
1101
  stdio: ["ignore"],
1041
1102
  encoding: "utf-8"
@@ -1066,8 +1127,8 @@ var git_default = (ctx) => {
1066
1127
  } else {
1067
1128
  const splitCharacter = "<##>";
1068
1129
  const prettyFormat = ["%h", "%H", "%s", "%f", "%b", "%at", "%ct", "%an", "%ae", "%cn", "%ce", "%N", ""];
1069
- const command4 = 'git log -1 --pretty=format:"' + prettyFormat.join(splitCharacter) + '" && git rev-parse --abbrev-ref HEAD && git tag --contains HEAD';
1070
- let res = executeCommand(command4).split(splitCharacter);
1130
+ const command5 = 'git log -1 --pretty=format:"' + prettyFormat.join(splitCharacter) + '" && git rev-parse --abbrev-ref HEAD && git tag --contains HEAD';
1131
+ let res = executeCommand(command5).split(splitCharacter);
1071
1132
  var branchAndTags = res[res.length - 1].split("\n").filter((n) => n);
1072
1133
  var branch = ctx.env.CURRENT_BRANCH || branchAndTags[0];
1073
1134
  branchAndTags.slice(1);
@@ -1208,9 +1269,9 @@ var finalizeBuild_default = (ctx) => {
1208
1269
  return {
1209
1270
  title: `Finalizing build`,
1210
1271
  task: (ctx2, task) => __async(void 0, null, function* () {
1272
+ var _a, _b;
1211
1273
  updateLogContext({ task: "finalizeBuild" });
1212
1274
  try {
1213
- yield new Promise((resolve) => setTimeout(resolve, 2e3));
1214
1275
  yield ctx2.client.finalizeBuild(ctx2.build.id, ctx2.totalSnapshots, ctx2.log);
1215
1276
  task.output = chalk7__default.default.gray(`build url: ${ctx2.build.url}`);
1216
1277
  task.title = "Finalized build";
@@ -1219,6 +1280,17 @@ var finalizeBuild_default = (ctx) => {
1219
1280
  task.output = chalk7__default.default.gray(error.message);
1220
1281
  throw new Error("Finalize build failed");
1221
1282
  }
1283
+ try {
1284
+ yield (_a = ctx2.browser) == null ? void 0 : _a.close();
1285
+ ctx2.log.debug(`Closed browser`);
1286
+ yield (_b = ctx2.server) == null ? void 0 : _b.close();
1287
+ ctx2.log.debug(`Closed server`);
1288
+ let resp = yield ctx2.client.getS3PreSignedURL(ctx2);
1289
+ yield ctx2.client.uploadLogs(ctx2, resp.data.url);
1290
+ fs5.unlinkSync(constants_default.LOG_FILE_PATH);
1291
+ } catch (error) {
1292
+ ctx2.log.debug(error);
1293
+ }
1222
1294
  }),
1223
1295
  rendererOptions: { persistentOutput: true }
1224
1296
  };
@@ -1403,6 +1475,7 @@ var Queue = class {
1403
1475
  function processSnapshot(snapshot, ctx) {
1404
1476
  return __async(this, null, function* () {
1405
1477
  var _a;
1478
+ updateLogContext({ task: "discovery" });
1406
1479
  ctx.log.debug(`Processing snapshot ${snapshot.name}`);
1407
1480
  let launchOptions = { headless: true };
1408
1481
  let contextOptions = {
@@ -1412,8 +1485,8 @@ function processSnapshot(snapshot, ctx) {
1412
1485
  if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
1413
1486
  if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
1414
1487
  launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
1415
- ctx.browser = yield test.chromium.launch(launchOptions);
1416
- ctx.log.debug(`Chromium launched with options ${JSON.stringify(launchOptions)}`);
1488
+ ctx.browser = yield test.firefox.launch(launchOptions);
1489
+ ctx.log.debug(`Firefox launched with options ${JSON.stringify(launchOptions)}`);
1417
1490
  }
1418
1491
  const context = yield ctx.browser.newContext(contextOptions);
1419
1492
  ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
@@ -1606,10 +1679,9 @@ function processSnapshot(snapshot, ctx) {
1606
1679
 
1607
1680
  // src/commander/exec.ts
1608
1681
  var command = new commander.Command();
1609
- command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").option("-P, --port <number>", "Port number for the server").action(function(execCommand, _, command4) {
1682
+ command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").option("-P, --port <number>", "Port number for the server").action(function(execCommand, _, command5) {
1610
1683
  return __async(this, null, function* () {
1611
- var _a, _b;
1612
- let ctx = ctx_default(command4.optsWithGlobals());
1684
+ let ctx = ctx_default(command5.optsWithGlobals());
1613
1685
  if (!which__default.default.sync(execCommand[0], { nothrow: true })) {
1614
1686
  ctx.log.error(`Error: Command not found "${execCommand[0]}"`);
1615
1687
  return;
@@ -1642,11 +1714,6 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
1642
1714
  yield tasks.run(ctx);
1643
1715
  } catch (error) {
1644
1716
  ctx.log.info("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
1645
- } finally {
1646
- yield (_a = ctx.browser) == null ? void 0 : _a.close();
1647
- ctx.log.debug(`Closed browser`);
1648
- yield (_b = ctx.server) == null ? void 0 : _b.close();
1649
- ctx.log.debug(`Closed server`);
1650
1717
  }
1651
1718
  });
1652
1719
  });
@@ -1832,6 +1899,108 @@ function captureScreenshots(ctx) {
1832
1899
  return { capturedScreenshots, output };
1833
1900
  });
1834
1901
  }
1902
+ function getImageDimensions(filePath) {
1903
+ const buffer = fs5__default.default.readFileSync(filePath);
1904
+ let width, height;
1905
+ if (buffer.toString("hex", 0, 2) === "ffd8") {
1906
+ let offset = 2;
1907
+ while (offset < buffer.length) {
1908
+ const marker = buffer.toString("hex", offset, offset + 2);
1909
+ offset += 2;
1910
+ const length = buffer.readUInt16BE(offset);
1911
+ if (marker === "ffc0" || marker === "ffc2") {
1912
+ height = buffer.readUInt16BE(offset + 3);
1913
+ width = buffer.readUInt16BE(offset + 5);
1914
+ return { width, height };
1915
+ }
1916
+ offset += length;
1917
+ }
1918
+ } else if (buffer.toString("hex", 1, 4) === "504e47") {
1919
+ width = buffer.readUInt32BE(16);
1920
+ height = buffer.readUInt32BE(20);
1921
+ return { width, height };
1922
+ }
1923
+ return null;
1924
+ }
1925
+ function isAllowedImage(filePath) {
1926
+ return __async(this, null, function* () {
1927
+ try {
1928
+ const fileBuffer = fs5__default.default.readFileSync(filePath);
1929
+ const isMagicValid = constants_default.MAGIC_NUMBERS.some((magic) => fileBuffer.slice(0, magic.magic.length).equals(magic.magic));
1930
+ const metadata = yield sharp__default.default(filePath).metadata();
1931
+ if (metadata.format === constants_default.FILE_EXTENSION_GIFS) {
1932
+ return false;
1933
+ }
1934
+ if (metadata.width > 0 && metadata.height > 0) {
1935
+ return true;
1936
+ }
1937
+ if (isMagicValid && metadata.format !== constants_default.FILE_EXTENSION_GIFS) {
1938
+ return true;
1939
+ }
1940
+ return false;
1941
+ } catch (error) {
1942
+ return false;
1943
+ }
1944
+ });
1945
+ }
1946
+ function uploadScreenshots(ctx) {
1947
+ return __async(this, null, function* () {
1948
+ const allowedExtensions = ctx.options.fileExtension.map((ext) => `.${ext.trim().toLowerCase()}`);
1949
+ let noOfScreenshots = 0;
1950
+ function processDirectory(directory, relativePath = "") {
1951
+ return __async(this, null, function* () {
1952
+ const files = fs5__default.default.readdirSync(directory);
1953
+ for (let file of files) {
1954
+ const filePath = path2__default.default.join(directory, file);
1955
+ const stat = fs5__default.default.statSync(filePath);
1956
+ const relativeFilePath = path2__default.default.join(relativePath, file);
1957
+ if (stat.isDirectory() && ctx.options.ignorePattern.includes(relativeFilePath)) {
1958
+ ctx.log.info(`Ignoring Directory ${relativeFilePath}`);
1959
+ continue;
1960
+ }
1961
+ if (stat.isDirectory()) {
1962
+ yield processDirectory(filePath, relativeFilePath);
1963
+ } else {
1964
+ let fileExtension = path2__default.default.extname(file).toLowerCase();
1965
+ if (allowedExtensions.includes(fileExtension)) {
1966
+ const isValid = yield isAllowedImage(filePath);
1967
+ if (!isValid) {
1968
+ ctx.log.info(`File ${filePath} is not a valid ${fileExtension} image or is corrupted. Skipping.`);
1969
+ continue;
1970
+ }
1971
+ let ssId = relativeFilePath;
1972
+ if (ctx.options.stripExtension) {
1973
+ ssId = path2__default.default.join(relativePath, path2__default.default.basename(file, fileExtension));
1974
+ }
1975
+ let viewport = "default";
1976
+ if (!ctx.options.ignoreResolutions) {
1977
+ const dimensions = getImageDimensions(filePath);
1978
+ if (!dimensions) {
1979
+ ctx.log.info(`Unable to determine dimensions for image: ${filePath}`);
1980
+ } else {
1981
+ const width = dimensions.width;
1982
+ const height = dimensions.height;
1983
+ viewport = `${width}x${height}`;
1984
+ }
1985
+ }
1986
+ yield ctx.client.uploadScreenshot(ctx.build, filePath, ssId, "default", viewport, ctx.log);
1987
+ ctx.log.info(`${filePath} : uploaded successfully`);
1988
+ noOfScreenshots++;
1989
+ } else {
1990
+ ctx.log.info(`File ${filePath} has invalid file extension: ${fileExtension}. Skipping`);
1991
+ }
1992
+ }
1993
+ }
1994
+ });
1995
+ }
1996
+ yield processDirectory(ctx.uploadFilePath);
1997
+ if (noOfScreenshots == 0) {
1998
+ ctx.log.info(`No screenshots uploaded.`);
1999
+ } else {
2000
+ ctx.log.info(`${noOfScreenshots} screenshots uploaded successfully.`);
2001
+ }
2002
+ });
2003
+ }
1835
2004
  var captureScreenshots_default = (ctx) => {
1836
2005
  return {
1837
2006
  title: "Capturing screenshots",
@@ -1857,9 +2026,9 @@ var captureScreenshots_default = (ctx) => {
1857
2026
 
1858
2027
  // src/commander/capture.ts
1859
2028
  var command2 = new commander.Command();
1860
- command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("--parallel", "Capture parallely on all browsers").action(function(file, _, command4) {
2029
+ command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("--parallel", "Capture parallely on all browsers").action(function(file, _, command5) {
1861
2030
  return __async(this, null, function* () {
1862
- let ctx = ctx_default(command4.optsWithGlobals());
2031
+ let ctx = ctx_default(command5.optsWithGlobals());
1863
2032
  if (!fs5__default.default.existsSync(file)) {
1864
2033
  console.log(`Error: Web Static Config file ${file} not found.`);
1865
2034
  return;
@@ -1899,6 +2068,71 @@ command2.name("capture").description("Capture screenshots of static sites").argu
1899
2068
  });
1900
2069
  });
1901
2070
  var capture_default = command2;
2071
+ var uploadScreenshots_default = (ctx) => {
2072
+ return {
2073
+ title: "Uploading screenshots",
2074
+ task: (ctx2, task) => __async(void 0, null, function* () {
2075
+ try {
2076
+ ctx2.task = task;
2077
+ updateLogContext({ task: "upload" });
2078
+ yield uploadScreenshots(ctx2);
2079
+ task.title = "Screenshots uploaded successfully";
2080
+ } catch (error) {
2081
+ ctx2.log.debug(error);
2082
+ task.output = chalk7__default.default.gray(`${error.message}`);
2083
+ throw new Error("Uploading screenshots failed");
2084
+ }
2085
+ }),
2086
+ rendererOptions: { persistentOutput: true },
2087
+ exitOnError: false
2088
+ };
2089
+ };
2090
+
2091
+ // src/commander/upload.ts
2092
+ var command3 = new commander.Command();
2093
+ command3.name("upload").description("Upload screenshots from given directory").argument("<directory>", "Path of the directory").option("-R, --ignoreResolutions", "Ignore resolution").option("-F, --files <extensions>", "Comma-separated list of allowed file extensions", (val) => {
2094
+ return val.split(",").map((ext) => ext.trim().toLowerCase());
2095
+ }).option("-E, --removeExtensions", "Strips file extensions from snapshot names").option("-i, --ignoreDir <patterns>", "Comma-separated list of directories to ignore", (val) => {
2096
+ return val.split(",").map((pattern) => pattern.trim());
2097
+ }).action(function(directory, _, command5) {
2098
+ return __async(this, null, function* () {
2099
+ let ctx = ctx_default(command5.optsWithGlobals());
2100
+ if (!fs5__default.default.existsSync(directory)) {
2101
+ console.log(`Error: The provided directory ${directory} not found.`);
2102
+ return;
2103
+ }
2104
+ if (path2__default.default.extname(directory).toLowerCase() === constants_default.FILE_EXTENSION_ZIP) {
2105
+ ctx.log.debug(`Error: The provided directory ${directory} is a zip file. Zips are not accepted.`);
2106
+ return;
2107
+ }
2108
+ ctx.uploadFilePath = directory;
2109
+ let tasks = new listr2.Listr(
2110
+ [
2111
+ auth_default(),
2112
+ getGitInfo_default(),
2113
+ createBuild_default(),
2114
+ uploadScreenshots_default(),
2115
+ finalizeBuild_default()
2116
+ ],
2117
+ {
2118
+ rendererOptions: {
2119
+ icon: {
2120
+ [listr2.ListrDefaultRendererLogLevels.OUTPUT]: `\u2192`
2121
+ },
2122
+ color: {
2123
+ [listr2.ListrDefaultRendererLogLevels.OUTPUT]: listr2.color.gray
2124
+ }
2125
+ }
2126
+ }
2127
+ );
2128
+ try {
2129
+ yield tasks.run(ctx);
2130
+ } catch (error) {
2131
+ console.log("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
2132
+ }
2133
+ });
2134
+ });
2135
+ var upload_default = command3;
1902
2136
 
1903
2137
  // src/lib/uploadFigmaDesigns.ts
1904
2138
  var uploadFigmaDesigns_default = (ctx) => __async(void 0, null, function* () {
@@ -1950,11 +2184,11 @@ var uploadFigmaDesigns_default2 = (ctx) => {
1950
2184
  };
1951
2185
 
1952
2186
  // src/commander/uploadFigma.ts
1953
- var command3 = new commander.Command();
1954
- command3.name("upload-figma").description("Capture screenshots of static sites").argument("<file>", "figma design config file").option("--markBaseline", "Mark the uploaded images as baseline").option("--buildName <buildName>", "Name of the build").action(function(file, _, command4) {
2187
+ var command4 = new commander.Command();
2188
+ command4.name("upload-figma").description("Capture screenshots of static sites").argument("<file>", "figma design config file").option("--markBaseline", "Mark the uploaded images as baseline").option("--buildName <buildName>", "Name of the build").action(function(file, _, command5) {
1955
2189
  return __async(this, null, function* () {
1956
2190
  var _a, _b;
1957
- let ctx = ctx_default(command4.optsWithGlobals());
2191
+ let ctx = ctx_default(command5.optsWithGlobals());
1958
2192
  if (!fs5__default.default.existsSync(file)) {
1959
2193
  console.log(`Error: Figma Config file ${file} not found.`);
1960
2194
  return;
@@ -1992,31 +2226,34 @@ command3.name("upload-figma").description("Capture screenshots of static sites")
1992
2226
  }
1993
2227
  });
1994
2228
  });
1995
- var uploadFigma_default = command3;
2229
+ var uploadFigma_default = command4;
1996
2230
 
1997
2231
  // src/commander/commander.ts
1998
2232
  var program = new commander.Command();
1999
- program.name("smartui").description("CLI to help you run your SmartUI tests on LambdaTest platform").version(`v${version}`).option("-c --config <filepath>", "Config file path").addCommand(exec_default2).addCommand(capture_default).addCommand(configWeb).addCommand(configStatic).addCommand(configFigma).addCommand(uploadFigma_default);
2233
+ program.name("smartui").description("CLI to help you run your SmartUI tests on LambdaTest platform").version(`v${version}`).option("-c --config <filepath>", "Config file path").addCommand(exec_default2).addCommand(capture_default).addCommand(configWeb).addCommand(configStatic).addCommand(upload_default).addCommand(configFigma).addCommand(uploadFigma_default);
2000
2234
  var commander_default = program;
2001
2235
  (function() {
2002
2236
  return __async(this, null, function* () {
2003
2237
  let client = new httpClient(env_default());
2004
- let log = logger_default;
2238
+ let log2 = logger_default;
2005
2239
  try {
2006
- log.info(`
2240
+ let { data: { latestVersion, deprecated, additionalDescription, additionalDescriptionLatestVersion } } = yield client.checkUpdate(log2);
2241
+ log2.info(`
2007
2242
  LambdaTest SmartUI CLI v${package_default.version}`);
2008
- let { data: { latestVersion, deprecated } } = yield client.checkUpdate(log);
2009
- if (deprecated)
2010
- log.warn(`This version is deprecated. A new version ${latestVersion} is available!
2243
+ log2.info(chalk7__default.default.yellow(`${additionalDescription}`));
2244
+ if (deprecated) {
2245
+ log2.warn(`This version is deprecated. A new version ${latestVersion} is available!`);
2246
+ log2.warn(`${additionalDescriptionLatestVersion}
2011
2247
  `);
2012
- else if (package_default.version !== latestVersion)
2013
- log.info(chalk7__default.default.gray(`A new version ${latestVersion} is available!
2248
+ } else if (package_default.version !== latestVersion) {
2249
+ log2.info(chalk7__default.default.green(`A new version ${latestVersion} is available!`));
2250
+ log2.info(chalk7__default.default.red(`${additionalDescriptionLatestVersion}
2014
2251
  `));
2015
- else
2016
- log.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
2252
+ } else
2253
+ log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
2017
2254
  } catch (error) {
2018
- log.debug(error);
2019
- log.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
2255
+ log2.debug(error);
2256
+ log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
2020
2257
  }
2021
2258
  commander_default.parse();
2022
2259
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdatest/smartui-cli",
3
- "version": "3.0.12",
3
+ "version": "4.0.0",
4
4
  "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
5
5
  "files": [
6
6
  "dist/**/*"
@@ -33,6 +33,7 @@
33
33
  "fastify": "^4.24.3",
34
34
  "form-data": "^4.0.0",
35
35
  "listr2": "^7.0.1",
36
+ "sharp": "^0.33.4",
36
37
  "tsup": "^7.2.0",
37
38
  "which": "^4.0.0",
38
39
  "winston": "^3.10.0"