@vizzly-testing/cli 0.33.0 → 0.35.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 +53 -3
- package/dist/api/core.js +2 -1
- package/dist/cli.js +21 -8
- package/dist/client/index.js +62 -21
- package/dist/commands/api.js +9 -3
- package/dist/commands/init.js +214 -2
- package/dist/commands/preview.js +13 -11
- package/dist/commands/project.js +1 -1
- package/dist/commands/run.js +18 -6
- package/dist/commands/tdd-daemon.js +8 -3
- package/dist/commands/tdd.js +24 -4
- package/dist/commands/upload.js +31 -13
- package/dist/reporter/reporter-bundle.iife.js +1 -1
- package/dist/sdk/index.js +23 -9
- package/dist/server/handlers/api-handler.js +3 -2
- package/dist/server/handlers/tdd-handler.js +5 -1
- package/dist/server/routers/screenshot.js +11 -4
- package/dist/services/build-manager.js +6 -1
- package/dist/test-runner/core.js +4 -1
- package/dist/test-runner/operations.js +2 -1
- package/dist/types/client.d.ts +32 -5
- package/dist/types/index.d.ts +33 -5
- package/dist/uploader/core.js +41 -9
- package/dist/uploader/operations.js +19 -17
- package/dist/utils/config-loader.js +12 -6
- package/dist/utils/context.js +46 -76
- package/dist/utils/environment-config.js +20 -0
- package/dist/utils/output.js +5 -5
- package/dist/utils/screenshot-options.js +60 -10
- package/package.json +2 -1
- package/skills/vizzly/SKILL.md +99 -0
- package/skills/vizzly/agents/openai.yaml +4 -0
- package/skills/vizzly/references/cli-context.md +42 -0
- package/skills/vizzly/references/dynamic-content.md +44 -0
- package/skills/vizzly/references/public-screenshots.md +44 -0
- package/skills/vizzly/references/sdks.md +62 -0
- package/skills/vizzly/references/setup-ci.md +42 -0
package/README.md
CHANGED
|
@@ -28,6 +28,16 @@ pnpm install -g @vizzly-testing/cli
|
|
|
28
28
|
vizzly init
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
For agent-friendly repos, install the Vizzly skill and add a short project
|
|
32
|
+
`AGENTS.md` note:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
vizzly init --agent-guidance
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `vizzly init --agent-skill` to install only the local skill, or
|
|
39
|
+
`vizzly init --skip-agent-skill` when you want config without the agent prompt.
|
|
40
|
+
|
|
31
41
|
### Start Local TDD
|
|
32
42
|
|
|
33
43
|
Start the TDD server, run your tests, and open the dashboard at
|
|
@@ -47,9 +57,14 @@ Use cloud builds when you want shared baselines, team review, and CI status:
|
|
|
47
57
|
|
|
48
58
|
```bash
|
|
49
59
|
vizzly login
|
|
60
|
+
vizzly project link your-org/your-project
|
|
50
61
|
vizzly run "pnpm test" --wait
|
|
51
62
|
```
|
|
52
63
|
|
|
64
|
+
`vizzly login` authenticates your user account. `vizzly project link` creates a
|
|
65
|
+
project-scoped upload credential for this checkout, which `vizzly run` uses for
|
|
66
|
+
cloud uploads.
|
|
67
|
+
|
|
53
68
|
`--wait` blocks until Vizzly finishes processing the build. It exits with code
|
|
54
69
|
`1` when visual differences need review.
|
|
55
70
|
|
|
@@ -104,12 +119,23 @@ test('homepage looks correct', async ({ page }) => {
|
|
|
104
119
|
|
|
105
120
|
let screenshot = await page.screenshot();
|
|
106
121
|
await vizzlyScreenshot('homepage', screenshot, {
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
fullPage: true,
|
|
123
|
+
requestTimeout: 5000,
|
|
124
|
+
properties: {
|
|
125
|
+
browser: 'chrome',
|
|
126
|
+
viewport: { width: 1920, height: 1080 },
|
|
127
|
+
},
|
|
109
128
|
});
|
|
110
129
|
});
|
|
111
130
|
```
|
|
112
131
|
|
|
132
|
+
`properties` is your metadata bag for baseline grouping, filtering, and
|
|
133
|
+
debugging. SDK options such as `threshold`, `minClusterSize`, `fullPage`,
|
|
134
|
+
`requestTimeout`, and `buildId` stay at the top level. If one of those reserved
|
|
135
|
+
SDK options is accidentally placed inside `properties`, Vizzly removes it from
|
|
136
|
+
metadata, applies it as an option when possible, and warns so the call site can
|
|
137
|
+
be cleaned up.
|
|
138
|
+
|
|
113
139
|
The client SDK is lightweight. It posts screenshots to the local Vizzly server
|
|
114
140
|
or the cloud build wrapper. It works with any test runner.
|
|
115
141
|
|
|
@@ -129,9 +155,26 @@ await vizzlyScreenshot('homepage', './screenshots/homepage.png');
|
|
|
129
155
|
Or upload an existing folder of screenshots:
|
|
130
156
|
|
|
131
157
|
```bash
|
|
132
|
-
vizzly upload ./screenshots
|
|
158
|
+
vizzly upload ./screenshots --threshold 2 --min-cluster-size 4 --batch-size 10 --upload-timeout 60000
|
|
133
159
|
```
|
|
134
160
|
|
|
161
|
+
`--batch-size` controls how many screenshots are uploaded per request.
|
|
162
|
+
`--upload-timeout` controls the upload client's timeout, including how long
|
|
163
|
+
`--wait` polls for build processing.
|
|
164
|
+
|
|
165
|
+
CI workflows can force every screenshot through even when the SHA cache says it
|
|
166
|
+
already uploaded. Parallel jobs should use the same stable ID, then finalize
|
|
167
|
+
that ID after every shard completes:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
vizzly run "pnpm test" --upload-all --parallel-id "$GITHUB_RUN_ID"
|
|
171
|
+
vizzly finalize "$GITHUB_RUN_ID"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For smoke jobs where cloud credentials are intentionally unavailable, add
|
|
175
|
+
`--allow-no-token` to `vizzly run` and Vizzly will keep the local screenshot
|
|
176
|
+
server path working without creating a cloud build.
|
|
177
|
+
|
|
135
178
|
## Configuration
|
|
136
179
|
|
|
137
180
|
Generate a config file:
|
|
@@ -140,6 +183,13 @@ Generate a config file:
|
|
|
140
183
|
vizzly init
|
|
141
184
|
```
|
|
142
185
|
|
|
186
|
+
To teach project agents about Vizzly screenshot memory and the local visual TDD
|
|
187
|
+
loop, add the repo-local skill and AGENTS.md guidance:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
vizzly init --agent-guidance
|
|
191
|
+
```
|
|
192
|
+
|
|
143
193
|
Or create `vizzly.config.js` manually:
|
|
144
194
|
|
|
145
195
|
```javascript
|
package/dist/api/core.js
CHANGED
|
@@ -172,7 +172,8 @@ export function buildScreenshotCheckObject(sha256, name, metadata = {}) {
|
|
|
172
172
|
name,
|
|
173
173
|
browser: meta.browser || 'chrome',
|
|
174
174
|
viewport_width: meta.viewport?.width || meta.viewport_width || 1920,
|
|
175
|
-
viewport_height: meta.viewport?.height || meta.viewport_height || 1080
|
|
175
|
+
viewport_height: meta.viewport?.height || meta.viewport_height || 1080,
|
|
176
|
+
properties: meta
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
|
package/dist/cli.js
CHANGED
|
@@ -207,6 +207,7 @@ const formatHelp = (cmd, helper) => {
|
|
|
207
207
|
};
|
|
208
208
|
function extractGlobalOptionsFromArgv(argv, commandNames = null) {
|
|
209
209
|
let configPath = null;
|
|
210
|
+
let tokenArg = null;
|
|
210
211
|
let verboseMode = false;
|
|
211
212
|
let logLevelArg = null;
|
|
212
213
|
let jsonArg = null;
|
|
@@ -214,6 +215,11 @@ function extractGlobalOptionsFromArgv(argv, commandNames = null) {
|
|
|
214
215
|
if ((argv[i] === '-c' || argv[i] === '--config') && argv[i + 1]) {
|
|
215
216
|
configPath = argv[i + 1];
|
|
216
217
|
}
|
|
218
|
+
if (argv[i] === '--token' && argv[i + 1]) {
|
|
219
|
+
tokenArg = argv[i + 1];
|
|
220
|
+
} else if (argv[i].startsWith('--token=')) {
|
|
221
|
+
tokenArg = argv[i].substring('--token='.length);
|
|
222
|
+
}
|
|
217
223
|
if (argv[i] === '-v' || argv[i] === '--verbose') {
|
|
218
224
|
verboseMode = true;
|
|
219
225
|
}
|
|
@@ -234,6 +240,7 @@ function extractGlobalOptionsFromArgv(argv, commandNames = null) {
|
|
|
234
240
|
}
|
|
235
241
|
return {
|
|
236
242
|
configPath,
|
|
243
|
+
tokenArg,
|
|
237
244
|
verboseMode,
|
|
238
245
|
logLevelArg,
|
|
239
246
|
jsonArg
|
|
@@ -260,6 +267,7 @@ program.name('vizzly').description('Vizzly CLI for visual regression testing').v
|
|
|
260
267
|
// We need to manually parse to get the config option early
|
|
261
268
|
let {
|
|
262
269
|
configPath,
|
|
270
|
+
tokenArg,
|
|
263
271
|
verboseMode,
|
|
264
272
|
logLevelArg,
|
|
265
273
|
jsonArg
|
|
@@ -280,9 +288,11 @@ output.configure({
|
|
|
280
288
|
color: colorOverride,
|
|
281
289
|
json: jsonArg
|
|
282
290
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
291
|
+
let config = await loadConfig(configPath, {
|
|
292
|
+
token: tokenArg
|
|
293
|
+
});
|
|
294
|
+
let services = createServices(config);
|
|
295
|
+
let pluginServices = createPluginServices(services);
|
|
286
296
|
let plugins = [];
|
|
287
297
|
try {
|
|
288
298
|
plugins = await loadPlugins(configPath, config);
|
|
@@ -303,16 +313,16 @@ try {
|
|
|
303
313
|
} catch (error) {
|
|
304
314
|
output.debug(`Plugin loading failed: ${error.message}`);
|
|
305
315
|
}
|
|
306
|
-
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').action(async options => {
|
|
307
|
-
|
|
316
|
+
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install the repo-local Vizzly agent skill').option('--agent-guidance', 'Add Vizzly guidance to this project AGENTS.md and install the agent skill').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
|
|
317
|
+
let globalOptions = program.opts();
|
|
308
318
|
await init({
|
|
309
319
|
...globalOptions,
|
|
310
320
|
...options,
|
|
311
321
|
plugins
|
|
312
322
|
});
|
|
313
323
|
});
|
|
314
|
-
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory
|
|
315
|
-
|
|
324
|
+
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', Number).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', Number).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', Number).option('--min-cluster-size <pixels>', 'Minimum changed-pixel cluster size', Number).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (path, options) => {
|
|
325
|
+
let globalOptions = program.opts();
|
|
316
326
|
|
|
317
327
|
// Validate options
|
|
318
328
|
const validationErrors = validateUploadOptions(path, options);
|
|
@@ -323,7 +333,10 @@ program.command('upload').description('Upload screenshots to Vizzly').argument('
|
|
|
323
333
|
}
|
|
324
334
|
process.exit(1);
|
|
325
335
|
}
|
|
326
|
-
await uploadCommand(path, options, globalOptions);
|
|
336
|
+
let result = await uploadCommand(path, options, globalOptions);
|
|
337
|
+
if (result?.exitCode) {
|
|
338
|
+
process.exit(result.exitCode);
|
|
339
|
+
}
|
|
327
340
|
});
|
|
328
341
|
|
|
329
342
|
// TDD command with subcommands - local visual review with an interactive dashboard
|
package/dist/client/index.js
CHANGED
|
@@ -8,22 +8,27 @@ import { request as httpRequest } from 'node:http';
|
|
|
8
8
|
import { request as httpsRequest } from 'node:https';
|
|
9
9
|
import { dirname, join, parse } from 'node:path';
|
|
10
10
|
import { getBuildId, getServerUrl, isTddMode, setVizzlyEnabled } from '../utils/environment-config.js';
|
|
11
|
-
import {
|
|
11
|
+
import { normalizeScreenshotOptions } from '../utils/screenshot-options.js';
|
|
12
12
|
|
|
13
13
|
// Internal client state
|
|
14
14
|
let currentClient = null;
|
|
15
|
+
let currentServerUrl = null;
|
|
15
16
|
let isDisabled = false;
|
|
17
|
+
let currentFailOnDiff = false;
|
|
16
18
|
|
|
17
19
|
// Default timeout for screenshot requests (30 seconds)
|
|
18
20
|
let DEFAULT_TIMEOUT_MS = 30000;
|
|
19
21
|
|
|
20
22
|
// Log levels for client SDK output control
|
|
21
|
-
export let LOG_LEVELS = {
|
|
23
|
+
export let LOG_LEVELS = Object.freeze({
|
|
22
24
|
debug: 0,
|
|
23
25
|
info: 1,
|
|
24
26
|
warn: 2,
|
|
25
27
|
error: 3
|
|
26
|
-
};
|
|
28
|
+
});
|
|
29
|
+
function getEnvFailOnDiff(env = process.env) {
|
|
30
|
+
return env.VIZZLY_FAIL_ON_DIFF === 'true' || env.VIZZLY_FAIL_ON_DIFF === '1';
|
|
31
|
+
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
34
|
* Check if client should log at the given level
|
|
@@ -56,6 +61,7 @@ function isVizzlyDisabled() {
|
|
|
56
61
|
function disableVizzly() {
|
|
57
62
|
isDisabled = true;
|
|
58
63
|
currentClient = null;
|
|
64
|
+
currentServerUrl = null;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/**
|
|
@@ -67,7 +73,8 @@ function disableVizzly() {
|
|
|
67
73
|
export function autoDiscoverTddServer(startDir, deps = {}) {
|
|
68
74
|
let {
|
|
69
75
|
exists = existsSync,
|
|
70
|
-
readFile = readFileSync
|
|
76
|
+
readFile = readFileSync,
|
|
77
|
+
env = process.env
|
|
71
78
|
} = deps;
|
|
72
79
|
try {
|
|
73
80
|
// Look for .vizzly/server.json in current directory and parent directories
|
|
@@ -80,6 +87,7 @@ export function autoDiscoverTddServer(startDir, deps = {}) {
|
|
|
80
87
|
let serverInfo = JSON.parse(readFile(serverJsonPath, 'utf8'));
|
|
81
88
|
if (serverInfo.port) {
|
|
82
89
|
let url = `http://localhost:${serverInfo.port}`;
|
|
90
|
+
currentFailOnDiff = getEnvFailOnDiff(env) || Boolean(serverInfo.failOnDiff);
|
|
83
91
|
return url;
|
|
84
92
|
}
|
|
85
93
|
} catch {
|
|
@@ -104,6 +112,7 @@ function getClient() {
|
|
|
104
112
|
}
|
|
105
113
|
if (!currentClient) {
|
|
106
114
|
let serverUrl = getServerUrl();
|
|
115
|
+
currentFailOnDiff = getEnvFailOnDiff();
|
|
107
116
|
|
|
108
117
|
// Auto-detect local TDD server and enable Vizzly if TDD server is found
|
|
109
118
|
if (!serverUrl) {
|
|
@@ -116,7 +125,10 @@ function getClient() {
|
|
|
116
125
|
|
|
117
126
|
// If we have a server URL, create the client (regardless of initial enabled state)
|
|
118
127
|
if (serverUrl) {
|
|
119
|
-
|
|
128
|
+
currentServerUrl = serverUrl;
|
|
129
|
+
currentClient = createSimpleClient(serverUrl, {
|
|
130
|
+
failOnDiff: currentFailOnDiff
|
|
131
|
+
});
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
return currentClient;
|
|
@@ -183,28 +195,38 @@ function httpPost(url, body, timeoutMs) {
|
|
|
183
195
|
* Create a simple HTTP client for screenshots
|
|
184
196
|
* @private
|
|
185
197
|
*/
|
|
186
|
-
function createSimpleClient(serverUrl) {
|
|
198
|
+
function createSimpleClient(serverUrl, clientOptions = {}) {
|
|
199
|
+
let {
|
|
200
|
+
failOnDiff = false
|
|
201
|
+
} = clientOptions;
|
|
187
202
|
return {
|
|
188
203
|
async screenshot(name, imageBuffer, options = {}) {
|
|
204
|
+
let normalizedOptions = normalizeScreenshotOptions(options);
|
|
205
|
+
let requestTimeout = normalizedOptions.requestTimeout || DEFAULT_TIMEOUT_MS;
|
|
206
|
+
for (let warning of normalizedOptions.warnings) {
|
|
207
|
+
console.warn(`[vizzly] ${warning.message}`);
|
|
208
|
+
}
|
|
189
209
|
try {
|
|
190
210
|
// If it's a string, assume it's a file path and send directly
|
|
191
211
|
// Otherwise it's a Buffer, so convert to base64
|
|
192
212
|
let isFilePath = typeof imageBuffer === 'string';
|
|
193
213
|
let image = isFilePath ? imageBuffer : imageBuffer.toString('base64');
|
|
194
214
|
let type = isFilePath ? 'file-path' : 'base64';
|
|
195
|
-
let
|
|
196
|
-
|
|
215
|
+
let screenshotData = {
|
|
216
|
+
buildId: normalizedOptions.buildId ?? getBuildId(),
|
|
217
|
+
name,
|
|
218
|
+
image,
|
|
219
|
+
type,
|
|
220
|
+
properties: normalizedOptions.properties
|
|
221
|
+
};
|
|
222
|
+
if (normalizedOptions.warnings.length > 0) {
|
|
223
|
+
screenshotData.warnings = normalizedOptions.warnings;
|
|
224
|
+
}
|
|
197
225
|
let httpStart = Date.now();
|
|
198
226
|
let {
|
|
199
227
|
status,
|
|
200
228
|
json
|
|
201
|
-
} = await httpPost(`${serverUrl}/screenshot`,
|
|
202
|
-
buildId: getBuildId(),
|
|
203
|
-
name,
|
|
204
|
-
image,
|
|
205
|
-
type,
|
|
206
|
-
properties
|
|
207
|
-
}, requestTimeout);
|
|
229
|
+
} = await httpPost(`${serverUrl}/screenshot`, screenshotData, requestTimeout);
|
|
208
230
|
let httpMs = Date.now() - httpStart;
|
|
209
231
|
if (shouldLogClient('debug')) {
|
|
210
232
|
console.debug(`[vizzly-client] ${name} HTTP completed in ${httpMs}ms`);
|
|
@@ -215,6 +237,9 @@ function createSimpleClient(serverUrl) {
|
|
|
215
237
|
// The summary will show all failures at the end
|
|
216
238
|
if (status === 422 && json.tddMode && json.comparison) {
|
|
217
239
|
let comp = json.comparison;
|
|
240
|
+
if (failOnDiff) {
|
|
241
|
+
throw new Error(`Visual diff detected for "${comp.name}" (${comp.diffPercentage ?? 0}% difference)`);
|
|
242
|
+
}
|
|
218
243
|
|
|
219
244
|
// Return success so test continues and captures remaining screenshots
|
|
220
245
|
// Visual diff details will be shown in the summary after tests complete
|
|
@@ -227,12 +252,15 @@ function createSimpleClient(serverUrl) {
|
|
|
227
252
|
}
|
|
228
253
|
throw new Error(`Screenshot failed: ${status} - ${json.error || 'Unknown error'}`);
|
|
229
254
|
}
|
|
255
|
+
if (failOnDiff && json?.tddMode && ['diff', 'failed'].includes(json.status)) {
|
|
256
|
+
throw new Error(`Visual diff detected for "${json.name || name}" (${json.diffPercentage ?? 0}% difference)`);
|
|
257
|
+
}
|
|
230
258
|
return json;
|
|
231
259
|
} catch (error) {
|
|
232
260
|
// Handle timeout
|
|
233
261
|
if (error.message === 'Request timeout') {
|
|
234
262
|
if (shouldLogClient('error')) {
|
|
235
|
-
console.error(`[vizzly] Screenshot timed out for "${name}" after ${
|
|
263
|
+
console.error(`[vizzly] Screenshot timed out for "${name}" after ${requestTimeout / 1000}s`);
|
|
236
264
|
}
|
|
237
265
|
disableVizzly();
|
|
238
266
|
return null;
|
|
@@ -308,7 +336,7 @@ function createSimpleClient(serverUrl) {
|
|
|
308
336
|
* await vizzlyScreenshot('checkout-form', screenshot, {
|
|
309
337
|
* properties: {
|
|
310
338
|
* browser: 'chrome',
|
|
311
|
-
* viewport:
|
|
339
|
+
* viewport: { width: 1920, height: 1080 }
|
|
312
340
|
* },
|
|
313
341
|
* threshold: 5
|
|
314
342
|
* });
|
|
@@ -366,8 +394,20 @@ export function isVizzlyReady() {
|
|
|
366
394
|
* @param {boolean} [config.enabled] - Enable/disable screenshots
|
|
367
395
|
*/
|
|
368
396
|
export function configure(config = {}) {
|
|
397
|
+
if ('failOnDiff' in config) {
|
|
398
|
+
currentFailOnDiff = config.failOnDiff === true;
|
|
399
|
+
} else if ('serverUrl' in config) {
|
|
400
|
+
currentFailOnDiff = getEnvFailOnDiff();
|
|
401
|
+
}
|
|
369
402
|
if ('serverUrl' in config) {
|
|
370
|
-
|
|
403
|
+
currentServerUrl = config.serverUrl || null;
|
|
404
|
+
currentClient = config.serverUrl ? createSimpleClient(config.serverUrl, {
|
|
405
|
+
failOnDiff: currentFailOnDiff
|
|
406
|
+
}) : null;
|
|
407
|
+
} else if ('failOnDiff' in config && currentClient && currentServerUrl) {
|
|
408
|
+
currentClient = createSimpleClient(currentServerUrl, {
|
|
409
|
+
failOnDiff: currentFailOnDiff
|
|
410
|
+
});
|
|
371
411
|
}
|
|
372
412
|
if (typeof config.enabled === 'boolean') {
|
|
373
413
|
setVizzlyEnabled(config.enabled);
|
|
@@ -397,10 +437,11 @@ export function getVizzlyInfo() {
|
|
|
397
437
|
let client = getClient();
|
|
398
438
|
return {
|
|
399
439
|
enabled: !isVizzlyDisabled(),
|
|
400
|
-
serverUrl: getServerUrl(),
|
|
440
|
+
serverUrl: currentServerUrl || getServerUrl() || null,
|
|
401
441
|
ready: !isVizzlyDisabled() && client !== null,
|
|
402
|
-
buildId: getBuildId(),
|
|
442
|
+
buildId: getBuildId() || null,
|
|
403
443
|
tddMode: isTddMode(),
|
|
404
|
-
disabled: isVizzlyDisabled()
|
|
444
|
+
disabled: isVizzlyDisabled(),
|
|
445
|
+
failOnDiff: currentFailOnDiff
|
|
405
446
|
};
|
|
406
447
|
}
|
package/dist/commands/api.js
CHANGED
|
@@ -65,7 +65,8 @@ export function appendApiQuery(endpoint, queryOption) {
|
|
|
65
65
|
}
|
|
66
66
|
export function validateApiRequest({
|
|
67
67
|
endpoint,
|
|
68
|
-
method
|
|
68
|
+
method,
|
|
69
|
+
hasData = false
|
|
69
70
|
}) {
|
|
70
71
|
let errors = [];
|
|
71
72
|
if (method !== 'GET' && method !== 'POST') {
|
|
@@ -75,6 +76,9 @@ export function validateApiRequest({
|
|
|
75
76
|
if (method === 'POST' && !isAllowedPostEndpoint(endpoint)) {
|
|
76
77
|
errors.push(`POST not allowed for ${endpoint}. Only approve, reject, and comment endpoints support POST.`);
|
|
77
78
|
}
|
|
79
|
+
if (hasData && method !== 'POST') {
|
|
80
|
+
errors.push('Request data requires --method POST.');
|
|
81
|
+
}
|
|
78
82
|
return errors;
|
|
79
83
|
}
|
|
80
84
|
export function buildApiRequest({
|
|
@@ -85,7 +89,8 @@ export function buildApiRequest({
|
|
|
85
89
|
let method = normalizeApiMethod(options.method || 'GET');
|
|
86
90
|
let errors = validateApiRequest({
|
|
87
91
|
endpoint: normalizedEndpoint,
|
|
88
|
-
method
|
|
92
|
+
method,
|
|
93
|
+
hasData: options.data !== undefined
|
|
89
94
|
});
|
|
90
95
|
if (errors.length > 0) {
|
|
91
96
|
return {
|
|
@@ -239,7 +244,8 @@ export function validateApiOptions(endpoint, options = {}) {
|
|
|
239
244
|
let method = normalizeApiMethod(options.method || 'GET');
|
|
240
245
|
errors.push(...validateApiRequest({
|
|
241
246
|
endpoint: normalizedEndpoint,
|
|
242
|
-
method
|
|
247
|
+
method,
|
|
248
|
+
hasData: options.data !== undefined
|
|
243
249
|
}));
|
|
244
250
|
return errors;
|
|
245
251
|
}
|