@vizzly-testing/cli 0.4.0 → 0.5.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/README.md +24 -0
- package/dist/cli.js +17 -1
- package/dist/commands/run.js +5 -3
- package/dist/commands/tdd.js +29 -22
- package/dist/commands/upload.js +4 -2
- package/dist/services/test-runner.js +10 -8
- package/dist/services/uploader.js +5 -3
- package/dist/types/commands/tdd.d.ts +3 -2
- package/dist/types/services/uploader.d.ts +2 -1
- package/dist/types/utils/ci-env.d.ts +55 -0
- package/dist/types/utils/git.d.ts +12 -0
- package/dist/utils/ci-env.js +293 -0
- package/dist/utils/git.js +38 -0
- package/docs/api-reference.md +15 -0
- package/docs/test-integration.md +6 -0
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -277,6 +277,10 @@ For CI/CD pipelines, use the `--wait` flag to wait for visual comparison results
|
|
|
277
277
|
run: npx vizzly run "npm test" --wait
|
|
278
278
|
env:
|
|
279
279
|
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
280
|
+
# Optional: Provide correct git information from GitHub context
|
|
281
|
+
VIZZLY_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
282
|
+
VIZZLY_COMMIT_SHA: ${{ github.event.head_commit.id }}
|
|
283
|
+
VIZZLY_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
280
284
|
```
|
|
281
285
|
|
|
282
286
|
### GitLab CI
|
|
@@ -319,10 +323,30 @@ Check if Vizzly is enabled in the current environment.
|
|
|
319
323
|
|
|
320
324
|
## Environment Variables
|
|
321
325
|
|
|
326
|
+
### Core Configuration
|
|
322
327
|
- `VIZZLY_TOKEN`: API authentication token. Example: `export VIZZLY_TOKEN=your-token`.
|
|
323
328
|
- `VIZZLY_API_URL`: Override API base URL. Default: `https://vizzly.dev`.
|
|
324
329
|
- `VIZZLY_LOG_LEVEL`: Logger level. One of `debug`, `info`, `warn`, `error`. Example: `export VIZZLY_LOG_LEVEL=debug`.
|
|
325
330
|
|
|
331
|
+
### Git Information Override
|
|
332
|
+
For enhanced CI/CD integration, you can override git detection with these environment variables:
|
|
333
|
+
|
|
334
|
+
- `VIZZLY_COMMIT_SHA`: Override detected commit SHA. Useful in CI environments.
|
|
335
|
+
- `VIZZLY_COMMIT_MESSAGE`: Override detected commit message. Useful in CI environments.
|
|
336
|
+
- `VIZZLY_BRANCH`: Override detected branch name. Useful in CI environments.
|
|
337
|
+
- `VIZZLY_PR_NUMBER`: Override detected pull request number. Useful for PR-specific builds.
|
|
338
|
+
|
|
339
|
+
**Example for GitHub Actions:**
|
|
340
|
+
```yaml
|
|
341
|
+
env:
|
|
342
|
+
VIZZLY_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
343
|
+
VIZZLY_COMMIT_SHA: ${{ github.event.head_commit.id }}
|
|
344
|
+
VIZZLY_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
345
|
+
VIZZLY_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
These variables take highest priority over both CLI arguments and automatic git detection.
|
|
349
|
+
|
|
326
350
|
## Contributing
|
|
327
351
|
|
|
328
352
|
We welcome contributions! Whether you're fixing bugs, adding features, or improving documentation, your help makes Vizzly better for everyone.
|
package/dist/cli.js
CHANGED
|
@@ -38,10 +38,26 @@ program.command('tdd').description('Run tests in TDD mode with local visual comp
|
|
|
38
38
|
validationErrors.forEach(error => console.error(` - ${error}`));
|
|
39
39
|
process.exit(1);
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const {
|
|
42
|
+
result,
|
|
43
|
+
cleanup
|
|
44
|
+
} = await tddCommand(command, options, globalOptions);
|
|
45
|
+
|
|
46
|
+
// Set up cleanup on process signals
|
|
47
|
+
const handleCleanup = async () => {
|
|
48
|
+
await cleanup();
|
|
49
|
+
};
|
|
50
|
+
process.once('SIGINT', () => {
|
|
51
|
+
handleCleanup().then(() => process.exit(1));
|
|
52
|
+
});
|
|
53
|
+
process.once('SIGTERM', () => {
|
|
54
|
+
handleCleanup().then(() => process.exit(1));
|
|
55
|
+
});
|
|
42
56
|
if (result && !result.success && result.exitCode > 0) {
|
|
57
|
+
await cleanup();
|
|
43
58
|
process.exit(result.exitCode);
|
|
44
59
|
}
|
|
60
|
+
await cleanup();
|
|
45
61
|
});
|
|
46
62
|
program.command('run').description('Run tests with Vizzly integration').argument('<command>', 'Test command to run').option('--port <port>', 'Port for screenshot server', '47392').option('-b, --build-name <name>', 'Custom build name').option('--branch <branch>', 'Git branch override').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--allow-no-token', 'Allow running without API token').option('--upload-all', 'Upload all screenshots without SHA deduplication').action(async (command, options) => {
|
|
47
63
|
const globalOptions = program.opts();
|
package/dist/commands/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
2
|
import { ConsoleUI } from '../utils/console-ui.js';
|
|
3
3
|
import { createServiceContainer } from '../container/index.js';
|
|
4
|
-
import { detectBranch, detectCommit,
|
|
4
|
+
import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Run command implementation
|
|
@@ -55,8 +55,9 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
55
55
|
// Collect git metadata and build info
|
|
56
56
|
const branch = await detectBranch(options.branch);
|
|
57
57
|
const commit = await detectCommit(options.commit);
|
|
58
|
-
const message = options.message || (await
|
|
58
|
+
const message = options.message || (await detectCommitMessage());
|
|
59
59
|
const buildName = await generateBuildNameWithGit(options.buildName);
|
|
60
|
+
const pullRequestNumber = detectPullRequestNumber();
|
|
60
61
|
if (globalOptions.verbose) {
|
|
61
62
|
ui.info('Configuration loaded', {
|
|
62
63
|
testCommand,
|
|
@@ -146,7 +147,8 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
146
147
|
eager: config.eager || false,
|
|
147
148
|
allowNoToken: config.allowNoToken || false,
|
|
148
149
|
wait: config.wait || options.wait || false,
|
|
149
|
-
uploadAll: options.uploadAll || false
|
|
150
|
+
uploadAll: options.uploadAll || false,
|
|
151
|
+
pullRequestNumber
|
|
150
152
|
};
|
|
151
153
|
|
|
152
154
|
// Start test run
|
package/dist/commands/tdd.js
CHANGED
|
@@ -8,6 +8,7 @@ import { detectBranch, detectCommit } from '../utils/git.js';
|
|
|
8
8
|
* @param {string} testCommand - Test command to execute
|
|
9
9
|
* @param {Object} options - Command options
|
|
10
10
|
* @param {Object} globalOptions - Global CLI options
|
|
11
|
+
* @returns {Promise<{result: Object, cleanup: Function}>} Result and cleanup function
|
|
11
12
|
*/
|
|
12
13
|
export async function tddCommand(testCommand, options = {}, globalOptions = {}) {
|
|
13
14
|
// Create UI handler
|
|
@@ -17,20 +18,17 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
17
18
|
color: !globalOptions.noColor
|
|
18
19
|
});
|
|
19
20
|
let testRunner = null;
|
|
21
|
+
let isCleanedUp = false;
|
|
20
22
|
|
|
21
|
-
//
|
|
23
|
+
// Create cleanup function that can be called by the caller
|
|
22
24
|
const cleanup = async () => {
|
|
25
|
+
if (isCleanedUp) return;
|
|
26
|
+
isCleanedUp = true;
|
|
23
27
|
ui.cleanup();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const sigintHandler = async () => {
|
|
28
|
-
await cleanup();
|
|
29
|
-
process.exit(1);
|
|
28
|
+
if (testRunner?.cancel) {
|
|
29
|
+
await testRunner.cancel();
|
|
30
|
+
}
|
|
30
31
|
};
|
|
31
|
-
const exitHandler = () => ui.cleanup();
|
|
32
|
-
process.on('SIGINT', sigintHandler);
|
|
33
|
-
process.on('exit', exitHandler);
|
|
34
32
|
try {
|
|
35
33
|
// Load configuration with CLI overrides
|
|
36
34
|
const allOptions = {
|
|
@@ -166,22 +164,31 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
166
164
|
}
|
|
167
165
|
ui.success('TDD test run completed');
|
|
168
166
|
|
|
169
|
-
//
|
|
170
|
-
|
|
167
|
+
// Determine success based on comparison results
|
|
168
|
+
const hasFailures = result.failed || result.comparisons && result.comparisons.some(c => c.status === 'failed');
|
|
169
|
+
if (hasFailures) {
|
|
171
170
|
ui.error('Visual differences detected in TDD mode', {}, 0);
|
|
172
|
-
// Return error status without calling process.exit in tests
|
|
173
|
-
return {
|
|
174
|
-
success: false,
|
|
175
|
-
exitCode: 1
|
|
176
|
-
};
|
|
177
171
|
}
|
|
178
|
-
|
|
172
|
+
|
|
173
|
+
// Return result and cleanup function
|
|
174
|
+
return {
|
|
175
|
+
result: {
|
|
176
|
+
success: !hasFailures,
|
|
177
|
+
exitCode: hasFailures ? 1 : 0,
|
|
178
|
+
...result
|
|
179
|
+
},
|
|
180
|
+
cleanup
|
|
181
|
+
};
|
|
179
182
|
} catch (error) {
|
|
180
183
|
ui.error('TDD test run failed', error);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
return {
|
|
185
|
+
result: {
|
|
186
|
+
success: false,
|
|
187
|
+
exitCode: 1,
|
|
188
|
+
error: error.message
|
|
189
|
+
},
|
|
190
|
+
cleanup
|
|
191
|
+
};
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
|
package/dist/commands/upload.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
2
|
import { ConsoleUI } from '../utils/console-ui.js';
|
|
3
3
|
import { createServiceContainer } from '../container/index.js';
|
|
4
|
-
import { detectBranch, detectCommit,
|
|
4
|
+
import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
|
|
5
5
|
import { ApiService } from '../services/api-service.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -71,8 +71,9 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
71
71
|
// Collect git metadata if not provided
|
|
72
72
|
const branch = await detectBranch(options.branch);
|
|
73
73
|
const commit = await detectCommit(options.commit);
|
|
74
|
-
const message = options.message || (await
|
|
74
|
+
const message = options.message || (await detectCommitMessage());
|
|
75
75
|
const buildName = await generateBuildNameWithGit(options.buildName);
|
|
76
|
+
const pullRequestNumber = detectPullRequestNumber();
|
|
76
77
|
ui.info(`Uploading screenshots from: ${screenshotsPath}`);
|
|
77
78
|
if (globalOptions.verbose) {
|
|
78
79
|
ui.info('Configuration loaded', {
|
|
@@ -99,6 +100,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
99
100
|
threshold: config.comparison.threshold,
|
|
100
101
|
uploadAll: options.uploadAll || false,
|
|
101
102
|
metadata: options.metadata ? JSON.parse(options.metadata) : {},
|
|
103
|
+
pullRequestNumber,
|
|
102
104
|
onProgress: progressData => {
|
|
103
105
|
const {
|
|
104
106
|
message: progressMessage,
|
|
@@ -114,13 +114,12 @@ export class TestRunner extends BaseService {
|
|
|
114
114
|
const apiService = await this.createApiService();
|
|
115
115
|
if (apiService) {
|
|
116
116
|
const buildResult = await apiService.createBuild({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
117
|
+
name: options.buildName || `Test Run ${new Date().toISOString()}`,
|
|
118
|
+
branch: options.branch || 'main',
|
|
119
|
+
environment: options.environment || 'test',
|
|
120
|
+
commit_sha: options.commit,
|
|
121
|
+
commit_message: options.message,
|
|
122
|
+
github_pull_request_number: options.pullRequestNumber
|
|
124
123
|
});
|
|
125
124
|
this.logger.debug(`Build created with ID: ${buildResult.id}`);
|
|
126
125
|
|
|
@@ -154,10 +153,13 @@ export class TestRunner extends BaseService {
|
|
|
154
153
|
}
|
|
155
154
|
try {
|
|
156
155
|
if (isTddMode) {
|
|
157
|
-
// TDD mode: use server handler to finalize
|
|
156
|
+
// TDD mode: use server handler to finalize (local-only)
|
|
158
157
|
if (this.serverManager.server?.finishBuild) {
|
|
159
158
|
await this.serverManager.server.finishBuild(buildId);
|
|
160
159
|
this.logger.debug(`TDD build ${buildId} finalized with success: ${success}`);
|
|
160
|
+
} else {
|
|
161
|
+
// In TDD mode without a server, just log that finalization is skipped
|
|
162
|
+
this.logger.debug(`TDD build ${buildId} finalization skipped (local-only mode)`);
|
|
161
163
|
}
|
|
162
164
|
} else {
|
|
163
165
|
// API mode: use API service to update build status
|
|
@@ -50,6 +50,7 @@ export function createUploader({
|
|
|
50
50
|
message,
|
|
51
51
|
environment = 'production',
|
|
52
52
|
threshold,
|
|
53
|
+
pullRequestNumber,
|
|
53
54
|
onProgress = () => {}
|
|
54
55
|
}) {
|
|
55
56
|
try {
|
|
@@ -96,10 +97,11 @@ export function createUploader({
|
|
|
96
97
|
const buildInfo = {
|
|
97
98
|
name: buildName || `Upload ${new Date().toISOString()}`,
|
|
98
99
|
branch: branch || (await getDefaultBranch()) || 'main',
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
commit_sha: commit,
|
|
101
|
+
commit_message: message,
|
|
101
102
|
environment,
|
|
102
|
-
threshold
|
|
103
|
+
threshold,
|
|
104
|
+
github_pull_request_number: pullRequestNumber
|
|
103
105
|
};
|
|
104
106
|
const build = await api.createBuild(buildInfo);
|
|
105
107
|
const buildId = build.id;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* @param {string} testCommand - Test command to execute
|
|
4
4
|
* @param {Object} options - Command options
|
|
5
5
|
* @param {Object} globalOptions - Global CLI options
|
|
6
|
+
* @returns {Promise<{result: Object, cleanup: Function}>} Result and cleanup function
|
|
6
7
|
*/
|
|
7
8
|
export function tddCommand(testCommand: string, options?: any, globalOptions?: any): Promise<{
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
result: any;
|
|
10
|
+
cleanup: Function;
|
|
10
11
|
}>;
|
|
11
12
|
/**
|
|
12
13
|
* Validate TDD options
|
|
@@ -8,7 +8,7 @@ export function createUploader({ apiKey, apiUrl, userAgent, command, upload: upl
|
|
|
8
8
|
command: any;
|
|
9
9
|
upload?: {};
|
|
10
10
|
}, options?: {}): {
|
|
11
|
-
upload: ({ screenshotsDir, buildName, branch, commit, message, environment, threshold, onProgress, }: {
|
|
11
|
+
upload: ({ screenshotsDir, buildName, branch, commit, message, environment, threshold, pullRequestNumber, onProgress, }: {
|
|
12
12
|
screenshotsDir: any;
|
|
13
13
|
buildName: any;
|
|
14
14
|
branch: any;
|
|
@@ -16,6 +16,7 @@ export function createUploader({ apiKey, apiUrl, userAgent, command, upload: upl
|
|
|
16
16
|
message: any;
|
|
17
17
|
environment?: string;
|
|
18
18
|
threshold: any;
|
|
19
|
+
pullRequestNumber: any;
|
|
19
20
|
onProgress?: () => void;
|
|
20
21
|
}) => Promise<{
|
|
21
22
|
success: boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Environment Detection
|
|
3
|
+
*
|
|
4
|
+
* Generic functions to extract git and PR information from any CI provider
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Get the branch name from CI environment variables
|
|
8
|
+
* @returns {string|null} Branch name or null if not available
|
|
9
|
+
*/
|
|
10
|
+
export function getBranch(): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Get the commit SHA from CI environment variables
|
|
13
|
+
* @returns {string|null} Commit SHA or null if not available
|
|
14
|
+
*/
|
|
15
|
+
export function getCommit(): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Get the commit message from CI environment variables
|
|
18
|
+
* @returns {string|null} Commit message or null if not available
|
|
19
|
+
*/
|
|
20
|
+
export function getCommitMessage(): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Get the pull request number from CI environment variables
|
|
23
|
+
* @returns {number|null} PR number or null if not available/not a PR
|
|
24
|
+
*/
|
|
25
|
+
export function getPullRequestNumber(): number | null;
|
|
26
|
+
/**
|
|
27
|
+
* Get the PR head SHA from CI environment variables
|
|
28
|
+
* @returns {string|null} PR head SHA or null if not available
|
|
29
|
+
*/
|
|
30
|
+
export function getPullRequestHeadSha(): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Get the PR base SHA from CI environment variables
|
|
33
|
+
* @returns {string|null} PR base SHA or null if not available
|
|
34
|
+
*/
|
|
35
|
+
export function getPullRequestBaseSha(): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Get the PR head ref (branch) from CI environment variables
|
|
38
|
+
* @returns {string|null} PR head ref or null if not available
|
|
39
|
+
*/
|
|
40
|
+
export function getPullRequestHeadRef(): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Get the PR base ref (target branch) from CI environment variables
|
|
43
|
+
* @returns {string|null} PR base ref or null if not available
|
|
44
|
+
*/
|
|
45
|
+
export function getPullRequestBaseRef(): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Check if we're currently in a pull request context
|
|
48
|
+
* @returns {boolean} True if in a PR context
|
|
49
|
+
*/
|
|
50
|
+
export function isPullRequest(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Get the CI provider name
|
|
53
|
+
* @returns {string} CI provider name or 'unknown'
|
|
54
|
+
*/
|
|
55
|
+
export function getCIProvider(): string;
|
|
@@ -9,6 +9,13 @@ export function generateBuildName(): string;
|
|
|
9
9
|
* @returns {Promise<string|null>} Commit message or null if not available
|
|
10
10
|
*/
|
|
11
11
|
export function getCommitMessage(cwd?: string): Promise<string | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Detect commit message with override and environment variable support
|
|
14
|
+
* @param {string} override - Commit message override from CLI
|
|
15
|
+
* @param {string} cwd - Working directory
|
|
16
|
+
* @returns {Promise<string|null>} Commit message or null if not available
|
|
17
|
+
*/
|
|
18
|
+
export function detectCommitMessage(override?: string, cwd?: string): Promise<string | null>;
|
|
12
19
|
/**
|
|
13
20
|
* Check if the working directory is a git repository
|
|
14
21
|
* @param {string} cwd - Working directory
|
|
@@ -42,3 +49,8 @@ export function detectCommit(override?: string, cwd?: string): Promise<string |
|
|
|
42
49
|
* @returns {Promise<string>}
|
|
43
50
|
*/
|
|
44
51
|
export function generateBuildNameWithGit(override?: string, cwd?: string): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Detect pull request number from CI environment
|
|
54
|
+
* @returns {number|null} Pull request number or null if not in PR context
|
|
55
|
+
*/
|
|
56
|
+
export function detectPullRequestNumber(): number | null;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Environment Detection
|
|
3
|
+
*
|
|
4
|
+
* Generic functions to extract git and PR information from any CI provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the branch name from CI environment variables
|
|
9
|
+
* @returns {string|null} Branch name or null if not available
|
|
10
|
+
*/
|
|
11
|
+
export function getBranch() {
|
|
12
|
+
return process.env.VIZZLY_BRANCH ||
|
|
13
|
+
// Vizzly override
|
|
14
|
+
process.env.GITHUB_HEAD_REF ||
|
|
15
|
+
// GitHub Actions (for PRs)
|
|
16
|
+
process.env.GITHUB_REF_NAME ||
|
|
17
|
+
// GitHub Actions (for pushes)
|
|
18
|
+
process.env.CI_COMMIT_REF_NAME ||
|
|
19
|
+
// GitLab CI
|
|
20
|
+
process.env.CIRCLE_BRANCH ||
|
|
21
|
+
// CircleCI
|
|
22
|
+
process.env.TRAVIS_BRANCH ||
|
|
23
|
+
// Travis CI
|
|
24
|
+
process.env.BUILDKITE_BRANCH ||
|
|
25
|
+
// Buildkite
|
|
26
|
+
process.env.DRONE_BRANCH ||
|
|
27
|
+
// Drone CI
|
|
28
|
+
process.env.BRANCH_NAME ||
|
|
29
|
+
// Jenkins
|
|
30
|
+
process.env.GIT_BRANCH ||
|
|
31
|
+
// Jenkins (alternative)
|
|
32
|
+
process.env.BITBUCKET_BRANCH ||
|
|
33
|
+
// Bitbucket Pipelines
|
|
34
|
+
process.env.WERCKER_GIT_BRANCH ||
|
|
35
|
+
// Wercker
|
|
36
|
+
process.env.APPVEYOR_REPO_BRANCH ||
|
|
37
|
+
// AppVeyor
|
|
38
|
+
process.env.BUILD_SOURCEBRANCH?.replace(/^refs\/heads\//, '') ||
|
|
39
|
+
// Azure DevOps
|
|
40
|
+
process.env.CODEBUILD_WEBHOOK_HEAD_REF?.replace(/^refs\/heads\//, '') ||
|
|
41
|
+
// AWS CodeBuild
|
|
42
|
+
process.env.SEMAPHORE_GIT_BRANCH ||
|
|
43
|
+
// Semaphore
|
|
44
|
+
null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the commit SHA from CI environment variables
|
|
49
|
+
* @returns {string|null} Commit SHA or null if not available
|
|
50
|
+
*/
|
|
51
|
+
export function getCommit() {
|
|
52
|
+
return process.env.VIZZLY_COMMIT_SHA ||
|
|
53
|
+
// Vizzly override
|
|
54
|
+
process.env.GITHUB_SHA ||
|
|
55
|
+
// GitHub Actions
|
|
56
|
+
process.env.CI_COMMIT_SHA ||
|
|
57
|
+
// GitLab CI
|
|
58
|
+
process.env.CIRCLE_SHA1 ||
|
|
59
|
+
// CircleCI
|
|
60
|
+
process.env.TRAVIS_COMMIT ||
|
|
61
|
+
// Travis CI
|
|
62
|
+
process.env.BUILDKITE_COMMIT ||
|
|
63
|
+
// Buildkite
|
|
64
|
+
process.env.DRONE_COMMIT_SHA ||
|
|
65
|
+
// Drone CI
|
|
66
|
+
process.env.GIT_COMMIT ||
|
|
67
|
+
// Jenkins
|
|
68
|
+
process.env.BITBUCKET_COMMIT ||
|
|
69
|
+
// Bitbucket Pipelines
|
|
70
|
+
process.env.WERCKER_GIT_COMMIT ||
|
|
71
|
+
// Wercker
|
|
72
|
+
process.env.APPVEYOR_REPO_COMMIT ||
|
|
73
|
+
// AppVeyor
|
|
74
|
+
process.env.BUILD_SOURCEVERSION ||
|
|
75
|
+
// Azure DevOps
|
|
76
|
+
process.env.CODEBUILD_RESOLVED_SOURCE_VERSION ||
|
|
77
|
+
// AWS CodeBuild
|
|
78
|
+
process.env.SEMAPHORE_GIT_SHA ||
|
|
79
|
+
// Semaphore
|
|
80
|
+
process.env.HEROKU_TEST_RUN_COMMIT_VERSION ||
|
|
81
|
+
// Heroku CI
|
|
82
|
+
process.env.COMMIT_SHA ||
|
|
83
|
+
// Generic
|
|
84
|
+
process.env.HEAD_COMMIT ||
|
|
85
|
+
// Alternative generic
|
|
86
|
+
process.env.SHA ||
|
|
87
|
+
// Another generic option
|
|
88
|
+
null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the commit message from CI environment variables
|
|
93
|
+
* @returns {string|null} Commit message or null if not available
|
|
94
|
+
*/
|
|
95
|
+
export function getCommitMessage() {
|
|
96
|
+
return process.env.VIZZLY_COMMIT_MESSAGE ||
|
|
97
|
+
// Vizzly override
|
|
98
|
+
process.env.CI_COMMIT_MESSAGE ||
|
|
99
|
+
// GitLab CI
|
|
100
|
+
process.env.TRAVIS_COMMIT_MESSAGE ||
|
|
101
|
+
// Travis CI
|
|
102
|
+
process.env.BUILDKITE_MESSAGE ||
|
|
103
|
+
// Buildkite
|
|
104
|
+
process.env.DRONE_COMMIT_MESSAGE ||
|
|
105
|
+
// Drone CI
|
|
106
|
+
process.env.APPVEYOR_REPO_COMMIT_MESSAGE ||
|
|
107
|
+
// AppVeyor
|
|
108
|
+
process.env.COMMIT_MESSAGE ||
|
|
109
|
+
// Generic
|
|
110
|
+
null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the pull request number from CI environment variables
|
|
115
|
+
* @returns {number|null} PR number or null if not available/not a PR
|
|
116
|
+
*/
|
|
117
|
+
export function getPullRequestNumber() {
|
|
118
|
+
// Check VIZZLY override first
|
|
119
|
+
if (process.env.VIZZLY_PR_NUMBER) {
|
|
120
|
+
return parseInt(process.env.VIZZLY_PR_NUMBER, 10);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// GitHub Actions - extract from GITHUB_REF
|
|
124
|
+
if (process.env.GITHUB_ACTIONS && process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
125
|
+
const prMatch = process.env.GITHUB_REF?.match(/refs\/pull\/(\d+)\/merge/);
|
|
126
|
+
if (prMatch?.[1]) return parseInt(prMatch[1], 10);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// GitLab CI
|
|
130
|
+
if (process.env.CI_MERGE_REQUEST_ID) {
|
|
131
|
+
return parseInt(process.env.CI_MERGE_REQUEST_ID, 10);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// CircleCI - extract from PR URL
|
|
135
|
+
if (process.env.CIRCLE_PULL_REQUEST) {
|
|
136
|
+
const prMatch = process.env.CIRCLE_PULL_REQUEST.match(/\/pull\/(\d+)$/);
|
|
137
|
+
if (prMatch?.[1]) return parseInt(prMatch[1], 10);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Travis CI
|
|
141
|
+
if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false') {
|
|
142
|
+
return parseInt(process.env.TRAVIS_PULL_REQUEST, 10);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Buildkite
|
|
146
|
+
if (process.env.BUILDKITE_PULL_REQUEST && process.env.BUILDKITE_PULL_REQUEST !== 'false') {
|
|
147
|
+
return parseInt(process.env.BUILDKITE_PULL_REQUEST, 10);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Drone CI
|
|
151
|
+
if (process.env.DRONE_PULL_REQUEST) {
|
|
152
|
+
return parseInt(process.env.DRONE_PULL_REQUEST, 10);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Jenkins (GitHub Pull Request Builder plugin)
|
|
156
|
+
if (process.env.ghprbPullId) {
|
|
157
|
+
return parseInt(process.env.ghprbPullId, 10);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Azure DevOps
|
|
161
|
+
if (process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
|
|
162
|
+
return parseInt(process.env.SYSTEM_PULLREQUEST_PULLREQUESTID, 10);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// AppVeyor
|
|
166
|
+
if (process.env.APPVEYOR_PULL_REQUEST_NUMBER) {
|
|
167
|
+
return parseInt(process.env.APPVEYOR_PULL_REQUEST_NUMBER, 10);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the PR head SHA from CI environment variables
|
|
174
|
+
* @returns {string|null} PR head SHA or null if not available
|
|
175
|
+
*/
|
|
176
|
+
export function getPullRequestHeadSha() {
|
|
177
|
+
return process.env.VIZZLY_PR_HEAD_SHA ||
|
|
178
|
+
// Vizzly override
|
|
179
|
+
process.env.GITHUB_SHA ||
|
|
180
|
+
// GitHub Actions
|
|
181
|
+
process.env.CI_COMMIT_SHA ||
|
|
182
|
+
// GitLab CI
|
|
183
|
+
process.env.CIRCLE_SHA1 ||
|
|
184
|
+
// CircleCI
|
|
185
|
+
process.env.TRAVIS_COMMIT ||
|
|
186
|
+
// Travis CI
|
|
187
|
+
process.env.BUILDKITE_COMMIT ||
|
|
188
|
+
// Buildkite
|
|
189
|
+
process.env.DRONE_COMMIT_SHA ||
|
|
190
|
+
// Drone CI
|
|
191
|
+
process.env.ghprbActualCommit ||
|
|
192
|
+
// Jenkins
|
|
193
|
+
process.env.GIT_COMMIT ||
|
|
194
|
+
// Jenkins fallback
|
|
195
|
+
process.env.BUILD_SOURCEVERSION ||
|
|
196
|
+
// Azure DevOps
|
|
197
|
+
process.env.APPVEYOR_REPO_COMMIT ||
|
|
198
|
+
// AppVeyor
|
|
199
|
+
null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get the PR base SHA from CI environment variables
|
|
204
|
+
* @returns {string|null} PR base SHA or null if not available
|
|
205
|
+
*/
|
|
206
|
+
export function getPullRequestBaseSha() {
|
|
207
|
+
return process.env.VIZZLY_PR_BASE_SHA ||
|
|
208
|
+
// Vizzly override
|
|
209
|
+
process.env.CI_MERGE_REQUEST_TARGET_BRANCH_SHA ||
|
|
210
|
+
// GitLab CI
|
|
211
|
+
null // Most CIs don't provide this
|
|
212
|
+
;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the PR head ref (branch) from CI environment variables
|
|
217
|
+
* @returns {string|null} PR head ref or null if not available
|
|
218
|
+
*/
|
|
219
|
+
export function getPullRequestHeadRef() {
|
|
220
|
+
return process.env.VIZZLY_PR_HEAD_REF ||
|
|
221
|
+
// Vizzly override
|
|
222
|
+
process.env.GITHUB_HEAD_REF ||
|
|
223
|
+
// GitHub Actions
|
|
224
|
+
process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME ||
|
|
225
|
+
// GitLab CI
|
|
226
|
+
process.env.TRAVIS_PULL_REQUEST_BRANCH ||
|
|
227
|
+
// Travis CI
|
|
228
|
+
process.env.DRONE_SOURCE_BRANCH ||
|
|
229
|
+
// Drone CI
|
|
230
|
+
process.env.ghprbSourceBranch ||
|
|
231
|
+
// Jenkins
|
|
232
|
+
process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH?.replace(/^refs\/heads\//, '') ||
|
|
233
|
+
// Azure DevOps
|
|
234
|
+
process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH ||
|
|
235
|
+
// AppVeyor
|
|
236
|
+
null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get the PR base ref (target branch) from CI environment variables
|
|
241
|
+
* @returns {string|null} PR base ref or null if not available
|
|
242
|
+
*/
|
|
243
|
+
export function getPullRequestBaseRef() {
|
|
244
|
+
return process.env.VIZZLY_PR_BASE_REF ||
|
|
245
|
+
// Vizzly override
|
|
246
|
+
process.env.GITHUB_BASE_REF ||
|
|
247
|
+
// GitHub Actions
|
|
248
|
+
process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME ||
|
|
249
|
+
// GitLab CI
|
|
250
|
+
process.env.TRAVIS_BRANCH ||
|
|
251
|
+
// Travis CI (target branch)
|
|
252
|
+
process.env.BUILDKITE_PULL_REQUEST_BASE_BRANCH ||
|
|
253
|
+
// Buildkite
|
|
254
|
+
process.env.DRONE_TARGET_BRANCH ||
|
|
255
|
+
// Drone CI
|
|
256
|
+
process.env.ghprbTargetBranch ||
|
|
257
|
+
// Jenkins
|
|
258
|
+
process.env.SYSTEM_PULLREQUEST_TARGETBRANCH?.replace(/^refs\/heads\//, '') ||
|
|
259
|
+
// Azure DevOps
|
|
260
|
+
process.env.APPVEYOR_REPO_BRANCH ||
|
|
261
|
+
// AppVeyor (target branch)
|
|
262
|
+
null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if we're currently in a pull request context
|
|
267
|
+
* @returns {boolean} True if in a PR context
|
|
268
|
+
*/
|
|
269
|
+
export function isPullRequest() {
|
|
270
|
+
return getPullRequestNumber() !== null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the CI provider name
|
|
275
|
+
* @returns {string} CI provider name or 'unknown'
|
|
276
|
+
*/
|
|
277
|
+
export function getCIProvider() {
|
|
278
|
+
if (process.env.GITHUB_ACTIONS) return 'github-actions';
|
|
279
|
+
if (process.env.GITLAB_CI) return 'gitlab-ci';
|
|
280
|
+
if (process.env.CIRCLECI) return 'circleci';
|
|
281
|
+
if (process.env.TRAVIS) return 'travis-ci';
|
|
282
|
+
if (process.env.BUILDKITE) return 'buildkite';
|
|
283
|
+
if (process.env.DRONE) return 'drone-ci';
|
|
284
|
+
if (process.env.JENKINS_URL) return 'jenkins';
|
|
285
|
+
if (process.env.AZURE_HTTP_USER_AGENT || process.env.TF_BUILD) return 'azure-devops';
|
|
286
|
+
if (process.env.CODEBUILD_BUILD_ID) return 'aws-codebuild';
|
|
287
|
+
if (process.env.APPVEYOR) return 'appveyor';
|
|
288
|
+
if (process.env.SEMAPHORE) return 'semaphore';
|
|
289
|
+
if (process.env.WERCKER) return 'wercker';
|
|
290
|
+
if (process.env.BITBUCKET_BUILD_NUMBER) return 'bitbucket-pipelines';
|
|
291
|
+
if (process.env.HEROKU_TEST_RUN_ID) return 'heroku-ci';
|
|
292
|
+
return 'unknown';
|
|
293
|
+
}
|
package/dist/utils/git.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
+
import { getBranch as getCIBranch, getCommit as getCICommit, getCommitMessage as getCICommitMessage, getPullRequestNumber } from './ci-env.js';
|
|
3
4
|
const execAsync = promisify(exec);
|
|
4
5
|
export async function getCommonAncestor(commit1, commit2, cwd = process.cwd()) {
|
|
5
6
|
try {
|
|
@@ -144,6 +145,23 @@ export async function getCommitMessage(cwd = process.cwd()) {
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Detect commit message with override and environment variable support
|
|
150
|
+
* @param {string} override - Commit message override from CLI
|
|
151
|
+
* @param {string} cwd - Working directory
|
|
152
|
+
* @returns {Promise<string|null>} Commit message or null if not available
|
|
153
|
+
*/
|
|
154
|
+
export async function detectCommitMessage(override = null, cwd = process.cwd()) {
|
|
155
|
+
if (override) return override;
|
|
156
|
+
|
|
157
|
+
// Try CI environment variables first
|
|
158
|
+
const ciCommitMessage = getCICommitMessage();
|
|
159
|
+
if (ciCommitMessage) return ciCommitMessage;
|
|
160
|
+
|
|
161
|
+
// Fallback to regular git log
|
|
162
|
+
return await getCommitMessage(cwd);
|
|
163
|
+
}
|
|
164
|
+
|
|
147
165
|
/**
|
|
148
166
|
* Check if the working directory is a git repository
|
|
149
167
|
* @param {string} cwd - Working directory
|
|
@@ -193,6 +211,12 @@ export async function getGitStatus(cwd = process.cwd()) {
|
|
|
193
211
|
*/
|
|
194
212
|
export async function detectBranch(override = null, cwd = process.cwd()) {
|
|
195
213
|
if (override) return override;
|
|
214
|
+
|
|
215
|
+
// Try CI environment variables first
|
|
216
|
+
const ciBranch = getCIBranch();
|
|
217
|
+
if (ciBranch) return ciBranch;
|
|
218
|
+
|
|
219
|
+
// Fallback to git command when no CI environment variables
|
|
196
220
|
const currentBranch = await getCurrentBranch(cwd);
|
|
197
221
|
return currentBranch || 'unknown';
|
|
198
222
|
}
|
|
@@ -205,6 +229,12 @@ export async function detectBranch(override = null, cwd = process.cwd()) {
|
|
|
205
229
|
*/
|
|
206
230
|
export async function detectCommit(override = null, cwd = process.cwd()) {
|
|
207
231
|
if (override) return override;
|
|
232
|
+
|
|
233
|
+
// Try CI environment variables first
|
|
234
|
+
const ciCommit = getCICommit();
|
|
235
|
+
if (ciCommit) return ciCommit;
|
|
236
|
+
|
|
237
|
+
// Fallback to git command when no CI environment variables
|
|
208
238
|
return await getCurrentCommitSha(cwd);
|
|
209
239
|
}
|
|
210
240
|
|
|
@@ -223,4 +253,12 @@ export async function generateBuildNameWithGit(override = null, cwd = process.cw
|
|
|
223
253
|
return `${branch}-${shortCommit}`;
|
|
224
254
|
}
|
|
225
255
|
return generateBuildName();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Detect pull request number from CI environment
|
|
260
|
+
* @returns {number|null} Pull request number or null if not in PR context
|
|
261
|
+
*/
|
|
262
|
+
export function detectPullRequestNumber() {
|
|
263
|
+
return getPullRequestNumber();
|
|
226
264
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -481,14 +481,29 @@ Configuration loaded via cosmiconfig in this order:
|
|
|
481
481
|
|
|
482
482
|
### Environment Variables
|
|
483
483
|
|
|
484
|
+
**Core Configuration:**
|
|
484
485
|
- `VIZZLY_TOKEN` - API authentication token
|
|
485
486
|
- `VIZZLY_API_URL` - API base URL override
|
|
486
487
|
- `VIZZLY_LOG_LEVEL` - Logger level (`debug`, `info`, `warn`, `error`)
|
|
488
|
+
|
|
489
|
+
**Git Information Override (CI/CD Enhancement):**
|
|
490
|
+
- `VIZZLY_COMMIT_SHA` - Override detected commit SHA
|
|
491
|
+
- `VIZZLY_COMMIT_MESSAGE` - Override detected commit message
|
|
492
|
+
- `VIZZLY_BRANCH` - Override detected branch name
|
|
493
|
+
- `VIZZLY_PR_NUMBER` - Override detected pull request number
|
|
494
|
+
|
|
495
|
+
**Runtime (Set by CLI):**
|
|
487
496
|
- `VIZZLY_SERVER_URL` - Screenshot server URL (set by CLI)
|
|
488
497
|
- `VIZZLY_ENABLED` - Enable/disable client (set by CLI)
|
|
489
498
|
- `VIZZLY_BUILD_ID` - Current build ID (set by CLI)
|
|
490
499
|
- `VIZZLY_TDD_MODE` - TDD mode active (set by CLI)
|
|
491
500
|
|
|
501
|
+
**Priority Order for Git Information:**
|
|
502
|
+
1. CLI arguments (`--commit`, `--branch`, `--message`)
|
|
503
|
+
2. `VIZZLY_*` environment variables
|
|
504
|
+
3. CI-specific environment variables (e.g., `GITHUB_SHA`, `CI_COMMIT_SHA`)
|
|
505
|
+
4. Git command detection
|
|
506
|
+
|
|
492
507
|
## Error Handling
|
|
493
508
|
|
|
494
509
|
### Client Errors
|
package/docs/test-integration.md
CHANGED
|
@@ -275,8 +275,14 @@ jobs:
|
|
|
275
275
|
- run: npx vizzly run "npm test" --wait
|
|
276
276
|
env:
|
|
277
277
|
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
278
|
+
# Optional: Enhanced git information from GitHub context
|
|
279
|
+
VIZZLY_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
280
|
+
VIZZLY_COMMIT_SHA: ${{ github.event.head_commit.id }}
|
|
281
|
+
VIZZLY_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
278
282
|
```
|
|
279
283
|
|
|
284
|
+
**Enhanced Git Information:** The `VIZZLY_*` environment variables ensure accurate git metadata is captured in your builds, avoiding issues with merge commits that can occur in CI environments.
|
|
285
|
+
|
|
280
286
|
### GitLab CI
|
|
281
287
|
|
|
282
288
|
```yaml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Visual review platform for UI developers and designers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"visual-testing",
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
"registry": "https://registry.npmjs.org/"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"commander": "^
|
|
76
|
+
"commander": "^14.0.0",
|
|
77
77
|
"cosmiconfig": "^9.0.0",
|
|
78
78
|
"dotenv": "^17.2.1",
|
|
79
79
|
"form-data": "^4.0.0",
|
|
80
|
-
"glob": "^
|
|
80
|
+
"glob": "^11.0.3",
|
|
81
81
|
"odiff-bin": "^3.2.1"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
@@ -93,8 +93,7 @@
|
|
|
93
93
|
"eslint-config-prettier": "^10.1.8",
|
|
94
94
|
"eslint-plugin-prettier": "^5.5.3",
|
|
95
95
|
"prettier": "^3.6.2",
|
|
96
|
-
"
|
|
97
|
-
"rimraf": "^5.0.5",
|
|
96
|
+
"rimraf": "^6.0.1",
|
|
98
97
|
"typescript": "^5.0.4",
|
|
99
98
|
"vite": "^7.1.2",
|
|
100
99
|
"vitest": "^3.2.4"
|