cypress-validate 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.
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const chalk = require('chalk');
5
+ const { logger } = require('../utils/logger');
6
+ const { findProjectRoot } = require('../utils/config-finder');
7
+
8
+ module.exports = async function infoCommand() {
9
+ logger.title('Cypress Validate — Info');
10
+ logger.divider();
11
+
12
+ const projectRoot = findProjectRoot();
13
+
14
+ // Node & OS info
15
+ logger.step(`Node.js: ${chalk.cyan(process.version)}`);
16
+ logger.step(`Platform: ${chalk.cyan(process.platform)} (${process.arch})`);
17
+ logger.step(`CWD: ${chalk.cyan(projectRoot)}`);
18
+ logger.blank();
19
+
20
+ // Cypress version
21
+ logger.step('Fetching Cypress info...');
22
+ logger.blank();
23
+
24
+ try {
25
+ const result = await execa('npx', ['cypress', 'info'], {
26
+ cwd: projectRoot,
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ reject: false,
29
+ });
30
+
31
+ if (result.exitCode === 0) {
32
+ const lines = result.stdout.split('\n');
33
+ lines.forEach((line) => {
34
+ if (line.trim()) {
35
+ if (line.includes('Cypress:')) {
36
+ console.log(' ' + chalk.bold.cyan(line));
37
+ } else if (line.includes('✔') || line.includes('✓')) {
38
+ console.log(' ' + chalk.green(line));
39
+ } else if (line.includes('✖') || line.includes('✗')) {
40
+ console.log(' ' + chalk.red(line));
41
+ } else {
42
+ console.log(' ' + chalk.gray(line));
43
+ }
44
+ }
45
+ });
46
+ } else {
47
+ logger.warn('Could not retrieve full Cypress info. Is Cypress installed?');
48
+ logger.info('Install with: ' + chalk.cyan('npx cypress-validate install'));
49
+ }
50
+ } catch (err) {
51
+ logger.error('Failed to run cypress info: ' + err.message);
52
+ }
53
+
54
+ logger.blank();
55
+ logger.divider();
56
+
57
+ // package.json cypress version
58
+ try {
59
+ const pkg = require(`${projectRoot}/package.json`);
60
+ const cyVersion =
61
+ (pkg.devDependencies && pkg.devDependencies.cypress) ||
62
+ (pkg.dependencies && pkg.dependencies.cypress) ||
63
+ 'not found in package.json';
64
+ logger.info(`Cypress in package.json: ${chalk.cyan(cyVersion)}`);
65
+ } catch (_) { }
66
+
67
+ logger.blank();
68
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const { logger, createSpinner } = require('../utils/logger');
5
+ const { findProjectRoot } = require('../utils/config-finder');
6
+ const chalk = require('chalk');
7
+
8
+ module.exports = async function installCommand(opts) {
9
+ logger.title('Cypress Validate — Install');
10
+ logger.divider();
11
+
12
+ const projectRoot = findProjectRoot();
13
+
14
+ // Build the npm install command
15
+ const versionSuffix = opts.version ? `@${opts.version}` : '';
16
+ const pkg = `cypress${versionSuffix}`;
17
+ const npmArgs = opts.global
18
+ ? ['install', '-g', pkg]
19
+ : ['install', '--save-dev', pkg];
20
+
21
+ if (!opts.force) {
22
+ // Check if cypress is already installed
23
+ try {
24
+ const { stdout } = await execa('node', ['-e', "require('cypress')"], {
25
+ cwd: projectRoot,
26
+ reject: false,
27
+ });
28
+ const spinner2 = createSpinner('Checking existing Cypress installation...');
29
+ spinner2.start();
30
+ const result = await execa('npx', ['cypress', 'version'], {
31
+ cwd: projectRoot,
32
+ reject: false,
33
+ });
34
+ spinner2.stop();
35
+ if (result.exitCode === 0) {
36
+ logger.success('Cypress is already installed: ' + chalk.cyan(result.stdout.trim()));
37
+ logger.info('Use ' + chalk.cyan('--force') + ' to reinstall, or ' + chalk.cyan('cypress-validate verify') + ' to verify the installation.');
38
+ return;
39
+ }
40
+ } catch (_) {
41
+ // not installed, continue
42
+ }
43
+ }
44
+
45
+ const spinner = createSpinner(`Installing ${chalk.cyan(pkg)}...`);
46
+ spinner.start();
47
+
48
+ try {
49
+ await execa('npm', npmArgs, {
50
+ cwd: projectRoot,
51
+ stdio: ['ignore', 'pipe', 'pipe'],
52
+ });
53
+ spinner.succeed(chalk.green(`Successfully installed ${pkg}`));
54
+ } catch (err) {
55
+ spinner.fail(chalk.red('Installation failed'));
56
+ logger.error(err.message);
57
+ process.exit(1);
58
+ }
59
+
60
+ // Run verify after install
61
+ logger.blank();
62
+ logger.step('Running ' + chalk.cyan('cypress verify') + ' ...');
63
+ const verifySpinner = createSpinner('Verifying Cypress...');
64
+ verifySpinner.start();
65
+ try {
66
+ const verifyResult = await execa('npx', ['cypress', 'verify'], {
67
+ cwd: projectRoot,
68
+ stdio: ['ignore', 'pipe', 'pipe'],
69
+ });
70
+ verifySpinner.succeed(chalk.green('Cypress verified successfully!'));
71
+ logger.blank();
72
+ logger.info(verifyResult.stdout);
73
+ } catch (err) {
74
+ verifySpinner.fail('Cypress verification failed');
75
+ logger.error(err.stderr || err.message);
76
+ process.exit(1);
77
+ }
78
+ };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const path = require('path');
5
+ const { logger } = require('../utils/logger');
6
+ const { findProjectRoot } = require('../utils/config-finder');
7
+ const chalk = require('chalk');
8
+
9
+ module.exports = async function openCommand(opts) {
10
+ const projectRoot = findProjectRoot();
11
+
12
+ logger.title('Cypress Validate — Open (Interactive Test Runner)');
13
+ logger.divider();
14
+ logger.step(`Mode: ${chalk.cyan('Interactive / GUI')}`);
15
+ if (opts.browser) logger.step(`Browser: ${chalk.cyan(opts.browser)}`);
16
+ if (opts.spec) logger.step(`Spec: ${chalk.cyan(opts.spec)}`);
17
+ logger.divider();
18
+ logger.blank();
19
+
20
+ const args = ['open'];
21
+ if (opts.browser) args.push('--browser', opts.browser);
22
+ if (opts.spec) args.push('--spec', opts.spec);
23
+ if (opts.config) args.push('--config', opts.config);
24
+ if (opts.env) args.push('--env', opts.env);
25
+ if (opts.port) args.push('--port', opts.port);
26
+
27
+ const cypressBin = path.join(projectRoot, 'node_modules', '.bin', 'cypress');
28
+
29
+ try {
30
+ await execa(cypressBin, args, {
31
+ cwd: projectRoot,
32
+ stdio: 'inherit',
33
+ });
34
+ } catch (err) {
35
+ if (err.exitCode !== undefined) {
36
+ logger.error(`Cypress exited with code ${err.exitCode}`);
37
+ } else {
38
+ logger.error('Failed to open Cypress: ' + err.message);
39
+ logger.warn('Make sure Cypress is installed: ' + chalk.cyan('npx cypress-validate install'));
40
+ }
41
+ process.exit(err.exitCode || 1);
42
+ }
43
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+ const { logger } = require('../utils/logger');
7
+ const { findProjectRoot } = require('../utils/config-finder');
8
+
9
+ module.exports = async function recordCommand(opts) {
10
+ logger.title('Cypress Validate — Record');
11
+ logger.divider();
12
+
13
+ const projectRoot = findProjectRoot();
14
+
15
+ const recordKey = opts.key || process.env.CYPRESS_RECORD_KEY;
16
+ if (!recordKey) {
17
+ logger.error('No Cypress Cloud record key found!');
18
+ logger.info('Provide it via:');
19
+ logger.step(chalk.cyan('--key <CYPRESS_RECORD_KEY>'));
20
+ logger.step('or set env var: ' + chalk.cyan('CYPRESS_RECORD_KEY=<key>'));
21
+ logger.blank();
22
+ logger.info('Sign up at: ' + chalk.cyan('https://cloud.cypress.io'));
23
+ process.exit(1);
24
+ }
25
+
26
+ const args = ['run', '--record', '--key', recordKey];
27
+
28
+ if (opts.spec) args.push('--spec', opts.spec);
29
+ if (opts.browser) args.push('--browser', opts.browser);
30
+ if (opts.tag) args.push('--tag', opts.tag);
31
+ if (opts.group) args.push('--group', opts.group);
32
+ if (opts.parallel) args.push('--parallel');
33
+ if (opts.ciBuildId) args.push('--ci-build-id', opts.ciBuildId);
34
+
35
+ logger.step(`Browser: ${chalk.cyan(opts.browser || 'electron')}`);
36
+ if (opts.spec) logger.step(`Spec: ${chalk.cyan(opts.spec)}`);
37
+ if (opts.group) logger.step(`Group: ${chalk.cyan(opts.group)}`);
38
+ if (opts.parallel) logger.step(`Parallel: ${chalk.cyan('enabled')}`);
39
+ logger.step(`Key: ${chalk.cyan('*'.repeat(Math.min(8, recordKey.length)) + '...')}`);
40
+ logger.divider();
41
+ logger.blank();
42
+
43
+ const cypressBin = path.join(projectRoot, 'node_modules', '.bin', 'cypress');
44
+
45
+ try {
46
+ await execa(cypressBin, args, {
47
+ cwd: projectRoot,
48
+ stdio: 'inherit',
49
+ env: {
50
+ ...process.env,
51
+ CYPRESS_RECORD_KEY: recordKey,
52
+ },
53
+ });
54
+ logger.blank();
55
+ logger.success('Run recorded successfully to Cypress Cloud!');
56
+ } catch (err) {
57
+ logger.blank();
58
+ logger.error(`Recording exited with code ${err.exitCode || 1}`);
59
+ logger.info('View your run at: ' + chalk.cyan('https://cloud.cypress.io'));
60
+ process.exit(err.exitCode || 1);
61
+ }
62
+ };
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const chalk = require('chalk');
5
+ const fs = require('fs-extra');
6
+ const path = require('path');
7
+ const { logger } = require('../utils/logger');
8
+ const { findProjectRoot, lastRunResultsPath } = require('../utils/config-finder');
9
+
10
+ /**
11
+ * Build the cypress run argument list from CLI options
12
+ */
13
+ function buildArgs(opts) {
14
+ const args = ['run'];
15
+
16
+ if (opts.spec) args.push('--spec', opts.spec);
17
+ if (opts.browser) args.push('--browser', opts.browser);
18
+ if (opts.headed) args.push('--headed');
19
+ if (opts.grep) args.push('--env', `grep=${opts.grep}`);
20
+ if (opts.config) args.push('--config', opts.config);
21
+ if (opts.env) args.push('--env', opts.env);
22
+ if (opts.port) args.push('--port', opts.port);
23
+ if (opts.record) args.push('--record');
24
+ if (opts.key) args.push('--key', opts.key);
25
+ if (opts.tag) args.push('--tag', opts.tag);
26
+ if (opts.forbidOnly) args.push('--config', 'forbidOnly=true');
27
+
28
+ // Reporter
29
+ if (opts.reporter && opts.reporter !== 'spec') {
30
+ args.push('--reporter', opts.reporter);
31
+ if (opts.reporter === 'mochawesome') {
32
+ args.push('--reporter-options',
33
+ 'reportDir=cypress/reports,overwrite=false,html=true,json=true');
34
+ }
35
+ }
36
+
37
+ return args;
38
+ }
39
+
40
+ /**
41
+ * Load specs that failed in the last run
42
+ */
43
+ async function getLastFailedSpecs(projectRoot) {
44
+ const resultsFile = lastRunResultsPath(projectRoot);
45
+ if (!await fs.pathExists(resultsFile)) {
46
+ logger.warn('No previous run results found. Running all tests.');
47
+ return null;
48
+ }
49
+ const data = await fs.readJson(resultsFile);
50
+ if (!data.failedSpecs || data.failedSpecs.length === 0) {
51
+ logger.success('No failed specs from last run. Nothing to re-run!');
52
+ process.exit(0);
53
+ }
54
+ logger.info(`Re-running ${data.failedSpecs.length} failed spec(s):`);
55
+ data.failedSpecs.forEach((s) => logger.step(s));
56
+ return data.failedSpecs.join(',');
57
+ }
58
+
59
+ /**
60
+ * Save results summary for --last-failed support
61
+ */
62
+ async function saveRunResults(projectRoot, exitCode, specPattern) {
63
+ const resultsDir = path.join(projectRoot, '.cypress-validate');
64
+ await fs.ensureDir(resultsDir);
65
+ const resultsFile = lastRunResultsPath(projectRoot);
66
+
67
+ // Try to parse mochawesome/junit JSON to extract failed specs
68
+ const failedSpecs = exitCode !== 0 && specPattern ? [specPattern] : [];
69
+
70
+ await fs.writeJson(resultsFile, {
71
+ timestamp: new Date().toISOString(),
72
+ exitCode,
73
+ failedSpecs,
74
+ }, { spaces: 2 });
75
+ }
76
+
77
+ module.exports = async function runCommand(opts) {
78
+ const projectRoot = findProjectRoot();
79
+
80
+ logger.title('Cypress Validate — Run');
81
+ logger.divider();
82
+
83
+ // Resolve --last-failed
84
+ if (opts.lastFailed) {
85
+ const failed = await getLastFailedSpecs(projectRoot);
86
+ if (failed) opts.spec = failed;
87
+ }
88
+
89
+ // Build args
90
+ const args = buildArgs(opts);
91
+
92
+ // Handle retry
93
+ const retryCount = parseInt(opts.retry || '0', 10);
94
+ if (retryCount > 0) {
95
+ args.push('--config', `retries=${retryCount}`);
96
+ }
97
+
98
+ // Show summary before running
99
+ logger.step(`Browser: ${chalk.cyan(opts.browser || 'electron')}`);
100
+ if (opts.spec) logger.step(`Spec: ${chalk.cyan(opts.spec)}`);
101
+ if (opts.grep) logger.step(`Grep: ${chalk.cyan(opts.grep)}`);
102
+ if (opts.headed) logger.step(`Mode: ${chalk.cyan('headed')}`);
103
+ else logger.step(`Mode: ${chalk.cyan('headless (default)')}`);
104
+ if (retryCount) logger.step(`Retries: ${chalk.cyan(retryCount)}`);
105
+ logger.divider();
106
+ logger.blank();
107
+
108
+ // Resolve cypress binary
109
+ const cypressBin = path.join(projectRoot, 'node_modules', '.bin', 'cypress');
110
+
111
+ try {
112
+ const proc = execa(cypressBin, args, {
113
+ cwd: projectRoot,
114
+ stdio: 'inherit',
115
+ env: {
116
+ ...process.env,
117
+ CYPRESS_RECORD_KEY: opts.key || process.env.CYPRESS_RECORD_KEY,
118
+ },
119
+ });
120
+
121
+ const result = await proc;
122
+ await saveRunResults(projectRoot, result.exitCode, opts.spec);
123
+ logger.blank();
124
+ logger.success(chalk.green('All tests passed!'));
125
+ process.exit(0);
126
+ } catch (err) {
127
+ await saveRunResults(projectRoot, err.exitCode || 1, opts.spec);
128
+ logger.blank();
129
+ if (err.exitCode !== undefined) {
130
+ logger.error(`Tests completed with exit code ${err.exitCode}`);
131
+ logger.info('Run ' + chalk.cyan('cypress-validate show-report') + ' to view the HTML report.');
132
+ } else {
133
+ logger.error('Failed to run Cypress: ' + err.message);
134
+ logger.warn('Make sure Cypress is installed: ' + chalk.cyan('npx cypress-validate install'));
135
+ }
136
+ process.exit(err.exitCode || 1);
137
+ }
138
+ };
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const chalk = require('chalk');
8
+ const { logger, createSpinner } = require('../utils/logger');
9
+ const { findProjectRoot } = require('../utils/config-finder');
10
+
11
+ module.exports = async function screenshotCommand(opts) {
12
+ logger.title('Cypress Validate — Screenshot');
13
+ logger.divider();
14
+ logger.step(`URL: ${chalk.cyan(opts.url)}`);
15
+ logger.step(`Output: ${chalk.cyan(opts.output)}`);
16
+ logger.step(`Browser: ${chalk.cyan(opts.browser)}`);
17
+ if (opts.fullPage) logger.step('Full-page: enabled');
18
+ logger.divider();
19
+ logger.blank();
20
+
21
+ const projectRoot = findProjectRoot();
22
+
23
+ // Parse viewport
24
+ let viewportWidth = 1280;
25
+ let viewportHeight = 720;
26
+ if (opts.viewport) {
27
+ const parts = opts.viewport.split('x');
28
+ viewportWidth = parseInt(parts[0], 10) || 1280;
29
+ viewportHeight = parseInt(parts[1], 10) || 720;
30
+ }
31
+
32
+ // Build a temporary spec file
33
+ const tmpDir = path.join(os.tmpdir(), 'cypress-validate-screenshot');
34
+ await fs.ensureDir(tmpDir);
35
+ const specsDir = path.join(tmpDir, 'cypress', 'e2e');
36
+ await fs.ensureDir(specsDir);
37
+
38
+ const outputAbs = path.resolve(process.cwd(), opts.output);
39
+ await fs.ensureDir(path.dirname(outputAbs));
40
+
41
+ const tempSpec = path.join(specsDir, '__screenshot__.cy.js');
42
+ const specContent = `
43
+ /// <reference types="cypress" />
44
+ describe('cypress-validate screenshot', () => {
45
+ it('captures a screenshot of ${opts.url}', () => {
46
+ cy.viewport(${viewportWidth}, ${viewportHeight});
47
+ cy.visit(${JSON.stringify(opts.url)});
48
+ cy.screenshot('screenshot', {
49
+ capture: ${opts.fullPage ? "'fullPage'" : "'viewport'"},
50
+ });
51
+ });
52
+ });
53
+ `;
54
+ await fs.writeFile(tempSpec, specContent, 'utf-8');
55
+
56
+ // Write a temporary cypress config
57
+ const tmpConfigPath = path.join(tmpDir, 'cypress.config.js');
58
+ const screenshotDir = path.dirname(outputAbs);
59
+ await fs.writeFile(tmpConfigPath, `
60
+ const { defineConfig } = require('cypress');
61
+ module.exports = defineConfig({
62
+ e2e: {
63
+ supportFile: false,
64
+ screenshotsFolder: ${JSON.stringify(screenshotDir)},
65
+ video: false,
66
+ },
67
+ });
68
+ `, 'utf-8');
69
+
70
+ const spinner = createSpinner('Taking screenshot...');
71
+ spinner.start();
72
+
73
+ const cypressBin = path.join(projectRoot, 'node_modules', '.bin', 'cypress');
74
+
75
+ try {
76
+ await execa(cypressBin, [
77
+ 'run',
78
+ '--spec', tempSpec,
79
+ '--config-file', tmpConfigPath,
80
+ '--browser', opts.browser,
81
+ '--headless',
82
+ ], {
83
+ cwd: tmpDir,
84
+ stdio: ['ignore', 'pipe', 'pipe'],
85
+ });
86
+
87
+ // Move screenshot to intended output location
88
+ const generatedScreenshot = path.join(screenshotDir, 'cypress-validate screenshot', 'screenshot.png');
89
+ if (await fs.pathExists(generatedScreenshot)) {
90
+ await fs.move(generatedScreenshot, outputAbs, { overwrite: true });
91
+ }
92
+
93
+ spinner.succeed(chalk.green('Screenshot saved!'));
94
+ logger.blank();
95
+ logger.info(`File: ${chalk.cyan(outputAbs)}`);
96
+ } catch (err) {
97
+ spinner.fail(chalk.red('Screenshot failed'));
98
+ logger.error(err.message);
99
+ logger.warn('Ensure Cypress is installed: ' + chalk.cyan('npx cypress-validate install'));
100
+ process.exit(1);
101
+ } finally {
102
+ await fs.remove(tmpDir).catch(() => { });
103
+ }
104
+
105
+ logger.blank();
106
+ };
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+ const open = require('open');
7
+ const { logger, createSpinner } = require('../utils/logger');
8
+ const { findProjectRoot } = require('../utils/config-finder');
9
+
10
+ /**
11
+ * Recursively find HTML report files inside a directory
12
+ */
13
+ async function findHtmlReports(dir) {
14
+ const reports = [];
15
+ if (!await fs.pathExists(dir)) return reports;
16
+ const stat = await fs.stat(dir);
17
+ if (stat.isFile() && dir.endsWith('.html')) return [dir];
18
+
19
+ const walk = async (d) => {
20
+ const entries = await fs.readdir(d, { withFileTypes: true });
21
+ for (const entry of entries) {
22
+ const full = path.join(d, entry.name);
23
+ if (entry.isDirectory()) await walk(full);
24
+ else if (entry.isFile() && entry.name.endsWith('.html')) reports.push(full);
25
+ }
26
+ };
27
+ await walk(dir);
28
+ return reports;
29
+ }
30
+
31
+ module.exports = async function showReportCommand(opts) {
32
+ logger.title('Cypress Validate — Show Report');
33
+ logger.divider();
34
+
35
+ const projectRoot = findProjectRoot();
36
+ const reportRoot = path.resolve(process.cwd(), opts.path || 'cypress/reports');
37
+
38
+ // If --serve, start a local HTTP server
39
+ if (opts.serve) {
40
+ const port = opts.port || '9323';
41
+ logger.step(`Serving report from: ${chalk.cyan(reportRoot)}`);
42
+ logger.step(`Port: ${chalk.cyan(port)}`);
43
+ logger.blank();
44
+ try {
45
+ const { execa } = require('execa');
46
+ logger.info(`Open: ${chalk.cyan(`http://localhost:${port}`)}`);
47
+ logger.blank();
48
+ await execa('npx', ['serve', '-p', port, reportRoot], {
49
+ cwd: projectRoot,
50
+ stdio: 'inherit',
51
+ });
52
+ } catch (err) {
53
+ if (err.exitCode !== undefined && err.exitCode !== 1) {
54
+ logger.error(`Server exited: ${err.message}`);
55
+ process.exit(1);
56
+ }
57
+ }
58
+ return;
59
+ }
60
+
61
+ // Otherwise, locate the HTML report and open it
62
+ const spinner = createSpinner('Looking for HTML report...');
63
+ spinner.start();
64
+
65
+ const reports = await findHtmlReports(reportRoot);
66
+
67
+ if (reports.length === 0) {
68
+ spinner.fail(chalk.red('No HTML report found'));
69
+ logger.blank();
70
+ logger.warn(`Searched in: ${chalk.cyan(reportRoot)}`);
71
+ logger.info('Generate a report by running:');
72
+ logger.step(chalk.cyan('npx cypress-validate run --reporter mochawesome'));
73
+ process.exit(1);
74
+ }
75
+
76
+ // Sort by modification time — open the most recent
77
+ const sorted = await Promise.all(
78
+ reports.map(async (f) => ({ file: f, mtime: (await fs.stat(f)).mtimeMs }))
79
+ );
80
+ sorted.sort((a, b) => b.mtime - a.mtime);
81
+ const reportFile = sorted[0].file;
82
+
83
+ spinner.succeed(chalk.green(`Found: ${path.relative(process.cwd(), reportFile)}`));
84
+ logger.blank();
85
+ logger.step(`Opening: ${chalk.cyan(reportFile)}`);
86
+ logger.blank();
87
+
88
+ if (reports.length > 1) {
89
+ logger.info(`${chalk.gray(`(${reports.length} reports found — opening the most recent)`)}`);
90
+ logger.blank();
91
+ }
92
+
93
+ await open(reportFile);
94
+ logger.success('Report opened in your default browser!');
95
+ logger.blank();
96
+ };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const { execa } = require('execa');
4
+ const { logger, createSpinner } = require('../utils/logger');
5
+ const { findProjectRoot } = require('../utils/config-finder');
6
+ const chalk = require('chalk');
7
+
8
+ module.exports = async function verifyCommand(opts) {
9
+ logger.title('Cypress Validate — Verify');
10
+ logger.divider();
11
+
12
+ const projectRoot = findProjectRoot();
13
+
14
+ const args = ['cypress', 'verify'];
15
+ if (opts.force) args.push('--force');
16
+
17
+ const spinner = createSpinner('Verifying Cypress installation...');
18
+ spinner.start();
19
+
20
+ try {
21
+ const result = await execa('npx', args, {
22
+ cwd: projectRoot,
23
+ stdio: ['ignore', 'pipe', 'pipe'],
24
+ reject: false,
25
+ });
26
+
27
+ if (result.exitCode === 0) {
28
+ spinner.succeed(chalk.green('Cypress is installed and verified!'));
29
+ logger.blank();
30
+ const lines = result.stdout.split('\n').filter(Boolean);
31
+ lines.forEach((line) => logger.info(line));
32
+ } else {
33
+ spinner.fail(chalk.red('Cypress verification failed'));
34
+ logger.blank();
35
+ logger.error(result.stderr || result.stdout || 'Unknown error');
36
+ logger.warn('Try running: ' + chalk.cyan('npx cypress-validate install'));
37
+ process.exit(1);
38
+ }
39
+ } catch (err) {
40
+ spinner.fail('Could not run cypress verify');
41
+ logger.error(err.message);
42
+ logger.warn('Is Cypress installed? Run: ' + chalk.cyan('npx cypress-validate install'));
43
+ process.exit(1);
44
+ }
45
+ };
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * cypress-validate — Programmatic API
5
+ * Allows using cypress-validate commands as a Node.js library.
6
+ *
7
+ * @example
8
+ * const { run, open, info } = require('cypress-validate');
9
+ * await run({ browser: 'chrome', headed: true });
10
+ */
11
+ module.exports = {
12
+ run: require('./commands/run'),
13
+ open: require('./commands/open'),
14
+ install: require('./commands/install'),
15
+ verify: require('./commands/verify'),
16
+ info: require('./commands/info'),
17
+ generate: require('./commands/generate'),
18
+ screenshot: require('./commands/screenshot'),
19
+ showReport: require('./commands/show-report'),
20
+ record: require('./commands/record'),
21
+ };