@vizzly-testing/cli 0.38.0 → 0.38.1
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 +14 -1
- package/dist/auth/client.js +4 -1
- package/dist/auth/core.js +64 -3
- package/dist/auth/operations.js +21 -11
- package/dist/commands/login.js +71 -137
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +39 -104
- package/dist/server/routers/auth.js +32 -42
- package/dist/utils/browser.js +7 -1
- package/dist/utils/login-poll-wait.js +50 -0
- package/package.json +27 -27
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Handles authentication endpoints (device flow login, logout, status)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { buildTokens, normalizeDeviceFlowResponse } from '../../auth/core.js';
|
|
6
7
|
import * as output from '../../utils/output.js';
|
|
7
8
|
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
8
|
-
import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
9
|
+
import { sendError, sendJson, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Create auth router
|
|
@@ -50,22 +51,20 @@ export function createAuthRouter({
|
|
|
50
51
|
// Initiate device flow login
|
|
51
52
|
if (req.method === 'POST' && pathname === '/api/auth/login') {
|
|
52
53
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
expiresIn: deviceFlow.expires_in,
|
|
54
|
+
let deviceFlow = await authService.initiateDeviceFlow();
|
|
55
|
+
let flow = normalizeDeviceFlowResponse(deviceFlow);
|
|
56
|
+
sendSuccess(res, {
|
|
57
|
+
deviceCode: flow.deviceCode,
|
|
58
|
+
userCode: flow.userCode,
|
|
59
|
+
verificationUri: flow.verificationUri,
|
|
60
|
+
verificationUriComplete: flow.urlWithCode,
|
|
61
|
+
expiresIn: deviceFlow.expires_in ?? deviceFlow.expiresIn,
|
|
62
62
|
interval: deviceFlow.interval
|
|
63
|
-
};
|
|
64
|
-
sendSuccess(res, response);
|
|
63
|
+
});
|
|
65
64
|
return true;
|
|
66
65
|
} catch (error) {
|
|
67
66
|
output.error('Error initiating device flow:', error);
|
|
68
|
-
|
|
67
|
+
sendAuthError(res, error);
|
|
69
68
|
return true;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
@@ -81,45 +80,25 @@ export function createAuthRouter({
|
|
|
81
80
|
sendError(res, 400, 'deviceCode is required');
|
|
82
81
|
return true;
|
|
83
82
|
}
|
|
84
|
-
let result;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} catch (error) {
|
|
88
|
-
// Handle "Authorization pending" as a valid response
|
|
89
|
-
if (error.message?.includes('Authorization pending')) {
|
|
90
|
-
sendSuccess(res, {
|
|
91
|
-
status: 'pending'
|
|
92
|
-
});
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Check if authorization is complete by looking for tokens
|
|
99
|
-
if (result.tokens?.accessToken) {
|
|
100
|
-
const tokensData = result.tokens;
|
|
101
|
-
const tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
|
|
102
|
-
const tokenExpiresAt = tokenExpiresIn ? new Date(Date.now() + tokenExpiresIn * 1000).toISOString() : result.expires_at || result.expiresAt;
|
|
103
|
-
const tokens = {
|
|
104
|
-
accessToken: tokensData.accessToken || tokensData.access_token,
|
|
105
|
-
refreshToken: tokensData.refreshToken || tokensData.refresh_token,
|
|
106
|
-
expiresAt: tokenExpiresAt,
|
|
107
|
-
user: result.user
|
|
108
|
-
};
|
|
83
|
+
let result = await authService.pollDeviceAuthorization(deviceCode);
|
|
84
|
+
if (result.tokens?.accessToken || result.tokens?.access_token) {
|
|
85
|
+
let tokens = buildTokens(result);
|
|
109
86
|
await authService.completeDeviceFlow(tokens);
|
|
110
87
|
sendSuccess(res, {
|
|
111
88
|
status: 'complete',
|
|
112
89
|
user: result.user
|
|
113
90
|
});
|
|
114
|
-
} else {
|
|
91
|
+
} else if (['pending', 'expired', 'denied', 'invalid'].includes(result.status)) {
|
|
115
92
|
sendSuccess(res, {
|
|
116
|
-
status:
|
|
93
|
+
status: result.status
|
|
117
94
|
});
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error('Unexpected response from authorization server');
|
|
118
97
|
}
|
|
119
98
|
return true;
|
|
120
99
|
} catch (error) {
|
|
121
100
|
output.error('Error polling device authorization:', error);
|
|
122
|
-
|
|
101
|
+
sendAuthError(res, error);
|
|
123
102
|
return true;
|
|
124
103
|
}
|
|
125
104
|
}
|
|
@@ -135,10 +114,21 @@ export function createAuthRouter({
|
|
|
135
114
|
return true;
|
|
136
115
|
} catch (error) {
|
|
137
116
|
output.error('Error logging out:', error);
|
|
138
|
-
|
|
117
|
+
sendAuthError(res, error);
|
|
139
118
|
return true;
|
|
140
119
|
}
|
|
141
120
|
}
|
|
142
121
|
return false;
|
|
143
122
|
};
|
|
123
|
+
}
|
|
124
|
+
function sendAuthError(res, error) {
|
|
125
|
+
let retryAfter = error.context?.retryAfter;
|
|
126
|
+
if (retryAfter) res.setHeader('Retry-After', String(retryAfter));
|
|
127
|
+
sendJson(res, error.context?.status || 500, {
|
|
128
|
+
error: error.message,
|
|
129
|
+
code: error.context?.serverCode || error.code,
|
|
130
|
+
...(retryAfter ? {
|
|
131
|
+
retryAfter
|
|
132
|
+
} : {})
|
|
133
|
+
});
|
|
144
134
|
}
|
package/dist/utils/browser.js
CHANGED
|
@@ -52,7 +52,13 @@ export async function openBrowser(url) {
|
|
|
52
52
|
args = [url];
|
|
53
53
|
break;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
// Some launchers stay in the foreground. Bound the attempt so login and
|
|
57
|
+
// other CLI workflows can continue using the displayed URL.
|
|
58
|
+
execFile(command, args, {
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
killSignal: 'SIGKILL'
|
|
61
|
+
}, error => {
|
|
56
62
|
if (error) {
|
|
57
63
|
// Browser opening failed, but don't throw - user can manually open
|
|
58
64
|
resolve(false);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
|
|
3
|
+
/** Wait for the next check, with optional terminal input and a live countdown. */
|
|
4
|
+
export async function waitForLoginPoll({
|
|
5
|
+
milliseconds,
|
|
6
|
+
manualAllowed = true,
|
|
7
|
+
earliestCheckAt = 0,
|
|
8
|
+
stdin = process.stdin,
|
|
9
|
+
now = Date.now,
|
|
10
|
+
sleep = delay,
|
|
11
|
+
onTick
|
|
12
|
+
}) {
|
|
13
|
+
if (!stdin.isTTY) {
|
|
14
|
+
onTick(milliseconds, false);
|
|
15
|
+
await sleep(milliseconds);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let deadline = now() + milliseconds;
|
|
19
|
+
let requested = false;
|
|
20
|
+
let timer;
|
|
21
|
+
let wasFlowing = stdin.readableFlowing === true;
|
|
22
|
+
let onData = data => {
|
|
23
|
+
if (manualAllowed && /[\r\n]/.test(data.toString())) {
|
|
24
|
+
requested = true;
|
|
25
|
+
timer?.abort();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
if (manualAllowed) {
|
|
29
|
+
stdin.on('data', onData);
|
|
30
|
+
stdin.resume();
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
while (true) {
|
|
34
|
+
let remaining = Math.min(deadline, requested ? earliestCheckAt : deadline) - now();
|
|
35
|
+
if (remaining <= 0) return;
|
|
36
|
+
onTick(remaining, manualAllowed && !requested);
|
|
37
|
+
timer = new AbortController();
|
|
38
|
+
try {
|
|
39
|
+
await sleep(Math.min(remaining, 1000), undefined, {
|
|
40
|
+
signal: timer.signal
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (!timer.signal.aborted) throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
stdin.removeListener('data', onData);
|
|
48
|
+
if (manualAllowed && !wasFlowing) stdin.pause();
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Visual regression testing from your terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm-context",
|
|
@@ -87,24 +87,24 @@
|
|
|
87
87
|
"fix": "pnpm run format && pnpm run lint:fix"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
|
-
"node": "
|
|
90
|
+
"node": "^22.18.0 || >=24.0.0"
|
|
91
91
|
},
|
|
92
92
|
"publishConfig": {
|
|
93
93
|
"access": "public",
|
|
94
94
|
"registry": "https://registry.npmjs.org/"
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@vizzly-testing/bear-den": "0.1.
|
|
97
|
+
"@vizzly-testing/bear-den": "0.1.6",
|
|
98
98
|
"@vizzly-testing/honeydiff": "^0.17.0",
|
|
99
|
-
"ansis": "^4.
|
|
99
|
+
"ansis": "^4.4.0",
|
|
100
100
|
"commander": "^15.0.0",
|
|
101
|
-
"cosmiconfig": "^
|
|
102
|
-
"dotenv": "^17.2
|
|
103
|
-
"form-data": "^4.0.
|
|
104
|
-
"glob": "^13.0.
|
|
105
|
-
"react": "^19.
|
|
106
|
-
"react-dom": "^19.
|
|
107
|
-
"zod": "^4.
|
|
101
|
+
"cosmiconfig": "^10.0.1",
|
|
102
|
+
"dotenv": "^17.4.2",
|
|
103
|
+
"form-data": "^4.0.6",
|
|
104
|
+
"glob": "^13.0.6",
|
|
105
|
+
"react": "^19.3.0",
|
|
106
|
+
"react-dom": "^19.3.0",
|
|
107
|
+
"zod": "^4.6.5"
|
|
108
108
|
},
|
|
109
109
|
"tsd": {
|
|
110
110
|
"directory": "test-d",
|
|
@@ -117,29 +117,29 @@
|
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
|
-
"@babel/cli": "^8.0.
|
|
121
|
-
"@babel/core": "^8.0.
|
|
122
|
-
"@babel/preset-env": "^8.0.
|
|
120
|
+
"@babel/cli": "^8.0.6",
|
|
121
|
+
"@babel/core": "^8.0.6",
|
|
122
|
+
"@babel/preset-env": "^8.0.6",
|
|
123
123
|
"@babel/preset-react": "^8.0.1",
|
|
124
124
|
"@babel/preset-typescript": "^8.0.1",
|
|
125
|
-
"@biomejs/biome": "^2.5.
|
|
125
|
+
"@biomejs/biome": "^2.5.15",
|
|
126
126
|
"@heroicons/react": "^2.2.0",
|
|
127
|
-
"@modelcontextprotocol/sdk": "^1.0
|
|
128
|
-
"@playwright/test": "^1.
|
|
129
|
-
"@tailwindcss/postcss": "^4.
|
|
130
|
-
"@tanstack/react-query": "^5.
|
|
131
|
-
"@types/node": "^26.
|
|
132
|
-
"@vitejs/plugin-react": "^6.
|
|
133
|
-
"autoprefixer": "^10.
|
|
127
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
128
|
+
"@playwright/test": "^1.63.0",
|
|
129
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
130
|
+
"@tanstack/react-query": "^5.104.0",
|
|
131
|
+
"@types/node": "^26.6.3",
|
|
132
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
133
|
+
"autoprefixer": "^10.6.1",
|
|
134
134
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
135
135
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
|
136
|
-
"postcss": "^8.5.
|
|
137
|
-
"rimraf": "^6.
|
|
138
|
-
"tailwindcss": "^4.
|
|
136
|
+
"postcss": "^8.5.29",
|
|
137
|
+
"rimraf": "^6.1.3",
|
|
138
|
+
"tailwindcss": "^4.3.3",
|
|
139
139
|
"tsd": "^0.33.0",
|
|
140
140
|
"tui-driver": "github:vizzly-testing/tui-driver",
|
|
141
141
|
"typescript": "^7.0.2",
|
|
142
|
-
"vite": "^8.
|
|
143
|
-
"wouter": "^3.
|
|
142
|
+
"vite": "^8.3.1",
|
|
143
|
+
"wouter": "^3.11.1"
|
|
144
144
|
}
|
|
145
145
|
}
|