@vizzly-testing/cli 0.39.0 → 0.39.2
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 +12 -3
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +50 -35
- package/dist/adapters/cli/setup.js +34 -0
- package/dist/{client → adapters/client}/index.js +6 -7
- package/dist/adapters/http/cloud-proxy.js +29 -0
- package/dist/{server → adapters/http}/http-server.js +12 -13
- package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
- package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
- package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
- package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
- package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
- package/dist/adapters/sdk/index.d.ts +89 -0
- package/dist/{sdk → adapters/sdk}/index.js +22 -19
- package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
- package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
- package/dist/domains/auth/auth-errors.js +50 -0
- package/dist/domains/auth/auth-headers.js +41 -0
- package/dist/domains/auth/auth.normalizers.js +50 -0
- package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
- package/dist/domains/auth/auth.rules.js +19 -0
- package/dist/domains/auth/auth.serializers.js +32 -0
- package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
- package/dist/domains/auth/auth.validations.js +21 -0
- package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
- package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
- package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
- package/dist/domains/auth/token-store.js +81 -0
- package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
- package/dist/domains/baselines/baseline-download.js +192 -0
- package/dist/domains/baselines/baseline-write.js +22 -0
- package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
- package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
- package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
- package/dist/domains/baselines/cloud-baselines.js +9 -0
- package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
- package/dist/domains/builds/build.normalizers.js +35 -0
- package/dist/domains/builds/build.serializers.js +164 -0
- package/dist/domains/builds/build.service.js +119 -0
- package/dist/domains/builds/build.validations.js +39 -0
- package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
- package/dist/domains/builds/cloud-builds.routes.js +31 -0
- package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
- package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
- package/dist/domains/comparisons/cloud-comparisons.js +43 -0
- package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
- package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
- package/dist/domains/config/config-defaults.js +27 -0
- package/dist/{utils → domains/config}/config-discovery.js +1 -9
- package/dist/{utils → domains/config}/config-loader.js +10 -9
- package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
- package/dist/{types → domains/config}/config.d.ts +3 -3
- package/dist/domains/config/config.normalizers.js +126 -0
- package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
- package/dist/domains/config/config.rules.js +47 -0
- package/dist/domains/config/config.serializers.js +202 -0
- package/dist/domains/config/config.service.js +625 -0
- package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
- package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
- package/dist/domains/context/compact-context.validations.js +60 -0
- package/dist/domains/context/context-presentation.js +368 -0
- package/dist/domains/context/context.commands.js +385 -0
- package/dist/domains/context/context.serializers.js +157 -0
- package/dist/domains/context/context.service.js +76 -0
- package/dist/domains/context/context.validations.js +100 -0
- package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
- package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
- package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
- package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
- package/dist/domains/previews/preview.service.js +37 -0
- package/dist/domains/projects/cloud-projects.routes.js +28 -0
- package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
- package/dist/{utils → domains/projects}/project-binding.js +3 -3
- package/dist/domains/projects/project-errors.js +61 -0
- package/dist/{utils → domains/projects}/project-link-store.js +2 -2
- package/dist/domains/projects/project-requests.js +61 -0
- package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
- package/dist/domains/projects/project.normalizers.js +69 -0
- package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
- package/dist/domains/projects/project.validations.js +31 -0
- package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
- package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
- package/dist/domains/reports/dashboard.routes.js +96 -0
- package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
- package/dist/domains/reports/report-assets.routes.js +81 -0
- package/dist/domains/reports/report-state.js +51 -0
- package/dist/domains/reports/report-store.js +85 -0
- package/dist/domains/reports/report.serializers.js +99 -0
- package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
- package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
- package/dist/domains/runs/run-environment.js +69 -0
- package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
- package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
- package/dist/domains/runs/run.normalizers.js +8 -0
- package/dist/domains/runs/run.rules.js +56 -0
- package/dist/domains/runs/run.serializers.js +101 -0
- package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
- package/dist/domains/runs/run.validations.js +17 -0
- package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
- package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
- package/dist/domains/screenshots/cloud-screenshots.js +109 -0
- package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
- package/dist/domains/screenshots/screenshot-hash.js +53 -0
- package/dist/domains/screenshots/screenshot-server-options.js +28 -0
- package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
- package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
- package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
- package/dist/domains/screenshots/screenshot.serializers.js +146 -0
- package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
- package/dist/{init/agents.js → domains/setup/agent-setup.js} +54 -14
- package/dist/{commands/init.js → domains/setup/init.commands.js} +11 -44
- package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
- package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
- package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
- package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
- package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
- package/dist/domains/updates/cli-update.js +103 -0
- package/dist/domains/updates/update-check.js +2 -0
- package/dist/domains/updates/update-notifications.js +175 -0
- package/dist/domains/uploads/upload-defaults.js +4 -0
- package/dist/domains/uploads/upload-files.js +32 -0
- package/dist/domains/uploads/upload-settings.js +21 -0
- package/dist/domains/uploads/upload-timing.js +18 -0
- package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
- package/dist/domains/uploads/upload.normalizers.js +60 -0
- package/dist/domains/uploads/upload.serializers.js +179 -0
- package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
- package/dist/domains/uploads/upload.validations.js +77 -0
- package/dist/index.js +12 -12
- package/dist/reporter/reporter-bundle.iife.js +86 -86
- package/dist/reporter-ssr/ssr-entry.js +3 -6
- package/dist/shared/cloud-protocol.js +171 -0
- package/dist/{utils → shared}/file-changes.js +1 -1
- package/dist/shared/files.js +9 -0
- package/dist/{utils → shared}/global-config.js +7 -78
- package/dist/shared/log-level.js +7 -0
- package/dist/{utils → shared}/output.js +1 -1
- package/dist/{utils → shared}/package-info.js +5 -4
- package/dist/shared/paths.js +57 -0
- package/package.json +37 -36
- package/dist/api/core.js +0 -382
- package/dist/api/endpoints.js +0 -421
- package/dist/api/index.js +0 -19
- package/dist/auth/core.js +0 -237
- package/dist/auth/index.js +0 -31
- package/dist/commands/context.js +0 -1066
- package/dist/config/core.js +0 -439
- package/dist/config/operations.js +0 -327
- package/dist/project/core.js +0 -211
- package/dist/screenshot-server/core.js +0 -161
- package/dist/screenshot-server/index.js +0 -11
- package/dist/server/routers/assets.js +0 -95
- package/dist/server/routers/cloud-proxy.js +0 -146
- package/dist/server/routers/dashboard.js +0 -172
- package/dist/services/auth-service.js +0 -117
- package/dist/services/config-service.js +0 -313
- package/dist/services/project-service.js +0 -88
- package/dist/test-runner/core.js +0 -293
- package/dist/test-runner/index.js +0 -13
- package/dist/types/sdk.d.ts +0 -186
- package/dist/uploader/core.js +0 -432
- package/dist/uploader/index.js +0 -88
- /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
- /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
- /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
- /package/dist/{server → adapters/http}/middleware/response.js +0 -0
- /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
- /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
- /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
- /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
- /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
- /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
- /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
- /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
- /package/dist/{context → domains/context}/provider-resolver.js +0 -0
- /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
- /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
- /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
- /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
- /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
- /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
- /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
- /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
- /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
- /package/dist/{types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{utils → shared}/api-url.js +0 -0
- /package/dist/{utils → shared}/async-utils.js +0 -0
- /package/dist/{utils → shared}/browser.js +0 -0
- /package/dist/{utils → shared}/ci-env.js +0 -0
- /package/dist/{utils → shared}/colors.js +0 -0
- /package/dist/{utils → shared}/fetch-utils.js +0 -0
- /package/dist/{utils → shared}/git.js +0 -0
- /package/dist/{utils → shared}/patterns.js +0 -0
- /package/dist/{utils → shared}/secret-store.js +0 -0
- /package/dist/{errors → shared}/vizzly-error.js +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { getPackageVersion } from '../../shared/package-info.js';
|
|
2
|
+
import { parseAuthError, parseAuthenticatedError } from './auth-errors.js';
|
|
3
|
+
import { buildAuthUserAgent, buildRequestHeaders } from './auth-headers.js';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Auth Client - HTTP client factory for authentication
|
|
3
7
|
*
|
|
4
8
|
* Creates a thin HTTP wrapper that can be injected into operations.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
|
-
import { getPackageVersion } from '../utils/package-info.js';
|
|
8
|
-
import { buildAuthUserAgent, buildRequestHeaders, parseAuthError, parseAuthenticatedError } from './core.js';
|
|
9
|
-
|
|
10
11
|
/**
|
|
11
12
|
* Parse response body based on content type
|
|
12
13
|
* @param {Response} response - Fetch response
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AuthError, VizzlyError } from '../../shared/vizzly-error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse error from API response
|
|
5
|
+
* @param {number} status - HTTP status code
|
|
6
|
+
* @param {Object|string} body - Response body (parsed JSON or text)
|
|
7
|
+
* @param {string} endpoint - API endpoint for context
|
|
8
|
+
* @returns {Error} Appropriate error type
|
|
9
|
+
*/
|
|
10
|
+
export function parseAuthError(status, body, _endpoint) {
|
|
11
|
+
let errorText = '';
|
|
12
|
+
if (typeof body === 'object' && body !== null) {
|
|
13
|
+
errorText = body.error || body.message || '';
|
|
14
|
+
} else if (typeof body === 'string') {
|
|
15
|
+
errorText = body;
|
|
16
|
+
}
|
|
17
|
+
let retryAfter = Number(body?.retryAfter);
|
|
18
|
+
let context = {
|
|
19
|
+
status,
|
|
20
|
+
serverCode: body?.code,
|
|
21
|
+
retryAfter: retryAfter > 0 ? retryAfter : undefined
|
|
22
|
+
};
|
|
23
|
+
if (status === 401) {
|
|
24
|
+
return new AuthError(errorText || 'Invalid credentials. Please check your email/username and password.', context);
|
|
25
|
+
}
|
|
26
|
+
if (status === 429) {
|
|
27
|
+
return new VizzlyError('Too many login attempts. Please try again later.', 'RATE_LIMIT_ERROR', context);
|
|
28
|
+
}
|
|
29
|
+
return new VizzlyError(`Authentication request failed: ${status}${errorText ? ` - ${errorText}` : ''}`, 'AUTH_REQUEST_ERROR', context);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse error for authenticated requests (different error messages)
|
|
34
|
+
* @param {number} status - HTTP status code
|
|
35
|
+
* @param {Object|string} body - Response body
|
|
36
|
+
* @param {string} endpoint - API endpoint
|
|
37
|
+
* @returns {Error} Appropriate error type
|
|
38
|
+
*/
|
|
39
|
+
export function parseAuthenticatedError(status, body, endpoint) {
|
|
40
|
+
let errorText = '';
|
|
41
|
+
if (typeof body === 'object' && body !== null) {
|
|
42
|
+
errorText = body.error || body.message || '';
|
|
43
|
+
} else if (typeof body === 'string') {
|
|
44
|
+
errorText = body;
|
|
45
|
+
}
|
|
46
|
+
if (status === 401) {
|
|
47
|
+
return new AuthError('Authentication token is invalid or expired. Please run "vizzly login" again.');
|
|
48
|
+
}
|
|
49
|
+
return new VizzlyError(`API request failed: ${status}${errorText ? ` - ${errorText}` : ''} (${endpoint})`, 'API_REQUEST_ERROR');
|
|
50
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Authorization header from access token
|
|
3
|
+
* @param {string|null} accessToken - Access token
|
|
4
|
+
* @returns {Object} Headers object with Authorization if token exists
|
|
5
|
+
*/
|
|
6
|
+
export function buildAuthHeader(accessToken) {
|
|
7
|
+
if (!accessToken) return {};
|
|
8
|
+
return {
|
|
9
|
+
Authorization: `Bearer ${accessToken}`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build User-Agent header for auth requests
|
|
15
|
+
* @param {string} version - CLI version
|
|
16
|
+
* @returns {string} User-Agent string
|
|
17
|
+
*/
|
|
18
|
+
export function buildAuthUserAgent(version) {
|
|
19
|
+
return `vizzly-cli/${version} (auth)`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build complete headers for a request
|
|
24
|
+
* @param {Object} options - Header options
|
|
25
|
+
* @returns {Object} Complete headers object
|
|
26
|
+
*/
|
|
27
|
+
export function buildRequestHeaders({
|
|
28
|
+
userAgent,
|
|
29
|
+
accessToken,
|
|
30
|
+
contentType,
|
|
31
|
+
extra = {}
|
|
32
|
+
}) {
|
|
33
|
+
return {
|
|
34
|
+
'User-Agent': userAgent,
|
|
35
|
+
...(accessToken ? buildAuthHeader(accessToken) : {}),
|
|
36
|
+
...(contentType ? {
|
|
37
|
+
'Content-Type': contentType
|
|
38
|
+
} : {}),
|
|
39
|
+
...extra
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build token data for storage from API response
|
|
3
|
+
* @param {Object} response - API response with tokens
|
|
4
|
+
* @param {Object|null} existingUser - Existing user data to preserve
|
|
5
|
+
* @returns {Object} Token data for storage
|
|
6
|
+
*/
|
|
7
|
+
export function buildTokenData(response, existingUser = null) {
|
|
8
|
+
return {
|
|
9
|
+
accessToken: response.accessToken,
|
|
10
|
+
refreshToken: response.refreshToken,
|
|
11
|
+
expiresAt: response.expiresAt,
|
|
12
|
+
user: response.user || existingUser
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function normalizeDeviceFlowResponse(deviceFlow) {
|
|
16
|
+
let verificationUri = deviceFlow.verification_uri || deviceFlow.verificationUri;
|
|
17
|
+
let userCode = deviceFlow.user_code || deviceFlow.userCode;
|
|
18
|
+
let deviceCode = deviceFlow.device_code || deviceFlow.deviceCode;
|
|
19
|
+
if (!verificationUri || !userCode || !deviceCode) {
|
|
20
|
+
throw new Error('Invalid device flow response from server');
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
verificationUri,
|
|
24
|
+
userCode,
|
|
25
|
+
deviceCode,
|
|
26
|
+
urlWithCode: deviceFlow.verification_uri_complete || deviceFlow.verificationUriComplete || `${verificationUri}?code=${encodeURIComponent(userCode)}`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function buildTokens(tokenData, now = Date.now()) {
|
|
30
|
+
let tokensData = tokenData.tokens || tokenData;
|
|
31
|
+
let tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
|
|
32
|
+
let tokenExpiresAt = tokenExpiresIn ? new Date(now + tokenExpiresIn * 1000).toISOString() : tokenData.expires_at || tokenData.expiresAt;
|
|
33
|
+
return {
|
|
34
|
+
accessToken: tokensData.accessToken || tokensData.access_token,
|
|
35
|
+
refreshToken: tokensData.refreshToken || tokensData.refresh_token,
|
|
36
|
+
expiresAt: tokenExpiresAt,
|
|
37
|
+
user: tokenData.user,
|
|
38
|
+
organizations: tokenData.organizations
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Map protocol codes without depending on the server's human-readable text. */
|
|
43
|
+
export function deviceAuthorizationStatus(error) {
|
|
44
|
+
return {
|
|
45
|
+
AUTHORIZATION_PENDING: 'pending',
|
|
46
|
+
ACCESS_DENIED: 'denied',
|
|
47
|
+
EXPIRED_TOKEN: 'expired',
|
|
48
|
+
INVALID_DEVICE_CODE: 'invalid'
|
|
49
|
+
}[error.context?.serverCode];
|
|
50
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { parseJsonBody } from '../../adapters/http/middleware/json-parser.js';
|
|
2
|
+
import { sendError, sendJson, sendServiceUnavailable, sendSuccess } from '../../adapters/http/middleware/response.js';
|
|
3
|
+
import * as output from '../../shared/output.js';
|
|
4
|
+
import { buildTokens, normalizeDeviceFlowResponse } from './auth.normalizers.js';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Auth Router
|
|
3
8
|
* Handles authentication endpoints (device flow login, logout, status)
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
import { buildTokens, normalizeDeviceFlowResponse } from '../../auth/core.js';
|
|
7
|
-
import * as output from '../../utils/output.js';
|
|
8
|
-
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
9
|
-
import { sendError, sendJson, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
10
|
-
|
|
11
11
|
/**
|
|
12
12
|
* Create auth router
|
|
13
13
|
* @param {Object} context - Router context
|
|
@@ -27,10 +27,10 @@ export function createAuthRouter({
|
|
|
27
27
|
// Get auth status and user info
|
|
28
28
|
if (req.method === 'GET' && pathname === '/api/auth/status') {
|
|
29
29
|
try {
|
|
30
|
-
|
|
30
|
+
let isAuthenticated = await authService.isAuthenticated();
|
|
31
31
|
let user = null;
|
|
32
32
|
if (isAuthenticated) {
|
|
33
|
-
|
|
33
|
+
let whoami = await authService.whoami();
|
|
34
34
|
user = whoami.user;
|
|
35
35
|
}
|
|
36
36
|
sendSuccess(res, {
|
|
@@ -72,8 +72,8 @@ export function createAuthRouter({
|
|
|
72
72
|
// Poll device authorization status
|
|
73
73
|
if (req.method === 'POST' && pathname === '/api/auth/poll') {
|
|
74
74
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
let body = await parseJsonBody(req);
|
|
76
|
+
let {
|
|
77
77
|
deviceCode
|
|
78
78
|
} = body;
|
|
79
79
|
if (!deviceCode) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function resolveAuthorizedTokenData(pollResponse) {
|
|
2
|
+
let accessToken = pollResponse.tokens?.accessToken || pollResponse.tokens?.access_token;
|
|
3
|
+
if (accessToken) {
|
|
4
|
+
return pollResponse;
|
|
5
|
+
}
|
|
6
|
+
if (pollResponse.status === 'pending') {
|
|
7
|
+
throw new Error('Authorization not complete yet. Please complete the authorization in your browser and try running "vizzly login" again.');
|
|
8
|
+
}
|
|
9
|
+
if (pollResponse.status === 'expired') {
|
|
10
|
+
throw new Error('Device code expired. Please try logging in again.');
|
|
11
|
+
}
|
|
12
|
+
if (pollResponse.status === 'invalid') {
|
|
13
|
+
throw new Error('Device code is invalid or expired. Please try logging in again.');
|
|
14
|
+
}
|
|
15
|
+
if (pollResponse.status === 'denied') {
|
|
16
|
+
throw new Error('Authorization denied. Please try logging in again.');
|
|
17
|
+
}
|
|
18
|
+
throw new Error('Unexpected response from authorization server. Please try logging in again.');
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build device poll request payload
|
|
3
|
+
* @param {string} deviceCode - Device code from initiate
|
|
4
|
+
* @returns {Object} Request payload
|
|
5
|
+
*/
|
|
6
|
+
export function buildDevicePollPayload(deviceCode) {
|
|
7
|
+
return {
|
|
8
|
+
device_code: deviceCode
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build refresh token request payload
|
|
14
|
+
* @param {string} refreshToken - Refresh token
|
|
15
|
+
* @returns {Object} Request payload
|
|
16
|
+
*/
|
|
17
|
+
export function buildRefreshPayload(refreshToken) {
|
|
18
|
+
return {
|
|
19
|
+
refreshToken
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build logout request payload
|
|
25
|
+
* @param {string} refreshToken - Refresh token to revoke
|
|
26
|
+
* @returns {Object} Request payload
|
|
27
|
+
*/
|
|
28
|
+
export function buildLogoutPayload(refreshToken) {
|
|
29
|
+
return {
|
|
30
|
+
refreshToken
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import * as output from '../../shared/output.js';
|
|
2
|
+
import { getApiUrl } from '../config/environment.js';
|
|
3
|
+
import { buildTokenData, deviceAuthorizationStatus } from './auth.normalizers.js';
|
|
4
|
+
import { buildDevicePollPayload, buildLogoutPayload, buildRefreshPayload } from './auth.serializers.js';
|
|
5
|
+
import { validateTokens } from './auth.validations.js';
|
|
6
|
+
import { createAuthClient } from './auth-client.js';
|
|
7
|
+
import { createTokenStore } from './token-store.js';
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Auth Operations - Authentication operations with dependency injection
|
|
3
11
|
*
|
|
@@ -8,12 +16,114 @@
|
|
|
8
16
|
* This makes them trivially testable without mocking modules.
|
|
9
17
|
*/
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Auth Service
|
|
21
|
+
* Wraps auth operations for use by the HTTP server
|
|
22
|
+
*
|
|
23
|
+
* Provides the interface expected by src/domains/auth/auth.routes.js:
|
|
24
|
+
* - isAuthenticated() - Returns boolean, false if no tokens or API call fails
|
|
25
|
+
* - whoami() - Throws if not authenticated or tokens invalid
|
|
26
|
+
* - initiateDeviceFlow() - Throws on API error
|
|
27
|
+
* - pollDeviceAuthorization(deviceCode) - Returns pending status or tokens, throws on error
|
|
28
|
+
* - completeDeviceFlow(tokens) - Saves tokens to global config
|
|
29
|
+
* - logout() - Clears local tokens, may warn if server revocation fails
|
|
30
|
+
* - authenticatedRequest(endpoint, options) - Throws 'Not authenticated' if no tokens
|
|
31
|
+
*
|
|
32
|
+
* Error handling:
|
|
33
|
+
* - isAuthenticated() never throws, returns false on any error
|
|
34
|
+
* - whoami() throws if tokens are missing/invalid (caller should check isAuthenticated first)
|
|
35
|
+
* - authenticatedRequest() throws 'Not authenticated' if no access token
|
|
36
|
+
* - Device flow methods throw on API errors (network, server errors)
|
|
37
|
+
*/
|
|
13
38
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Create an auth service instance
|
|
41
|
+
* @param {Object} [options]
|
|
42
|
+
* @param {string} [options.apiUrl] - API base URL (defaults to VIZZLY_API_URL or https://app.vizzly.dev)
|
|
43
|
+
* @param {Object} [options.httpClient] - Injectable HTTP client (for testing)
|
|
44
|
+
* @param {Object} [options.tokenStore] - Injectable token store (for testing)
|
|
45
|
+
* @returns {Object} Auth service
|
|
46
|
+
*/
|
|
47
|
+
export function createAuthService(options = {}) {
|
|
48
|
+
let apiUrl = options.apiUrl || getApiUrl();
|
|
49
|
+
|
|
50
|
+
// Create HTTP client for API requests (uses auth client for proper auth handling)
|
|
51
|
+
// Allow injection for testing
|
|
52
|
+
let httpClient = options.httpClient || createAuthClient({
|
|
53
|
+
baseUrl: apiUrl
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Create token store adapter for global config
|
|
57
|
+
// Allow injection for testing
|
|
58
|
+
let tokenStore = options.tokenStore || createTokenStore();
|
|
59
|
+
return {
|
|
60
|
+
/**
|
|
61
|
+
* Check if user is authenticated
|
|
62
|
+
* @returns {Promise<boolean>}
|
|
63
|
+
*/
|
|
64
|
+
async isAuthenticated() {
|
|
65
|
+
return isAuthenticated(httpClient, tokenStore);
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Get current user information
|
|
69
|
+
* @returns {Promise<Object>} User and organization data
|
|
70
|
+
*/
|
|
71
|
+
async whoami() {
|
|
72
|
+
return whoami(httpClient, tokenStore);
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Initiate OAuth device flow
|
|
76
|
+
* @returns {Promise<Object>} Device code info
|
|
77
|
+
*/
|
|
78
|
+
async initiateDeviceFlow() {
|
|
79
|
+
return initiateDeviceFlow(httpClient);
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Poll for device authorization
|
|
83
|
+
* @param {string} deviceCode
|
|
84
|
+
* @returns {Promise<Object>} Token data or pending status
|
|
85
|
+
*/
|
|
86
|
+
async pollDeviceAuthorization(deviceCode) {
|
|
87
|
+
return pollDeviceAuthorization(httpClient, deviceCode);
|
|
88
|
+
},
|
|
89
|
+
/**
|
|
90
|
+
* Complete device flow and save tokens
|
|
91
|
+
* @param {Object} tokens - Token data
|
|
92
|
+
* @returns {Promise<Object>}
|
|
93
|
+
*/
|
|
94
|
+
async completeDeviceFlow(tokens) {
|
|
95
|
+
return completeDeviceFlow(tokenStore, tokens);
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Logout and revoke tokens
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
*/
|
|
101
|
+
async logout() {
|
|
102
|
+
return logout(httpClient, tokenStore);
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Refresh access token
|
|
106
|
+
* @returns {Promise<Object>} New tokens
|
|
107
|
+
*/
|
|
108
|
+
async refresh() {
|
|
109
|
+
return refresh(httpClient, tokenStore);
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* Make an authenticated request to the API
|
|
113
|
+
* Used by cloud-proxy router for proxying requests
|
|
114
|
+
* @param {string} endpoint - API endpoint
|
|
115
|
+
* @param {Object} options - Fetch options
|
|
116
|
+
* @returns {Promise<Object>} Response data
|
|
117
|
+
*/
|
|
118
|
+
async authenticatedRequest(endpoint, options = {}) {
|
|
119
|
+
let auth = await tokenStore.getTokens();
|
|
120
|
+
if (!auth?.accessToken) {
|
|
121
|
+
throw new Error('Not authenticated');
|
|
122
|
+
}
|
|
123
|
+
return httpClient.authenticatedRequest(endpoint, auth.accessToken, options);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
17
127
|
|
|
18
128
|
/**
|
|
19
129
|
* Initiate OAuth device flow
|
|
@@ -65,10 +175,6 @@ export async function completeDeviceFlow(tokenStore, tokenData) {
|
|
|
65
175
|
return tokenData;
|
|
66
176
|
}
|
|
67
177
|
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// Token Operations
|
|
70
|
-
// ============================================================================
|
|
71
|
-
|
|
72
178
|
/**
|
|
73
179
|
* Refresh access token using refresh token
|
|
74
180
|
* @param {Object} httpClient - HTTP client
|
|
@@ -94,10 +200,6 @@ export async function refresh(httpClient, tokenStore) {
|
|
|
94
200
|
return response;
|
|
95
201
|
}
|
|
96
202
|
|
|
97
|
-
// ============================================================================
|
|
98
|
-
// Logout Operations
|
|
99
|
-
// ============================================================================
|
|
100
|
-
|
|
101
203
|
/**
|
|
102
204
|
* Logout and revoke tokens
|
|
103
205
|
* @param {Object} httpClient - HTTP client
|
|
@@ -123,10 +225,6 @@ export async function logout(httpClient, tokenStore) {
|
|
|
123
225
|
await tokenStore.clearTokens();
|
|
124
226
|
}
|
|
125
227
|
|
|
126
|
-
// ============================================================================
|
|
127
|
-
// User Operations
|
|
128
|
-
// ============================================================================
|
|
129
|
-
|
|
130
228
|
/**
|
|
131
229
|
* Get current user information
|
|
132
230
|
* @param {Object} httpClient - HTTP client
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AuthError } from '../../shared/vizzly-error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate that tokens exist and have required fields
|
|
5
|
+
* @param {Object|null} auth - Auth tokens object
|
|
6
|
+
* @param {string} requiredField - Field that must exist ('accessToken' or 'refreshToken')
|
|
7
|
+
* @returns {{ valid: boolean, error: Error|null }}
|
|
8
|
+
*/
|
|
9
|
+
export function validateTokens(auth, requiredField = 'accessToken') {
|
|
10
|
+
if (!auth?.[requiredField]) {
|
|
11
|
+
let message = requiredField === 'refreshToken' ? 'No refresh token found. Please run "vizzly login" first.' : 'No authentication token found. Please run "vizzly login" first.';
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
error: new AuthError(message)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
valid: true,
|
|
19
|
+
error: null
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* The client handles authentication, token refresh, and error handling.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { buildApiUrl, buildRequestHeaders, buildUserAgent, extractErrorBody, isAuthError, parseApiError } from '../../shared/cloud-protocol.js';
|
|
9
|
+
import { getPackageVersion } from '../../shared/package-info.js';
|
|
10
|
+
import { AuthError, VizzlyError } from '../../shared/vizzly-error.js';
|
|
11
|
+
import { getAuthTokens, saveAuthTokens } from './token-store.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Default API URL
|
|
@@ -85,8 +85,9 @@ export function createApiClient(options = {}) {
|
|
|
85
85
|
let errorBody = await extractErrorBody(response);
|
|
86
86
|
|
|
87
87
|
// Handle 401 with token refresh
|
|
88
|
-
if (retryAuthentication &&
|
|
89
|
-
let
|
|
88
|
+
if (retryAuthentication && response.status === 401 && !isRetry && !isProjectToken(token)) {
|
|
89
|
+
let auth = await getAuthTokens();
|
|
90
|
+
let refreshed = auth?.accessToken === token && auth.refreshToken ? await attemptTokenRefresh(auth) : null;
|
|
90
91
|
if (refreshed) {
|
|
91
92
|
token = refreshed;
|
|
92
93
|
return request(endpoint, fetchOptions, true);
|
|
@@ -95,7 +96,9 @@ export function createApiClient(options = {}) {
|
|
|
95
96
|
|
|
96
97
|
// Auth error
|
|
97
98
|
if (isAuthError(response.status)) {
|
|
98
|
-
throw new AuthError('Invalid or expired API token. Run "vizzly project link <org>/<project>" or set VIZZLY_TOKEN.'
|
|
99
|
+
throw new AuthError('Invalid or expired API token. Run "vizzly project link <org>/<project>" or set VIZZLY_TOKEN.', {
|
|
100
|
+
status: response.status
|
|
101
|
+
});
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// Other errors
|
|
@@ -114,21 +117,11 @@ export function createApiClient(options = {}) {
|
|
|
114
117
|
return response.json();
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
/**
|
|
118
|
-
* Check if refresh token is available
|
|
119
|
-
*/
|
|
120
|
-
async function hasRefreshToken() {
|
|
121
|
-
let auth = await getAuthTokens();
|
|
122
|
-
return !!auth?.refreshToken;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
120
|
/**
|
|
126
121
|
* Attempt to refresh the access token
|
|
127
122
|
* @returns {Promise<string|null>} New token or null if refresh failed
|
|
128
123
|
*/
|
|
129
|
-
async function attemptTokenRefresh() {
|
|
130
|
-
let auth = await getAuthTokens();
|
|
131
|
-
if (!auth?.refreshToken) return null;
|
|
124
|
+
async function attemptTokenRefresh(auth) {
|
|
132
125
|
try {
|
|
133
126
|
let refreshUrl = buildApiUrl(baseUrl, '/api/auth/cli/refresh');
|
|
134
127
|
let response = await fetch(refreshUrl, {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { openBrowser } from '../../shared/browser.js';
|
|
3
|
+
import * as output from '../../shared/output.js';
|
|
4
|
+
import { getApiUrl } from '../config/environment.js';
|
|
5
|
+
import { buildTokens, normalizeDeviceFlowResponse } from './auth.normalizers.js';
|
|
6
|
+
import { resolveAuthorizedTokenData } from './auth.rules.js';
|
|
7
|
+
import { completeDeviceFlow, initiateDeviceFlow, pollDeviceAuthorization } from './auth.service.js';
|
|
8
|
+
import { createAuthClient } from './auth-client.js';
|
|
9
|
+
import { waitForLoginPoll } from './login-wait.js';
|
|
10
|
+
import { createTokenStore } from './token-store.js';
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* Login command implementation
|
|
3
14
|
* Authenticates user via OAuth device flow
|
|
4
15
|
*/
|
|
5
16
|
|
|
6
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
-
import { buildTokens, normalizeDeviceFlowResponse, resolveAuthorizedTokenData } from '../auth/core.js';
|
|
8
|
-
import { completeDeviceFlow, createAuthClient, createTokenStore, initiateDeviceFlow, pollDeviceAuthorization } from '../auth/index.js';
|
|
9
|
-
import { openBrowser } from '../utils/browser.js';
|
|
10
|
-
import { getApiUrl } from '../utils/environment-config.js';
|
|
11
|
-
import { waitForLoginPoll } from '../utils/login-poll-wait.js';
|
|
12
|
-
import * as output from '../utils/output.js';
|
|
13
|
-
|
|
14
17
|
/**
|
|
15
18
|
* Login command implementation using OAuth device flow
|
|
16
19
|
* @param {Object} options - Command options
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
2
|
+
import { getApiUrl as defaultGetApiUrl } from '../config/environment.js';
|
|
3
|
+
import { logout as defaultLogout } from './auth.service.js';
|
|
4
|
+
import { createAuthClient as defaultCreateAuthClient } from './auth-client.js';
|
|
5
|
+
import { createTokenStore as defaultCreateTokenStore, getAuthTokens as defaultGetAuthTokens } from './token-store.js';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Logout command implementation
|
|
3
9
|
* Clears stored authentication tokens
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
|
-
import { createAuthClient as defaultCreateAuthClient, createTokenStore as defaultCreateTokenStore, getAuthTokens as defaultGetAuthTokens, logout as defaultLogout } from '../auth/index.js';
|
|
7
|
-
import { getApiUrl as defaultGetApiUrl } from '../utils/environment-config.js';
|
|
8
|
-
import * as defaultOutput from '../utils/output.js';
|
|
9
|
-
|
|
10
12
|
/**
|
|
11
13
|
* Logout command implementation
|
|
12
14
|
* @param {Object} options - Command options
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { loadGlobalConfig, saveGlobalConfig } from '../../shared/global-config.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get authentication tokens from global config
|
|
5
|
+
* @returns {Promise<Object|null>} Token object with accessToken, refreshToken, expiresAt, user, or null if not found
|
|
6
|
+
*/
|
|
7
|
+
export async function getAuthTokens() {
|
|
8
|
+
let config = await loadGlobalConfig();
|
|
9
|
+
if (!config.auth?.accessToken) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return config.auth;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Save authentication tokens to global config
|
|
17
|
+
* @param {Object} auth - Auth object with accessToken, refreshToken, expiresAt, user
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
export async function saveAuthTokens(auth) {
|
|
21
|
+
let config = await loadGlobalConfig();
|
|
22
|
+
config.auth = {
|
|
23
|
+
accessToken: auth.accessToken,
|
|
24
|
+
refreshToken: auth.refreshToken,
|
|
25
|
+
expiresAt: auth.expiresAt,
|
|
26
|
+
user: auth.user
|
|
27
|
+
};
|
|
28
|
+
await saveGlobalConfig(config);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Clear authentication tokens from global config
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
export async function clearAuthTokens() {
|
|
36
|
+
let config = await loadGlobalConfig();
|
|
37
|
+
delete config.auth;
|
|
38
|
+
await saveGlobalConfig(config);
|
|
39
|
+
}
|
|
40
|
+
function hasValidAccessToken(auth, now = Date.now()) {
|
|
41
|
+
if (!auth?.accessToken) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (!auth.expiresAt) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
let expiresAt = new Date(auth.expiresAt).getTime();
|
|
48
|
+
// Treat tokens expiring within five minutes as unavailable.
|
|
49
|
+
let bufferMs = 5 * 60 * 1000;
|
|
50
|
+
return now < expiresAt - bufferMs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if authentication tokens exist and are not expired
|
|
55
|
+
* @returns {Promise<boolean>} True if valid tokens exist
|
|
56
|
+
*/
|
|
57
|
+
export async function hasValidTokens() {
|
|
58
|
+
let auth = await getAuthTokens();
|
|
59
|
+
return hasValidAccessToken(auth);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the access token from global config if valid and not expired
|
|
64
|
+
* @returns {Promise<string|null>} Access token or null
|
|
65
|
+
*/
|
|
66
|
+
export async function getAccessToken() {
|
|
67
|
+
let auth = await getAuthTokens();
|
|
68
|
+
return hasValidAccessToken(auth) ? auth.accessToken : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a token store adapter from global-config functions
|
|
73
|
+
* Used by auth operations that need tokenStore parameter
|
|
74
|
+
*/
|
|
75
|
+
export function createTokenStore() {
|
|
76
|
+
return {
|
|
77
|
+
getTokens: getAuthTokens,
|
|
78
|
+
saveTokens: saveAuthTokens,
|
|
79
|
+
clearTokens: clearAuthTokens
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
2
|
+
import { getApiUrl as defaultGetApiUrl } from '../config/environment.js';
|
|
3
|
+
import { whoami as defaultWhoami } from './auth.service.js';
|
|
4
|
+
import { createAuthClient as defaultCreateAuthClient } from './auth-client.js';
|
|
5
|
+
import { createTokenStore as defaultCreateTokenStore, getAuthTokens as defaultGetAuthTokens } from './token-store.js';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Whoami command implementation
|
|
3
9
|
* Shows current user and authentication status
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
|
-
import { createAuthClient as defaultCreateAuthClient, createTokenStore as defaultCreateTokenStore, getAuthTokens as defaultGetAuthTokens, whoami as defaultWhoami } from '../auth/index.js';
|
|
7
|
-
import { getApiUrl as defaultGetApiUrl } from '../utils/environment-config.js';
|
|
8
|
-
import * as defaultOutput from '../utils/output.js';
|
|
9
12
|
export function getTokenExpiryStatus(expiresAt, now = new Date()) {
|
|
10
13
|
let expiresDate = new Date(expiresAt);
|
|
11
14
|
let msUntilExpiry = expiresDate.getTime() - now.getTime();
|