@vizzly-testing/cli 0.38.0 → 0.39.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 +222 -183
- package/dist/auth/client.js +4 -1
- package/dist/auth/core.js +64 -3
- package/dist/auth/operations.js +21 -11
- package/dist/cli.js +45 -53
- package/dist/commands/init.js +156 -483
- package/dist/commands/login.js +71 -137
- package/dist/commands/project.js +128 -44
- package/dist/commands/run.js +2 -2
- package/dist/commands/tdd.js +89 -38
- package/dist/commands/upload.js +2 -2
- package/dist/init/agents.js +99 -0
- package/dist/init/core.js +31 -0
- package/dist/init/prompts.js +44 -0
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +31 -127
- package/dist/server/handlers/tdd-handler.js +33 -2
- package/dist/server/routers/auth.js +32 -42
- package/dist/services/index.js +8 -2
- package/dist/tdd/services/comparison-service.js +4 -1
- package/dist/tdd/tdd-service.js +14 -2
- package/dist/test-runner/core.js +28 -3
- package/dist/test-runner/operations.js +41 -20
- package/dist/types/index.d.ts +8 -0
- package/dist/uploader/core.js +1 -0
- package/dist/utils/browser.js +7 -1
- package/dist/utils/config-discovery.js +73 -0
- package/dist/utils/config-loader.js +29 -8
- package/dist/utils/config-schema.js +5 -0
- package/dist/utils/file-changes.js +105 -0
- package/dist/utils/login-poll-wait.js +50 -0
- package/dist/utils/project-binding.js +96 -0
- package/dist/utils/project-link-store.js +13 -7
- package/package.json +27 -27
- package/skills/vizzly/SKILL.md +4 -3
- package/skills/vizzly/references/ci.md +33 -0
- package/skills/vizzly/references/cli-context.md +81 -71
- package/skills/vizzly/references/dynamic-content.md +16 -17
- package/skills/vizzly/references/sdks.md +22 -22
- package/skills/vizzly/references/setup-ci.md +0 -44
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vizzly CLI
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Reviewed UI context for coding agents and teams
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@vizzly-testing/cli)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -9,259 +9,298 @@
|
|
|
9
9
|
<img src="./docs/assets/vizzly-mascot-inspector.png" alt="Vizzly bear inspecting a screenshot" width="220" />
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
Vizzly
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
status, and preview links.
|
|
12
|
+
Vizzly helps your team and coding agents see what the UI should look like,
|
|
13
|
+
what changed, and what was approved. It keeps screenshots, diffs, and review
|
|
14
|
+
decisions together so you can use that history the next time you change the UI.
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
upload screenshots from CI, generate static reports, and fetch machine-readable
|
|
19
|
-
build or diff data for scripts, automation, and coding agents.
|
|
16
|
+
## Get started
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
You need Node.js 22+.
|
|
18
|
+
You need Node.js 22.18+ on Node 22, or Node.js 24+. From your test project:
|
|
24
19
|
|
|
25
20
|
```bash
|
|
26
|
-
pnpm
|
|
27
|
-
|
|
28
|
-
vizzly init
|
|
21
|
+
pnpm add --save-dev @vizzly-testing/cli
|
|
22
|
+
pnpm exec vizzly init
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
The wizard creates a config and offers agent guidance and cloud linking.
|
|
26
|
+
It previews file changes before applying them. If Vizzly is already configured,
|
|
27
|
+
init leaves it alone. Edit the config directly to change your setup.
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
29
|
+
To create a small config and edit it yourself, use `pnpm exec vizzly init --quick`.
|
|
30
|
+
It keeps existing config and doesn't install dependencies or run tests.
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
CLI upgrade to refresh both without replacing the rest of `AGENTS.md` or an
|
|
41
|
-
existing Vizzly config.
|
|
32
|
+
Choose a Vizzly client for your test setup, then add screenshot capture.
|
|
33
|
+
This example uses Playwright with the Node.js client:
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
```javascript
|
|
36
|
+
import { test } from '@playwright/test';
|
|
37
|
+
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
test('homepage looks correct', async ({ page }) => {
|
|
40
|
+
await page.goto('/');
|
|
41
|
+
await vizzlyScreenshot('homepage', await page.screenshot());
|
|
42
|
+
});
|
|
43
|
+
```
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
you. If the default port is busy, Vizzly picks the next available port; use the
|
|
51
|
-
printed `--port` value with `vizzly tdd status` or `vizzly tdd stop`.
|
|
45
|
+
The client runs in Node.js and works with any Node-based test runner. You can
|
|
46
|
+
also pass a file path:
|
|
52
47
|
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
pnpm test -- --watch
|
|
48
|
+
```javascript
|
|
49
|
+
await vizzlyScreenshot('homepage', './screenshots/homepage.png');
|
|
56
50
|
```
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
reject changes right from the UI.
|
|
52
|
+
### Review locally
|
|
60
53
|
|
|
61
|
-
|
|
54
|
+
Start the local TDD server, then run your tests in another terminal:
|
|
62
55
|
|
|
63
56
|
```bash
|
|
64
|
-
vizzly tdd
|
|
65
|
-
|
|
57
|
+
pnpm exec vizzly tdd start --open
|
|
58
|
+
pnpm test -- --watch # Replace this with your test command
|
|
66
59
|
```
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
The dashboard shows screenshots and diffs as they arrive. Accept or reject
|
|
62
|
+
changes there. If the default port is busy, use the printed port with
|
|
63
|
+
`pnpm exec vizzly tdd status --port <port>` or
|
|
64
|
+
`pnpm exec vizzly tdd stop --port <port>`.
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Use cloud builds when you want shared baselines, team review, and CI status:
|
|
66
|
+
For a single run:
|
|
76
67
|
|
|
77
68
|
```bash
|
|
78
|
-
vizzly
|
|
79
|
-
vizzly project link your-org/your-project
|
|
80
|
-
vizzly run "pnpm test" --wait
|
|
69
|
+
pnpm exec vizzly tdd run "pnpm test" --no-open
|
|
81
70
|
```
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`--wait` blocks until Vizzly finishes processing the build. It exits with code
|
|
88
|
-
`1` when visual differences need review.
|
|
72
|
+
Review data goes in `.vizzly/`. When the run captures screenshots, it also
|
|
73
|
+
creates `.vizzly/report/index.html`. Omit `--no-open` to open the report.
|
|
74
|
+
If the configured port is busy, `tdd run` picks an available one. An explicit
|
|
75
|
+
`--port` uses only that port.
|
|
89
76
|
|
|
90
|
-
|
|
77
|
+
### Review in the cloud
|
|
91
78
|
|
|
92
79
|
```bash
|
|
93
|
-
|
|
94
|
-
vizzly
|
|
80
|
+
pnpm exec vizzly login
|
|
81
|
+
pnpm exec vizzly project link your-org/your-project
|
|
82
|
+
pnpm exec vizzly run "pnpm test"
|
|
95
83
|
```
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
reviews, agents should discover and call the public API through its schema:
|
|
85
|
+
`vizzly login` signs you in. Linking saves the project name in your config and
|
|
86
|
+
keeps the upload token in user storage. Run it again to reuse a saved token.
|
|
87
|
+
Replace `"pnpm test"` with your test command.
|
|
101
88
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```
|
|
89
|
+
If you linked with an older CLI, run `project link` once in each repository to
|
|
90
|
+
save its project selection. Handwritten config may need a manual edit; the
|
|
91
|
+
command prints what to add. Explicit `--token` and `VIZZLY_TOKEN` still work.
|
|
106
92
|
|
|
107
|
-
|
|
108
|
-
deprecated and scheduled for removal in v0.38.0. Use the schema-driven API for
|
|
109
|
-
cloud evidence; use `vizzly context ... --json` for local evidence.
|
|
93
|
+
To upload screenshots you already have:
|
|
110
94
|
|
|
111
95
|
```bash
|
|
112
|
-
|
|
113
|
-
vizzly context build abc123 --source cloud
|
|
114
|
-
|
|
115
|
-
# Discover cloud evidence operations for an agent workflow
|
|
116
|
-
vizzly api schema sdk.getBuildContext --json
|
|
117
|
-
vizzly api schema getComparisonContext --json
|
|
118
|
-
|
|
119
|
-
# Local workspace context from .vizzly/
|
|
120
|
-
vizzly context build current --source local
|
|
121
|
-
vizzly context build current --source local --json
|
|
122
|
-
vizzly context screenshot build-detail-screenshots --source local --json
|
|
123
|
-
vizzly context review-queue --source local --json
|
|
96
|
+
pnpm exec vizzly upload ./screenshots
|
|
124
97
|
```
|
|
125
98
|
|
|
126
|
-
|
|
127
|
-
pagination before making requests. The old compact `--agent` handoff remains
|
|
128
|
-
available during deprecation, but new cloud workflows should use the schema.
|
|
129
|
-
Use `--json` for machine-readable local context.
|
|
99
|
+
### Run in CI
|
|
130
100
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
or region metadata.
|
|
101
|
+
Store a project token as a CI secret and pass it as `VIZZLY_TOKEN`. Add `--wait`
|
|
102
|
+
when you want CI to wait for comparison results and fail on visual differences:
|
|
134
103
|
|
|
135
|
-
|
|
136
|
-
|
|
104
|
+
```bash
|
|
105
|
+
export VIZZLY_TOKEN=your-project-token
|
|
106
|
+
pnpm exec vizzly run "pnpm test" --wait
|
|
107
|
+
```
|
|
137
108
|
|
|
138
|
-
|
|
109
|
+
With `--wait`, failed comparisons make the command exit with code `1`.
|
|
139
110
|
|
|
140
|
-
|
|
111
|
+
## Screenshot options
|
|
141
112
|
|
|
142
113
|
```javascript
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
fullPage: true,
|
|
151
|
-
requestTimeout: 5000,
|
|
152
|
-
properties: {
|
|
153
|
-
theme: 'dark',
|
|
154
|
-
locale: 'en-US',
|
|
155
|
-
},
|
|
156
|
-
});
|
|
114
|
+
let screenshot = await page.screenshot({ fullPage: true });
|
|
115
|
+
await vizzlyScreenshot('checkout', screenshot, {
|
|
116
|
+
threshold: 2,
|
|
117
|
+
minClusterSize: 4,
|
|
118
|
+
fullPage: true,
|
|
119
|
+
requestTimeout: 5000,
|
|
120
|
+
properties: { theme: 'dark', locale: 'en-US' },
|
|
157
121
|
});
|
|
158
122
|
```
|
|
159
123
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
124
|
+
Put details like theme or locale in `properties` for baseline grouping and
|
|
125
|
+
filtering. Vizzly keeps these separate from screenshot options and reads image
|
|
126
|
+
dimensions from the screenshot. For local baseline matching, list custom keys
|
|
127
|
+
in `signatureProperties`. For cloud builds, set matching properties in your
|
|
128
|
+
Vizzly project settings. Common top-level options are
|
|
163
129
|
`threshold`, `minClusterSize`, `fullPage`, `requestTimeout`, and `buildId`.
|
|
164
|
-
Vizzly reads width and height from the captured image, so viewport dimensions do
|
|
165
|
-
not need to be included in `properties`.
|
|
166
130
|
|
|
167
|
-
|
|
168
|
-
|
|
131
|
+
For setup help, see the [Storybook](clients/storybook/README.md),
|
|
132
|
+
[static site](clients/static-site/README.md), [Vitest](clients/vitest/README.md),
|
|
133
|
+
and [Ember](clients/ember/README.md) guides. There are also
|
|
134
|
+
[Ruby](clients/ruby/README.md) and [Swift](clients/swift/README.md) SDKs.
|
|
169
135
|
|
|
170
|
-
|
|
171
|
-
[JavaScript](https://docs.vizzly.dev/integration/sdk/javascript),
|
|
172
|
-
[Ruby](https://docs.vizzly.dev/integration/sdk/ruby),
|
|
173
|
-
[Swift](https://docs.vizzly.dev/integration/sdk/swift), and
|
|
174
|
-
[more](https://docs.vizzly.dev/integration/sdk/overview).
|
|
136
|
+
## Configuration
|
|
175
137
|
|
|
176
|
-
|
|
138
|
+
`vizzly init --quick` creates a config with commented examples. Put settings
|
|
139
|
+
there when you want to change a default, or write `vizzly.config.js` yourself:
|
|
177
140
|
|
|
178
141
|
```javascript
|
|
179
|
-
|
|
180
|
-
|
|
142
|
+
export default {
|
|
143
|
+
comparison: {
|
|
144
|
+
threshold: 2, // CIEDE2000 Delta E color tolerance.
|
|
145
|
+
},
|
|
146
|
+
};
|
|
181
147
|
```
|
|
182
148
|
|
|
183
|
-
|
|
149
|
+
Cloud comparisons use your project's settings unless you override them in
|
|
150
|
+
config, environment variables, or command options. Local comparisons use the
|
|
151
|
+
CLI defaults.
|
|
152
|
+
|
|
153
|
+
Existing config is kept, including TypeScript, rc files, and `package.json`.
|
|
154
|
+
Use `--config <path>` to choose a config or a new destination. `--force`
|
|
155
|
+
replaces the selected config; in `package.json`, it replaces only `vizzly`.
|
|
156
|
+
|
|
157
|
+
For TypeScript, use `vizzly.config.ts`. Node strips the types, so you don't need
|
|
158
|
+
a separate compiler. Use `import type` for types and include extensions in local
|
|
159
|
+
imports, like `./helpers.ts`. Enums, namespaces with runtime code, constructor
|
|
160
|
+
parameter properties, and `tsconfig.json` path aliases won't work in the config
|
|
161
|
+
or files it imports.
|
|
162
|
+
|
|
163
|
+
### Coding agents and scripts
|
|
164
|
+
|
|
165
|
+
Install the Vizzly skill and a short `AGENTS.md` note:
|
|
184
166
|
|
|
185
167
|
```bash
|
|
186
|
-
|
|
168
|
+
pnpm exec vizzly init --agent-guidance
|
|
187
169
|
```
|
|
188
170
|
|
|
189
|
-
|
|
190
|
-
`--
|
|
191
|
-
|
|
171
|
+
This adds `.agents/skills/vizzly` and a note in `AGENTS.md`, keeping other
|
|
172
|
+
guidance. Use `--agent-skill` to install only the skill during setup.
|
|
173
|
+
These flags don't open the wizard or update an existing Vizzly setup.
|
|
174
|
+
`--skip-agent-skill` skips agent setup unless you also request `--agent-guidance`.
|
|
192
175
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
creates the screenshot record without re-uploading the image. CI workflows can
|
|
196
|
-
force every screenshot through with `--upload-all`. Parallel jobs should use the
|
|
197
|
-
same stable ID, then finalize that ID after every shard completes:
|
|
176
|
+
Use `context` to read local results. For cloud results, check the API schema
|
|
177
|
+
to find the available requests:
|
|
198
178
|
|
|
199
179
|
```bash
|
|
200
|
-
vizzly
|
|
201
|
-
vizzly
|
|
180
|
+
pnpm exec vizzly context build current --source local --json
|
|
181
|
+
pnpm exec vizzly context review-queue --source local --json
|
|
182
|
+
pnpm exec vizzly api schema --json
|
|
183
|
+
pnpm exec vizzly api schema sdk.getBuildContext --json
|
|
202
184
|
```
|
|
203
185
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
186
|
+
The schema lists inputs, output fields, and how to fetch more results. The old
|
|
187
|
+
`context build --agent` and `context comparison --agent` formats are deprecated.
|
|
188
|
+
Use the API schema for new cloud workflows and `context --json` for local data.
|
|
189
|
+
See the [JSON reference](docs/json-output.md) for output formats and scripts.
|
|
207
190
|
|
|
208
|
-
|
|
191
|
+
### Parallel CI and uploads
|
|
209
192
|
|
|
210
|
-
|
|
193
|
+
Vizzly reuses images it already has by SHA. Add `--upload-all` to send every
|
|
194
|
+
image. For parallel jobs, use the same ID for every shard and finalize it when
|
|
195
|
+
they all finish:
|
|
211
196
|
|
|
212
197
|
```bash
|
|
213
|
-
vizzly
|
|
198
|
+
pnpm exec vizzly run "pnpm test" --parallel-id "$GITHUB_RUN_ID"
|
|
199
|
+
pnpm exec vizzly finalize "$GITHUB_RUN_ID"
|
|
214
200
|
```
|
|
215
201
|
|
|
216
|
-
|
|
217
|
-
|
|
202
|
+
Use `--batch-size` to set how many screenshots upload per request.
|
|
203
|
+
`--upload-timeout` limits how long `--wait` waits for build processing; it
|
|
204
|
+
doesn't set the HTTP upload timeout. You can also set
|
|
205
|
+
`--threshold` and `--min-cluster-size`. If CI has no cloud token,
|
|
206
|
+
`vizzly run --allow-no-token` lets your test command run without starting Vizzly.
|
|
218
207
|
|
|
219
|
-
|
|
220
|
-
vizzly init --agent-guidance
|
|
221
|
-
```
|
|
208
|
+
## Commands
|
|
222
209
|
|
|
223
|
-
|
|
210
|
+
Use `pnpm exec vizzly --help` to see what's available in your project, or add
|
|
211
|
+
`--help` to a command for its options. These are the built-in commands; run them
|
|
212
|
+
with `pnpm exec` when Vizzly is installed locally.
|
|
224
213
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
comparison: {
|
|
228
|
-
// CIEDE2000 Delta E. 0 is exact. 2 is a good default.
|
|
229
|
-
threshold: 2.0,
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
```
|
|
214
|
+
<details>
|
|
215
|
+
<summary>Setup and accounts</summary>
|
|
233
216
|
|
|
234
|
-
|
|
217
|
+
| Command | What it does |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| `vizzly init` | Walk through setup. Use `--quick` to create config without questions. |
|
|
220
|
+
| `vizzly config [key]` | Show your current config. |
|
|
221
|
+
| `vizzly project link <org>/<project>` | Link this checkout to a cloud project. |
|
|
222
|
+
| `vizzly orgs` | List your organizations. |
|
|
223
|
+
| `vizzly projects` | List projects you can access. |
|
|
224
|
+
| `vizzly login` | Sign in through the browser. |
|
|
225
|
+
| `vizzly logout` | Clear your saved login. |
|
|
226
|
+
| `vizzly whoami` | Check your account and authentication status. |
|
|
227
|
+
| `vizzly doctor` | Check your setup. |
|
|
228
|
+
|
|
229
|
+
</details>
|
|
230
|
+
|
|
231
|
+
<details>
|
|
232
|
+
<summary>Local review</summary>
|
|
235
233
|
|
|
236
234
|
| Command | What it does |
|
|
237
235
|
| --- | --- |
|
|
238
|
-
| `vizzly tdd start` | Start the local
|
|
239
|
-
| `vizzly tdd status` | Check
|
|
240
|
-
| `vizzly tdd list` | List running local
|
|
241
|
-
| `vizzly tdd stop` | Stop
|
|
242
|
-
| `vizzly tdd run "
|
|
243
|
-
| `vizzly
|
|
244
|
-
| `vizzly
|
|
245
|
-
| `vizzly
|
|
246
|
-
| `vizzly
|
|
247
|
-
| `vizzly
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
236
|
+
| `vizzly tdd start` | Start the local server and dashboard. |
|
|
237
|
+
| `vizzly tdd status` | Check this project's local server. |
|
|
238
|
+
| `vizzly tdd list` | List running local servers. |
|
|
239
|
+
| `vizzly tdd stop` | Stop this project's local server. |
|
|
240
|
+
| `vizzly tdd run "command"` | Run tests once and save local review data. |
|
|
241
|
+
| `vizzly tdd screenshots list` | List captures from the latest local run. |
|
|
242
|
+
| `vizzly tdd screenshots latest [name]` | Print the latest capture's image path. |
|
|
243
|
+
| `vizzly tdd screenshots show <id-or-name>` | Show local capture details and the current image path. |
|
|
244
|
+
| `vizzly tdd screenshots accept <id-or-name>` | Save a local capture as its baseline. |
|
|
245
|
+
| `vizzly baselines` | List local baselines, or inspect one with `--info <name>`. |
|
|
246
|
+
|
|
247
|
+
</details>
|
|
248
|
+
|
|
249
|
+
<details>
|
|
250
|
+
<summary>Cloud builds</summary>
|
|
251
|
+
|
|
252
|
+
| Command | What it does |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| `vizzly run "command"` | Run tests and upload for cloud review. |
|
|
255
|
+
| `vizzly upload <dir>` | Upload screenshots. |
|
|
256
|
+
| `vizzly finalize <parallel-id>` | Finish a cloud build after all shards complete. |
|
|
257
|
+
| `vizzly preview <path>` | Upload a static preview for a cloud build; private projects need `--public-link`. |
|
|
258
|
+
| `vizzly status <build-id>` | Check cloud build status. |
|
|
259
|
+
| `vizzly builds` | List builds, or fetch one with `--build <id>`. |
|
|
260
|
+
| `vizzly comparisons --build <build-id>` | List a build's comparisons; also supports `--id` or `--name` queries. |
|
|
261
|
+
|
|
262
|
+
</details>
|
|
263
|
+
|
|
264
|
+
<details>
|
|
265
|
+
<summary>Build and screenshot context</summary>
|
|
266
|
+
|
|
267
|
+
| Command | What it does |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `vizzly context build <build-id>` | Read build context. |
|
|
270
|
+
| `vizzly context comparison <comparison-id>` | Read comparison context. |
|
|
271
|
+
| `vizzly context screenshot <name>` | Read screenshot history. |
|
|
272
|
+
| `vizzly context similar <fingerprint-hash>` | Find cloud comparisons with similar diffs. |
|
|
273
|
+
| `vizzly context review-queue` | Read comparisons waiting for review. |
|
|
274
|
+
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
<details>
|
|
278
|
+
<summary>Review decisions</summary>
|
|
279
|
+
|
|
280
|
+
| Command | What it does |
|
|
281
|
+
| --- | --- |
|
|
282
|
+
| `vizzly approve <comparison-id>` | Approve a comparison. |
|
|
283
|
+
| `vizzly reject <comparison-id> --reason "message"` | Reject a comparison with a reason. |
|
|
284
|
+
| `vizzly comment <build-id> "message"` | Comment on a build. |
|
|
285
|
+
|
|
286
|
+
</details>
|
|
287
|
+
|
|
288
|
+
<details>
|
|
289
|
+
<summary>API</summary>
|
|
290
|
+
|
|
291
|
+
| Command | What it does |
|
|
292
|
+
| --- | --- |
|
|
293
|
+
| `vizzly api schema [operation-id]` | Discover cloud API requests, fields, and response formats. |
|
|
294
|
+
| `vizzly api <endpoint>` | Make API requests; use `--output <file>` for images. |
|
|
295
|
+
|
|
296
|
+
</details>
|
|
297
|
+
|
|
298
|
+
Installed plugins add commands such as `storybook`, `static-site`, and `previews`.
|
|
299
|
+
See their setup guides above for details.
|
|
300
|
+
|
|
301
|
+
## More
|
|
302
|
+
|
|
303
|
+
[Full docs](https://docs.vizzly.dev) ·
|
|
304
|
+
[Report a bug or suggest a change](https://github.com/vizzly-testing/cli/issues)
|
|
305
|
+
|
|
306
|
+
PRs welcome. MIT licensed.
|
package/dist/auth/client.js
CHANGED
|
@@ -56,7 +56,10 @@ export function createAuthClient(options = {}) {
|
|
|
56
56
|
});
|
|
57
57
|
if (!response.ok) {
|
|
58
58
|
let body = await parseResponseBody(response);
|
|
59
|
-
|
|
59
|
+
let error = parseAuthError(response.status, body, endpoint);
|
|
60
|
+
let retryAfter = Number(response.headers?.get('retry-after'));
|
|
61
|
+
if (retryAfter > 0) error.context.retryAfter = retryAfter;
|
|
62
|
+
throw error;
|
|
60
63
|
}
|
|
61
64
|
return response.json();
|
|
62
65
|
}
|
package/dist/auth/core.js
CHANGED
|
@@ -70,13 +70,19 @@ export function parseAuthError(status, body, _endpoint) {
|
|
|
70
70
|
} else if (typeof body === 'string') {
|
|
71
71
|
errorText = body;
|
|
72
72
|
}
|
|
73
|
+
let retryAfter = Number(body?.retryAfter);
|
|
74
|
+
let context = {
|
|
75
|
+
status,
|
|
76
|
+
serverCode: body?.code,
|
|
77
|
+
retryAfter: retryAfter > 0 ? retryAfter : undefined
|
|
78
|
+
};
|
|
73
79
|
if (status === 401) {
|
|
74
|
-
return new AuthError(errorText || 'Invalid credentials. Please check your email/username and password.');
|
|
80
|
+
return new AuthError(errorText || 'Invalid credentials. Please check your email/username and password.', context);
|
|
75
81
|
}
|
|
76
82
|
if (status === 429) {
|
|
77
|
-
return new VizzlyError('Too many login attempts. Please try again later.', 'RATE_LIMIT_ERROR');
|
|
83
|
+
return new VizzlyError('Too many login attempts. Please try again later.', 'RATE_LIMIT_ERROR', context);
|
|
78
84
|
}
|
|
79
|
-
return new VizzlyError(`Authentication request failed: ${status}${errorText ? ` - ${errorText}` : ''}`, 'AUTH_REQUEST_ERROR');
|
|
85
|
+
return new VizzlyError(`Authentication request failed: ${status}${errorText ? ` - ${errorText}` : ''}`, 'AUTH_REQUEST_ERROR', context);
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/**
|
|
@@ -173,4 +179,59 @@ export function validateTokens(auth, requiredField = 'accessToken') {
|
|
|
173
179
|
valid: true,
|
|
174
180
|
error: null
|
|
175
181
|
};
|
|
182
|
+
}
|
|
183
|
+
export function normalizeDeviceFlowResponse(deviceFlow) {
|
|
184
|
+
let verificationUri = deviceFlow.verification_uri || deviceFlow.verificationUri;
|
|
185
|
+
let userCode = deviceFlow.user_code || deviceFlow.userCode;
|
|
186
|
+
let deviceCode = deviceFlow.device_code || deviceFlow.deviceCode;
|
|
187
|
+
if (!verificationUri || !userCode || !deviceCode) {
|
|
188
|
+
throw new Error('Invalid device flow response from server');
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
verificationUri,
|
|
192
|
+
userCode,
|
|
193
|
+
deviceCode,
|
|
194
|
+
urlWithCode: deviceFlow.verification_uri_complete || deviceFlow.verificationUriComplete || `${verificationUri}?code=${encodeURIComponent(userCode)}`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export function resolveAuthorizedTokenData(pollResponse) {
|
|
198
|
+
let accessToken = pollResponse.tokens?.accessToken || pollResponse.tokens?.access_token;
|
|
199
|
+
if (accessToken) {
|
|
200
|
+
return pollResponse;
|
|
201
|
+
}
|
|
202
|
+
if (pollResponse.status === 'pending') {
|
|
203
|
+
throw new Error('Authorization not complete yet. Please complete the authorization in your browser and try running "vizzly login" again.');
|
|
204
|
+
}
|
|
205
|
+
if (pollResponse.status === 'expired') {
|
|
206
|
+
throw new Error('Device code expired. Please try logging in again.');
|
|
207
|
+
}
|
|
208
|
+
if (pollResponse.status === 'invalid') {
|
|
209
|
+
throw new Error('Device code is invalid or expired. Please try logging in again.');
|
|
210
|
+
}
|
|
211
|
+
if (pollResponse.status === 'denied') {
|
|
212
|
+
throw new Error('Authorization denied. Please try logging in again.');
|
|
213
|
+
}
|
|
214
|
+
throw new Error('Unexpected response from authorization server. Please try logging in again.');
|
|
215
|
+
}
|
|
216
|
+
export function buildTokens(tokenData, now = Date.now()) {
|
|
217
|
+
let tokensData = tokenData.tokens || tokenData;
|
|
218
|
+
let tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
|
|
219
|
+
let tokenExpiresAt = tokenExpiresIn ? new Date(now + tokenExpiresIn * 1000).toISOString() : tokenData.expires_at || tokenData.expiresAt;
|
|
220
|
+
return {
|
|
221
|
+
accessToken: tokensData.accessToken || tokensData.access_token,
|
|
222
|
+
refreshToken: tokensData.refreshToken || tokensData.refresh_token,
|
|
223
|
+
expiresAt: tokenExpiresAt,
|
|
224
|
+
user: tokenData.user,
|
|
225
|
+
organizations: tokenData.organizations
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Map protocol codes without depending on the server's human-readable text. */
|
|
230
|
+
export function deviceAuthorizationStatus(error) {
|
|
231
|
+
return {
|
|
232
|
+
AUTHORIZATION_PENDING: 'pending',
|
|
233
|
+
ACCESS_DENIED: 'denied',
|
|
234
|
+
EXPIRED_TOKEN: 'expired',
|
|
235
|
+
INVALID_DEVICE_CODE: 'invalid'
|
|
236
|
+
}[error.context?.serverCode];
|
|
176
237
|
}
|
package/dist/auth/operations.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* This makes them trivially testable without mocking modules.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import * as output from '../utils/output.js';
|
|
12
|
+
import { buildDevicePollPayload, buildLogoutPayload, buildRefreshPayload, buildTokenData, deviceAuthorizationStatus, validateTokens } from './core.js';
|
|
12
13
|
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Device Flow Operations
|
|
@@ -34,14 +35,23 @@ export async function initiateDeviceFlow(httpClient) {
|
|
|
34
35
|
* @param {string} deviceCode - Device code from initiate
|
|
35
36
|
* @returns {Promise<Object>} Token data or pending status
|
|
36
37
|
*/
|
|
37
|
-
export async function pollDeviceAuthorization(httpClient, deviceCode) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
export async function pollDeviceAuthorization(httpClient, deviceCode, options = {}) {
|
|
39
|
+
try {
|
|
40
|
+
return await httpClient.request('/api/auth/cli/device/poll', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(buildDevicePollPayload(deviceCode)),
|
|
46
|
+
signal: options.signal
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
let status = deviceAuthorizationStatus(error);
|
|
50
|
+
if (status) return {
|
|
51
|
+
status
|
|
52
|
+
};
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
/**
|
|
@@ -98,7 +108,7 @@ export async function logout(httpClient, tokenStore) {
|
|
|
98
108
|
let auth = await tokenStore.getTokens();
|
|
99
109
|
if (auth?.refreshToken) {
|
|
100
110
|
try {
|
|
101
|
-
await httpClient.
|
|
111
|
+
await httpClient.authenticatedRequest('/api/auth/cli/logout', auth.accessToken, {
|
|
102
112
|
method: 'POST',
|
|
103
113
|
headers: {
|
|
104
114
|
'Content-Type': 'application/json'
|
|
@@ -107,7 +117,7 @@ export async function logout(httpClient, tokenStore) {
|
|
|
107
117
|
});
|
|
108
118
|
} catch (error) {
|
|
109
119
|
// If server request fails, still clear local tokens
|
|
110
|
-
|
|
120
|
+
output.warn(`Failed to revoke tokens on server: ${error.message}`);
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
await tokenStore.clearTokens();
|