@vizzly-testing/cli 0.20.1-beta.0 → 0.20.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 +16 -18
- package/dist/cli.js +177 -2
- package/dist/client/index.js +144 -77
- package/dist/commands/doctor.js +118 -33
- package/dist/commands/finalize.js +8 -3
- package/dist/commands/init.js +13 -18
- package/dist/commands/login.js +42 -49
- package/dist/commands/logout.js +13 -5
- package/dist/commands/project.js +95 -67
- package/dist/commands/run.js +32 -6
- package/dist/commands/status.js +81 -50
- package/dist/commands/tdd-daemon.js +61 -32
- package/dist/commands/tdd.js +14 -26
- package/dist/commands/upload.js +18 -9
- package/dist/commands/whoami.js +40 -38
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +204 -22
- package/dist/server/handlers/tdd-handler.js +113 -7
- package/dist/server/http-server.js +9 -3
- package/dist/server/routers/baseline.js +58 -0
- package/dist/server/routers/dashboard.js +10 -6
- package/dist/server/routers/screenshot.js +32 -0
- package/dist/server-manager/core.js +5 -2
- package/dist/server-manager/operations.js +2 -1
- package/dist/services/config-service.js +306 -0
- package/dist/tdd/tdd-service.js +190 -126
- package/dist/types/client.d.ts +25 -2
- package/dist/utils/colors.js +187 -39
- package/dist/utils/config-loader.js +3 -6
- package/dist/utils/context.js +228 -0
- package/dist/utils/output.js +449 -14
- package/docs/api-reference.md +173 -8
- package/docs/tui-elements.md +560 -0
- package/package.json +13 -7
- package/dist/report-generator/core.js +0 -315
- package/dist/report-generator/index.js +0 -8
- package/dist/report-generator/operations.js +0 -196
- package/dist/services/static-report-generator.js +0 -65
|
@@ -20,8 +20,14 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
20
20
|
// Check if server already running
|
|
21
21
|
if (await isServerRunning(options.port || 47392)) {
|
|
22
22
|
const port = options.port || 47392;
|
|
23
|
-
output.
|
|
24
|
-
output.
|
|
23
|
+
let colors = output.getColors();
|
|
24
|
+
output.header('tdd', 'local');
|
|
25
|
+
output.print(` ${output.statusDot('success')} Already running`);
|
|
26
|
+
output.blank();
|
|
27
|
+
output.printBox(colors.brand.info(colors.underline(`http://localhost:${port}`)), {
|
|
28
|
+
title: 'Dashboard',
|
|
29
|
+
style: 'branded'
|
|
30
|
+
});
|
|
25
31
|
if (options.open) {
|
|
26
32
|
openDashboard(port);
|
|
27
33
|
}
|
|
@@ -37,6 +43,9 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
37
43
|
}
|
|
38
44
|
const port = options.port || 47392;
|
|
39
45
|
|
|
46
|
+
// Show header first so debug messages appear below it
|
|
47
|
+
output.header('tdd', 'local');
|
|
48
|
+
|
|
40
49
|
// Show loading indicator if downloading baselines (but not in verbose mode since child shows progress)
|
|
41
50
|
if (options.baselineBuild && !globalOptions.verbose) {
|
|
42
51
|
output.startSpinner(`Downloading baselines from build ${options.baselineBuild}...`);
|
|
@@ -109,7 +118,6 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
109
118
|
output.error('Failed to start TDD server - server not responding to health checks');
|
|
110
119
|
process.exit(1);
|
|
111
120
|
}
|
|
112
|
-
output.success(`TDD server started at http://localhost:${port}`);
|
|
113
121
|
|
|
114
122
|
// Write server info to global location for SDK discovery (iOS/Swift can read this)
|
|
115
123
|
try {
|
|
@@ -129,22 +137,29 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
129
137
|
} catch {
|
|
130
138
|
// Non-fatal, SDK can still use health check
|
|
131
139
|
}
|
|
140
|
+
|
|
141
|
+
// Get colors for styled output
|
|
142
|
+
let colors = output.getColors();
|
|
143
|
+
|
|
144
|
+
// Show dashboard URL in a branded box
|
|
145
|
+
let dashboardUrl = `http://localhost:${port}`;
|
|
146
|
+
output.printBox(colors.brand.info(colors.underline(dashboardUrl)), {
|
|
147
|
+
title: 'Dashboard',
|
|
148
|
+
style: 'branded'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Verbose mode: show next steps
|
|
152
|
+
if (globalOptions.verbose) {
|
|
153
|
+
output.blank();
|
|
154
|
+
output.print(` ${colors.brand.textTertiary('Next steps')}`);
|
|
155
|
+
output.print(` ${colors.brand.textMuted('1.')} Run tests in watch mode ${colors.brand.textMuted('(npm test -- --watch)')}`);
|
|
156
|
+
output.print(` ${colors.brand.textMuted('2.')} Review visual changes in the dashboard`);
|
|
157
|
+
output.print(` ${colors.brand.textMuted('3.')} Accept or reject baseline updates`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Always show stop hint
|
|
132
161
|
output.blank();
|
|
133
|
-
output.
|
|
134
|
-
output.info(` http://localhost:${port}/`);
|
|
135
|
-
output.blank();
|
|
136
|
-
output.info('Available views:');
|
|
137
|
-
output.info(` Comparisons: http://localhost:${port}/`);
|
|
138
|
-
output.info(` Stats: http://localhost:${port}/stats`);
|
|
139
|
-
output.info(` Settings: http://localhost:${port}/settings`);
|
|
140
|
-
output.info(` Projects: http://localhost:${port}/projects`);
|
|
141
|
-
output.blank();
|
|
142
|
-
output.info('Next steps:');
|
|
143
|
-
output.info(' 1. Run your tests in watch mode (e.g., npm test -- --watch)');
|
|
144
|
-
output.info(' 2. View live visual comparisons in the dashboard');
|
|
145
|
-
output.info(' 3. Accept/reject baselines directly in the UI');
|
|
146
|
-
output.blank();
|
|
147
|
-
output.info('Stop server: npx vizzly dev stop');
|
|
162
|
+
output.hint('Stop with: vizzly tdd stop');
|
|
148
163
|
if (options.open) {
|
|
149
164
|
openDashboard(port);
|
|
150
165
|
}
|
|
@@ -289,9 +304,11 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
289
304
|
return;
|
|
290
305
|
}
|
|
291
306
|
try {
|
|
307
|
+
let _colors = output.getColors();
|
|
308
|
+
|
|
292
309
|
// Try to kill the process gracefully
|
|
293
310
|
process.kill(pid, 'SIGTERM');
|
|
294
|
-
output.
|
|
311
|
+
output.startSpinner('Stopping TDD server...');
|
|
295
312
|
|
|
296
313
|
// Give it a moment to shut down gracefully
|
|
297
314
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
@@ -301,15 +318,16 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
301
318
|
process.kill(pid, 0); // Just check if process exists
|
|
302
319
|
// If we get here, process is still running, force kill it
|
|
303
320
|
process.kill(pid, 'SIGKILL');
|
|
304
|
-
output.
|
|
321
|
+
output.stopSpinner();
|
|
322
|
+
output.debug('tdd', 'Force killed process');
|
|
305
323
|
} catch {
|
|
306
324
|
// Process is gone, which is what we want
|
|
325
|
+
output.stopSpinner();
|
|
307
326
|
}
|
|
308
327
|
|
|
309
328
|
// Clean up files
|
|
310
329
|
if (existsSync(pidFile)) unlinkSync(pidFile);
|
|
311
330
|
if (existsSync(serverFile)) unlinkSync(serverFile);
|
|
312
|
-
output.success('TDD server stopped');
|
|
313
331
|
} catch (error) {
|
|
314
332
|
if (error.code === 'ESRCH') {
|
|
315
333
|
// Process not found - clean up stale files
|
|
@@ -356,25 +374,36 @@ export async function tddStatusCommand(_options, globalOptions = {}) {
|
|
|
356
374
|
// Try to check health endpoint
|
|
357
375
|
const health = await checkServerHealth(serverInfo.port);
|
|
358
376
|
if (health.running) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
output.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
output.info(` Projects: http://localhost:${serverInfo.port}/projects`);
|
|
377
|
+
let colors = output.getColors();
|
|
378
|
+
|
|
379
|
+
// Show header
|
|
380
|
+
output.header('tdd', 'local');
|
|
381
|
+
|
|
382
|
+
// Show running status with uptime
|
|
383
|
+
let uptimeStr = '';
|
|
367
384
|
if (serverInfo.startTime) {
|
|
368
385
|
const uptime = Math.floor((Date.now() - serverInfo.startTime) / 1000);
|
|
369
386
|
const hours = Math.floor(uptime / 3600);
|
|
370
387
|
const minutes = Math.floor(uptime % 3600 / 60);
|
|
371
388
|
const seconds = uptime % 60;
|
|
372
|
-
let uptimeStr = '';
|
|
373
389
|
if (hours > 0) uptimeStr += `${hours}h `;
|
|
374
390
|
if (minutes > 0 || hours > 0) uptimeStr += `${minutes}m `;
|
|
375
391
|
uptimeStr += `${seconds}s`;
|
|
392
|
+
}
|
|
393
|
+
output.print(` ${output.statusDot('success')} Running ${uptimeStr ? colors.brand.textTertiary(`· ${uptimeStr}`) : ''}`);
|
|
394
|
+
output.blank();
|
|
395
|
+
|
|
396
|
+
// Show dashboard URL in a branded box
|
|
397
|
+
let dashboardUrl = `http://localhost:${serverInfo.port}`;
|
|
398
|
+
output.printBox(colors.brand.info(colors.underline(dashboardUrl)), {
|
|
399
|
+
title: 'Dashboard',
|
|
400
|
+
style: 'branded'
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Verbose mode: show PID
|
|
404
|
+
if (globalOptions.verbose) {
|
|
376
405
|
output.blank();
|
|
377
|
-
output.
|
|
406
|
+
output.print(` ${colors.brand.textTertiary('PID:')} ${pid}`);
|
|
378
407
|
}
|
|
379
408
|
} else {
|
|
380
409
|
output.warn('TDD server process exists but not responding to health checks');
|
|
@@ -430,7 +459,7 @@ async function checkServerHealth(port = 47392) {
|
|
|
430
459
|
* @private
|
|
431
460
|
*/
|
|
432
461
|
function openDashboard(port = 47392) {
|
|
433
|
-
const url = `http://localhost:${port}
|
|
462
|
+
const url = `http://localhost:${port}`;
|
|
434
463
|
|
|
435
464
|
// Cross-platform open command
|
|
436
465
|
let openCmd;
|
package/dist/commands/tdd.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createBuild as defaultCreateApiBuild, createApiClient as defaultCreateA
|
|
|
8
8
|
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
9
9
|
import { createServerManager as defaultCreateServerManager } from '../server-manager/index.js';
|
|
10
10
|
import { createBuildObject as defaultCreateBuildObject } from '../services/build-manager.js';
|
|
11
|
+
import { createConfigService as defaultCreateConfigService } from '../services/config-service.js';
|
|
11
12
|
import { initializeDaemon as defaultInitializeDaemon, runTests as defaultRunTests } from '../test-runner/index.js';
|
|
12
13
|
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
13
14
|
import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit } from '../utils/git.js';
|
|
@@ -30,6 +31,7 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
30
31
|
getBuild = defaultGetBuild,
|
|
31
32
|
createServerManager = defaultCreateServerManager,
|
|
32
33
|
createBuildObject = defaultCreateBuildObject,
|
|
34
|
+
createConfigService = defaultCreateConfigService,
|
|
33
35
|
initializeDaemon = defaultInitializeDaemon,
|
|
34
36
|
runTests = defaultRunTests,
|
|
35
37
|
detectBranch = defaultDetectBranch,
|
|
@@ -85,11 +87,7 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
85
87
|
output.header('tdd', mode);
|
|
86
88
|
|
|
87
89
|
// Show config in verbose mode
|
|
88
|
-
output.debug('config',
|
|
89
|
-
port: config.server.port,
|
|
90
|
-
branch,
|
|
91
|
-
threshold: config.comparison.threshold
|
|
92
|
-
});
|
|
90
|
+
output.debug('config', `port=${config.server.port} threshold=${config.comparison.threshold}`);
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
// Create functional dependencies
|
|
@@ -99,8 +97,15 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
99
97
|
verbose: globalOptions.verbose
|
|
100
98
|
};
|
|
101
99
|
|
|
100
|
+
// Create config service for dashboard settings page
|
|
101
|
+
let configService = createConfigService({
|
|
102
|
+
workingDir: process.cwd()
|
|
103
|
+
});
|
|
104
|
+
|
|
102
105
|
// Create server manager (functional object)
|
|
103
|
-
serverManager = createServerManager(configWithVerbose, {
|
|
106
|
+
serverManager = createServerManager(configWithVerbose, {
|
|
107
|
+
configService
|
|
108
|
+
});
|
|
104
109
|
|
|
105
110
|
// Create build manager (functional object that provides the interface runTests expects)
|
|
106
111
|
let buildManager = {
|
|
@@ -135,7 +140,7 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
135
140
|
createError: (msg, code) => new VizzlyError(msg, code),
|
|
136
141
|
output,
|
|
137
142
|
onServerReady: data => {
|
|
138
|
-
output.debug('server', `
|
|
143
|
+
output.debug('server', `ready on :${data.port}`);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
});
|
|
@@ -172,7 +177,7 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
172
177
|
output.debug('build', `created ${data.buildId?.substring(0, 8)}`);
|
|
173
178
|
},
|
|
174
179
|
onServerReady: data => {
|
|
175
|
-
output.debug('server', `
|
|
180
|
+
output.debug('server', `ready on :${data.port}`);
|
|
176
181
|
},
|
|
177
182
|
onFinalizeFailed: data => {
|
|
178
183
|
output.warn(`Failed to finalize build: ${data.error}`);
|
|
@@ -180,26 +185,9 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
180
185
|
}
|
|
181
186
|
});
|
|
182
187
|
|
|
183
|
-
// Show summary
|
|
184
|
-
let {
|
|
185
|
-
screenshotsCaptured,
|
|
186
|
-
comparisons
|
|
187
|
-
} = runResult;
|
|
188
|
-
|
|
189
188
|
// Determine success based on comparison results
|
|
189
|
+
// (Summary is printed by printResults() in tdd-service.js, called from getTddResults)
|
|
190
190
|
let hasFailures = runResult.failed || runResult.comparisons?.some(c => c.status === 'failed');
|
|
191
|
-
if (comparisons && comparisons.length > 0) {
|
|
192
|
-
let passed = comparisons.filter(c => c.status === 'passed').length;
|
|
193
|
-
let failed = comparisons.filter(c => c.status === 'failed').length;
|
|
194
|
-
if (hasFailures) {
|
|
195
|
-
output.error(`${failed} visual difference${failed !== 1 ? 's' : ''} detected`);
|
|
196
|
-
output.info(`Check .vizzly/diffs/ for diff images`);
|
|
197
|
-
} else {
|
|
198
|
-
output.result(`${screenshotsCaptured} screenshot${screenshotsCaptured !== 1 ? 's' : ''} · ${passed} passed`);
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
output.result(`${screenshotsCaptured} screenshot${screenshotsCaptured !== 1 ? 's' : ''}`);
|
|
202
|
-
}
|
|
203
191
|
return {
|
|
204
192
|
result: {
|
|
205
193
|
success: !hasFailures,
|
package/dist/commands/upload.js
CHANGED
|
@@ -173,32 +173,41 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
173
173
|
output.warn(`Failed to finalize build: ${error.message}`);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
-
output.
|
|
176
|
+
output.complete('Upload completed');
|
|
177
177
|
|
|
178
178
|
// Show Vizzly summary
|
|
179
179
|
if (result.buildId) {
|
|
180
|
-
output.
|
|
180
|
+
output.blank();
|
|
181
|
+
output.keyValue({
|
|
182
|
+
Uploaded: `${result.stats.uploaded} of ${result.stats.total}`,
|
|
183
|
+
Build: result.buildId
|
|
184
|
+
});
|
|
181
185
|
// Use API-provided URL or construct proper URL with org/project context
|
|
182
186
|
let buildUrl = result.url || (await buildUrlConstructor(result.buildId, config.apiUrl, config.apiKey, deps));
|
|
183
|
-
output.
|
|
187
|
+
output.blank();
|
|
188
|
+
output.labelValue('View', output.link('Results', buildUrl));
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
// Wait for build completion if requested
|
|
187
192
|
if (options.wait && result.buildId) {
|
|
188
|
-
output.info('Waiting for build completion...');
|
|
189
193
|
output.startSpinner('Processing comparisons...');
|
|
190
|
-
|
|
191
|
-
output.
|
|
194
|
+
let buildResult = await uploader.waitForBuild(result.buildId);
|
|
195
|
+
output.stopSpinner();
|
|
196
|
+
output.complete('Build processing completed');
|
|
192
197
|
|
|
193
198
|
// Show build processing results
|
|
199
|
+
let colors = output.getColors();
|
|
194
200
|
if (buildResult.failedComparisons > 0) {
|
|
195
|
-
output.
|
|
201
|
+
output.blank();
|
|
202
|
+
output.print(` ${colors.brand.danger(buildResult.failedComparisons)} visual comparisons failed`);
|
|
196
203
|
} else {
|
|
197
|
-
output.
|
|
204
|
+
output.blank();
|
|
205
|
+
output.print(` ${colors.brand.success(buildResult.passedComparisons)} visual comparisons passed`);
|
|
198
206
|
}
|
|
199
207
|
// Use API-provided URL or construct proper URL with org/project context
|
|
200
208
|
let waitBuildUrl = buildResult.url || (await buildUrlConstructor(result.buildId, config.apiUrl, config.apiKey, deps));
|
|
201
|
-
output.
|
|
209
|
+
output.blank();
|
|
210
|
+
output.labelValue('View', output.link('Results', waitBuildUrl));
|
|
202
211
|
}
|
|
203
212
|
output.cleanup();
|
|
204
213
|
return {
|
package/dist/commands/whoami.js
CHANGED
|
@@ -20,16 +20,17 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
|
|
|
20
20
|
});
|
|
21
21
|
try {
|
|
22
22
|
// Check if user is logged in
|
|
23
|
-
|
|
23
|
+
let auth = await getAuthTokens();
|
|
24
24
|
if (!auth || !auth.accessToken) {
|
|
25
25
|
if (globalOptions.json) {
|
|
26
26
|
output.data({
|
|
27
27
|
authenticated: false
|
|
28
28
|
});
|
|
29
29
|
} else {
|
|
30
|
-
output.
|
|
30
|
+
output.header('whoami');
|
|
31
|
+
output.print(' Not logged in');
|
|
31
32
|
output.blank();
|
|
32
|
-
output.
|
|
33
|
+
output.hint('Run "vizzly login" to authenticate');
|
|
33
34
|
}
|
|
34
35
|
output.cleanup();
|
|
35
36
|
return;
|
|
@@ -57,36 +58,39 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// Human-readable output
|
|
60
|
-
output.
|
|
61
|
-
output.blank();
|
|
61
|
+
output.header('whoami');
|
|
62
62
|
|
|
63
|
-
// Show user info
|
|
63
|
+
// Show user info using keyValue
|
|
64
64
|
if (response.user) {
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
let userInfo = {
|
|
66
|
+
User: response.user.name || response.user.username,
|
|
67
|
+
Email: response.user.email
|
|
68
|
+
};
|
|
67
69
|
if (response.user.username) {
|
|
68
|
-
|
|
70
|
+
userInfo.Username = response.user.username;
|
|
69
71
|
}
|
|
70
72
|
if (globalOptions.verbose && response.user.id) {
|
|
71
|
-
|
|
73
|
+
userInfo['User ID'] = response.user.id;
|
|
72
74
|
}
|
|
75
|
+
output.keyValue(userInfo);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
// Show organizations
|
|
78
|
+
// Show organizations as a list
|
|
76
79
|
if (response.organizations && response.organizations.length > 0) {
|
|
77
80
|
output.blank();
|
|
78
|
-
output.
|
|
79
|
-
|
|
80
|
-
let
|
|
81
|
-
if (org.slug) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
output.labelValue('Organizations', '');
|
|
82
|
+
let orgItems = response.organizations.map(org => {
|
|
83
|
+
let parts = [org.name];
|
|
84
|
+
if (org.slug) parts.push(`@${org.slug}`);
|
|
85
|
+
if (org.role) parts.push(`[${org.role}]`);
|
|
86
|
+
return parts.join(' ');
|
|
87
|
+
});
|
|
88
|
+
output.list(orgItems);
|
|
89
|
+
if (globalOptions.verbose) {
|
|
90
|
+
for (let org of response.organizations) {
|
|
91
|
+
if (org.id) {
|
|
92
|
+
output.hint(` ${org.name} ID: ${org.id}`);
|
|
93
|
+
}
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
}
|
|
@@ -94,29 +98,27 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
|
|
|
94
98
|
// Show token expiry info
|
|
95
99
|
if (auth.expiresAt) {
|
|
96
100
|
output.blank();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
let expiresAt = new Date(auth.expiresAt);
|
|
102
|
+
let now = new Date();
|
|
103
|
+
let msUntilExpiry = expiresAt.getTime() - now.getTime();
|
|
104
|
+
let daysUntilExpiry = Math.floor(msUntilExpiry / (1000 * 60 * 60 * 24));
|
|
105
|
+
let hoursUntilExpiry = Math.floor(msUntilExpiry / (1000 * 60 * 60));
|
|
106
|
+
let minutesUntilExpiry = Math.floor(msUntilExpiry / (1000 * 60));
|
|
103
107
|
if (msUntilExpiry <= 0) {
|
|
104
108
|
output.warn('Token has expired');
|
|
105
|
-
output.
|
|
106
|
-
output.info('Run "vizzly login" to refresh your authentication');
|
|
109
|
+
output.hint('Run "vizzly login" to refresh your authentication');
|
|
107
110
|
} else if (daysUntilExpiry > 0) {
|
|
108
|
-
output.
|
|
111
|
+
output.hint(`Token expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? 's' : ''} (${expiresAt.toLocaleDateString()})`);
|
|
109
112
|
} else if (hoursUntilExpiry > 0) {
|
|
110
|
-
output.
|
|
113
|
+
output.hint(`Token expires in ${hoursUntilExpiry} hour${hoursUntilExpiry !== 1 ? 's' : ''}`);
|
|
111
114
|
} else if (minutesUntilExpiry > 0) {
|
|
112
|
-
output.
|
|
115
|
+
output.hint(`Token expires in ${minutesUntilExpiry} minute${minutesUntilExpiry !== 1 ? 's' : ''}`);
|
|
113
116
|
} else {
|
|
114
117
|
output.warn('Token expires in less than a minute');
|
|
115
|
-
output.
|
|
116
|
-
output.info('Run "vizzly login" to refresh your authentication');
|
|
118
|
+
output.hint('Run "vizzly login" to refresh your authentication');
|
|
117
119
|
}
|
|
118
120
|
if (globalOptions.verbose) {
|
|
119
|
-
output.
|
|
121
|
+
output.hint(`Token expires at: ${expiresAt.toISOString()}`);
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
output.cleanup();
|
|
@@ -133,7 +135,7 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
|
|
|
133
135
|
} else {
|
|
134
136
|
output.error('Authentication token is invalid or expired', error);
|
|
135
137
|
output.blank();
|
|
136
|
-
output.
|
|
138
|
+
output.hint('Run "vizzly login" to authenticate again');
|
|
137
139
|
}
|
|
138
140
|
output.cleanup();
|
|
139
141
|
process.exit(1);
|