@lambdatest/smartui-cli 1.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.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@lambdatest/smartui-cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/Lambdatest/smartui-cli"
13
+ },
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "@lambdatest/smartui-logger": "1.0.0",
18
+ "@lambdatest/smartui-core": "1.0.0",
19
+ "commander": "^11.0.0",
20
+ "fs": "^0.0.1-security",
21
+ "path": "^0.12.7",
22
+ "url": "^0.11.1"
23
+ },
24
+ "devDependencies": {
25
+ "@lambdatest/smartui-logger": "file:../logger",
26
+ "@lambdatest/smartui-core": "file:../core"
27
+ }
28
+ }
package/src/capture.js ADDED
@@ -0,0 +1,15 @@
1
+ import SmartUI from "@lambdatest/smartui-core"
2
+ import { parse } from './validate.js';
3
+
4
+ async function capture(file, options, log) {
5
+ let screenshots = parse(file);
6
+ log.debug(screenshots);
7
+ options.screenshots = screenshots
8
+ const smartui = new SmartUI();
9
+ await smartui.createBuild(options, log);
10
+ await smartui.capture(options, log);
11
+ await smartui.cleanup(true, log);
12
+
13
+ }
14
+
15
+ export { capture };
package/src/config.js ADDED
@@ -0,0 +1,79 @@
1
+ import fs from 'fs';
2
+ import { ABNORMAL_EXIT } from './constant.js';
3
+ import path from 'path';
4
+
5
+ export const defaultScreenshotConfig = [
6
+ {
7
+ "name": "lambdatest-home-page",
8
+ "url": "https://www.lambdatest.com",
9
+ "waitForTimeout": 1000
10
+ },
11
+ {
12
+ "name": "example-page",
13
+ "url": "https://example.com/"
14
+ }
15
+ ]
16
+
17
+ export const defaultSmartUIWebConfig = {
18
+ web: {
19
+ browsers: [
20
+ 'chrome',
21
+ 'firefox',
22
+ 'safari',
23
+ 'edge'
24
+ ],
25
+ resolutions: [
26
+ [1920, 1080],
27
+ [1366, 768],
28
+ [360, 640],
29
+ ]
30
+ }
31
+ };
32
+
33
+ export function createWebConfig(filepath, log) {
34
+ // default filepath
35
+ filepath = filepath || 'smartui-web.json';
36
+ let filetype = path.extname(filepath);
37
+ if (filetype != '.json') {
38
+ log.error(`Error: Web Config file must have .json extension`);
39
+ process.exitCode = ABNORMAL_EXIT;
40
+ return
41
+ }
42
+
43
+ // verify the file does not already exist
44
+ if (fs.existsSync(filepath)) {
45
+ log.error(`Error: SmartUI Web Config already exists: ${filepath}`);
46
+ log.error(`To create a new file, please specify the file name like: 'smartui config:create-web webConfig.json'`);
47
+ process.exitCode = ABNORMAL_EXIT;
48
+ return
49
+ }
50
+
51
+ // write stringified default config options to the filepath
52
+ fs.mkdirSync(path.dirname(filepath), { recursive: true });
53
+ fs.writeFileSync(filepath, JSON.stringify(defaultSmartUIWebConfig, null, 2) + '\n');
54
+ log.info(`Created SmartUI Web Config: ${filepath}`);
55
+ };
56
+
57
+ export function createWebStaticConfig(filepath, log) {
58
+ // default filepath
59
+ filepath = filepath || 'url.json';
60
+ let filetype = path.extname(filepath);
61
+ if (filetype != '.json') {
62
+ log.error(`Error: Config file must have .json extension`);
63
+ process.exitCode = ABNORMAL_EXIT;
64
+ return
65
+ }
66
+
67
+ // verify the file does not already exist
68
+ if (fs.existsSync(filepath)) {
69
+ log.error(`Error: web-static config already exists: ${filepath}`);
70
+ log.error(`To create a new file, please specify the file name like: 'smartui config:create-web links.json'`);
71
+ process.exitCode = ABNORMAL_EXIT;
72
+ return
73
+ }
74
+
75
+ // write stringified default config options to the filepath
76
+ fs.mkdirSync(path.dirname(filepath), { recursive: true });
77
+ fs.writeFileSync(filepath, JSON.stringify(defaultScreenshotConfig, null, 2) + '\n');
78
+ log.info(`Created web-static config: ${filepath}`);
79
+ };
@@ -0,0 +1,9 @@
1
+
2
+ // Error codes
3
+ export const ABNORMAL_EXIT = 1;
4
+ export const MAX_RESOLUTIONS = 5
5
+ export const MIN_RESOLUTION_WIDTH = 320
6
+ export const MIN_RESOLUTION_HEIGHT = 320
7
+ export const MAX_RESOLUTION_WIDTH = 7680
8
+ export const MAX_RESOLUTION_HEIGHT = 7680
9
+ export const VALID_BROWSERS = ['chrome', 'safari', 'firefox', 'edge'];
package/src/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import { Command, Option } from 'commander';
2
+ import { capture } from './capture.js';
3
+ import { logger } from '@lambdatest/smartui-logger';
4
+ import { validateScreenshotConfig } from './validate.js';
5
+ import packageJson from '../package.json' assert { type: 'json' };
6
+ import { createWebConfig, createWebStaticConfig } from './config.js';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('smartui')
12
+ .description('CLI to help you to take screenshot SmartUI on LambdaTest platform')
13
+ .addOption(new Option('--env <prod|stage>', 'Runtime environment option').choices(['prod', 'stage']));
14
+
15
+ program
16
+ .command('capture <file>')
17
+ .description('capture')
18
+ .option('-c --config <file>', 'Config file path')
19
+ .action(async function (file, options) {
20
+ const log = await logger();
21
+ options.env = program.opts().env || 'prod';
22
+ log.info('SmartUI Capture CLI v' + packageJson.version);
23
+ options.config = validateScreenshotConfig(file, options, log);
24
+ log.info('config validation done');
25
+ log.debug(options);
26
+ capture(file, options, log);
27
+ });
28
+
29
+ program.command('config:create-web')
30
+ .description('Create SmartUI Web config file')
31
+ .argument('[filepath]', 'Optional config filepath')
32
+ .action(async function (filepath, options) {
33
+ const log = await logger();
34
+ log.info('SmartUI Config CLI v' + packageJson.version);
35
+ log.info('\n');
36
+ createWebConfig(filepath, log);
37
+ });
38
+
39
+ program.command('config:web-static')
40
+ .description('Create Web Static config file')
41
+ .argument('[filepath]', 'Optional config filepath')
42
+ .action(async function (filepath, options) {
43
+ const log = await logger();
44
+ log.info('SmartUI Config CLI v' + packageJson.version);
45
+ log.info('\n');
46
+ createWebStaticConfig(filepath, log);
47
+ });
48
+
49
+ program.parse();
@@ -0,0 +1,134 @@
1
+ import fs from 'fs';
2
+ import { ABNORMAL_EXIT, VALID_BROWSERS, MAX_RESOLUTIONS, MAX_RESOLUTION_WIDTH, MAX_RESOLUTION_HEIGHT, MIN_RESOLUTION_WIDTH, MIN_RESOLUTION_HEIGHT } from './constant.js';
3
+ import { URL } from 'url';
4
+ import { defaultSmartUIWebConfig } from './config.js'
5
+
6
+
7
+ // Parse the JSON data
8
+ export function parse(file) {
9
+ const data = fs.readFileSync(file, 'utf-8');
10
+ return JSON.parse(data);
11
+ }
12
+
13
+ export function validateScreenshotConfig(configFile, options, log) {
14
+ log.info(`file : ${configFile}`)
15
+
16
+ // Check if file exists
17
+ if (Object.keys(configFile).length) {
18
+ try {
19
+ fs.promises.access(configFile);
20
+ } catch (error) {
21
+ log.error(`Error: Either File does not exist ${configFile} or doesn't have read permissions. Trace : ${error}`);
22
+ log.debug(error)
23
+ process.exit(ABNORMAL_EXIT);
24
+ }
25
+ }
26
+
27
+
28
+ let screenshots = {};
29
+ // Check JSON Parse Error
30
+ if (Object.keys(configFile).length) {
31
+ try {
32
+ screenshots = parse(configFile)
33
+ } catch (error) {
34
+ log.error('Error: Invalid file, capture command only supports json file');
35
+ process.exit(ABNORMAL_EXIT);
36
+ }
37
+ }
38
+
39
+ log.debug(screenshots)
40
+
41
+ //Check for URLs should not be empty
42
+ for (const screenshot of screenshots) {
43
+ if (!screenshot.name || screenshot.name == '') {
44
+ log.error(`Error: Missing screenshot name in ${configFile}`);
45
+ process.exit(ABNORMAL_EXIT);
46
+ }
47
+ if (!screenshot.url || screenshot.url == '') {
48
+ log.error('Error: Missing required URL for screenshot : '+screenshot.name);
49
+ process.exit(ABNORMAL_EXIT);
50
+ }
51
+ //Check for URLs should valid (like abcd in URL)
52
+ try {
53
+ new URL(screenshot.url);
54
+ } catch (error) {
55
+ log.error('Error: Invalid screenshot URL: ' + screenshot.url);
56
+ process.exit(ABNORMAL_EXIT);
57
+ }
58
+ }
59
+
60
+ log.debug(options)
61
+
62
+ //Check for smartui-web.json
63
+ let webConfigFile = options.config
64
+ // Verify config file exists
65
+ if (!fs.existsSync(webConfigFile)) {
66
+ log.error(`Error: Config file not found, will use default configs`);
67
+ return defaultSmartUIWebConfig
68
+ }
69
+
70
+ // Parse JSON
71
+ let webConfig;
72
+ try {
73
+ webConfig = JSON.parse(fs.readFileSync(webConfigFile));
74
+ } catch (error) {
75
+ log.error('Error: ', error.message);
76
+ process.exit(ABNORMAL_EXIT);
77
+ }
78
+ if (webConfig && webConfig.web) {
79
+ try {
80
+ validateConfigBrowsers(webConfig.web.browsers);
81
+ webConfig.web.resolutions = validateConfigResolutions(webConfig.web.resolutions);
82
+ } catch (error) {
83
+ log.error(`Error: Invalid webConfig, ${error.message}`);
84
+ process.exit(ABNORMAL_EXIT);
85
+ }
86
+ return webConfig
87
+ } else {
88
+ log.error(`No webConfig found in ${webConfigFile} file, will use default configs`);
89
+ return defaultSmartUIWebConfig
90
+ }
91
+ }
92
+
93
+
94
+ function validateConfigBrowsers(browsers) {
95
+ if (browsers.length == 0) {
96
+ throw new ValidationError('empty browsers list.');
97
+ }
98
+ const set = new Set();
99
+ for (let element of browsers) {
100
+ if (!VALID_BROWSERS.includes(element.toLowerCase()) || set.has(element)) {
101
+ throw new ValidationError(`invalid or duplicate value for browser. Accepted browsers are ${VALID_BROWSERS.join(',')}`);
102
+ }
103
+ set.add(element);
104
+ };
105
+ }
106
+
107
+ function validateConfigResolutions(resolutions) {
108
+ if (!Array.isArray(resolutions)) {
109
+ throw new ValidationError('invalid resolutions.');
110
+ }
111
+ if (resolutions.length == 0) {
112
+ throw new ValidationError('empty resolutions list in config.');
113
+ }
114
+ if (resolutions.length > 5) {
115
+ throw new ValidationError(`max resolutions: ${MAX_RESOLUTIONS}`);
116
+ }
117
+ let res = [];
118
+ resolutions.forEach(element => {
119
+ if (!Array.isArray(element) || element.length == 0 || element.length > 2) {
120
+ throw new ValidationError('invalid resolutions.');
121
+ }
122
+ let width = element[0];
123
+ let height = element[1];
124
+ if (typeof width != 'number' || width < MIN_RESOLUTION_WIDTH || width > MAX_RESOLUTION_WIDTH) {
125
+ throw new ValidationError(`width must be > ${MIN_RESOLUTION_WIDTH}, < ${MAX_RESOLUTION_WIDTH}`);
126
+ }
127
+ if (height && (typeof height != 'number' || height < MIN_RESOLUTION_WIDTH || height > MAX_RESOLUTION_WIDTH)) {
128
+ throw new ValidationError(`height must be > ${MIN_RESOLUTION_HEIGHT}, < ${MAX_RESOLUTION_HEIGHT}`);
129
+ }
130
+ res.push([width, height || 0]);
131
+ });
132
+
133
+ return res
134
+ }