@vizzly-testing/cli
Version:
Visual review platform for UI developers and designers
228 lines (212 loc) • 6.43 kB
JavaScript
/**
* Dynamic context detection for CLI commands
*
* Detects the current state of Vizzly in the working directory:
* - TDD server status
* - Project configuration
* - Authentication status
* - Baseline counts
*/
import { existsSync, readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
/**
* Get dynamic context about the current Vizzly state
* Returns an array of context items with type, label, and value
*
* @returns {Array<{type: 'success'|'warning'|'info', label: string, value: string}>}
*/
export function getContext() {
let items = [];
try {
let cwd = process.cwd();
let globalConfigPath = join(process.env.VIZZLY_HOME || join(homedir(), '.vizzly'), 'config.json');
// Load global config once
let globalConfig = {};
try {
if (existsSync(globalConfigPath)) {
globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf8'));
}
} catch {
// Ignore
}
// Check for vizzly.config.js (project config)
let hasProjectConfig = existsSync(join(cwd, 'vizzly.config.js'));
// Check for .vizzly directory (TDD baselines)
let baselineCount = 0;
try {
let metaPath = join(cwd, '.vizzly', 'baselines', 'metadata.json');
if (existsSync(metaPath)) {
let meta = JSON.parse(readFileSync(metaPath, 'utf8'));
baselineCount = meta.screenshots?.length || 0;
}
} catch {
// Ignore
}
// Check for TDD server running
let serverRunning = false;
let serverPort = null;
try {
let serverFile = join(cwd, '.vizzly', 'server.json');
if (existsSync(serverFile)) {
let serverInfo = JSON.parse(readFileSync(serverFile, 'utf8'));
serverPort = serverInfo.port;
serverRunning = true;
}
} catch {
// Ignore
}
// Check for project mapping (from vizzly project:select)
// Traverse up to find project config, with bounds check for Windows compatibility
let projectMapping = null;
let checkPath = cwd;
let prevPath = null;
while (checkPath && checkPath !== prevPath) {
if (globalConfig.projects?.[checkPath]) {
projectMapping = globalConfig.projects[checkPath];
break;
}
prevPath = checkPath;
checkPath = dirname(checkPath);
}
// Check for OAuth login (from vizzly login)
let isLoggedIn = !!globalConfig.auth?.accessToken;
let userName = globalConfig.auth?.user?.name || globalConfig.auth?.user?.email;
// Check for env token
let hasEnvToken = !!process.env.VIZZLY_TOKEN;
// Build context items - prioritize most useful info
if (serverRunning) {
items.push({
type: 'success',
label: 'TDD Server',
value: `running on :${serverPort}`
});
}
if (projectMapping) {
items.push({
type: 'success',
label: 'Project',
value: `${projectMapping.projectName} (${projectMapping.organizationSlug})`
});
} else if (isLoggedIn && userName) {
items.push({
type: 'success',
label: 'Logged in',
value: userName
});
} else if (hasEnvToken) {
items.push({
type: 'success',
label: 'API Token',
value: 'via VIZZLY_TOKEN'
});
} else {
items.push({
type: 'info',
label: 'Not connected',
value: 'run vizzly login or project:select'
});
}
if (baselineCount > 0) {
items.push({
type: 'success',
label: 'Baselines',
value: `${baselineCount} screenshots`
});
}
if (!hasProjectConfig && !serverRunning && baselineCount === 0) {
// Only show "no config" hint if there's nothing else useful
items.push({
type: 'info',
label: 'Get started',
value: 'run vizzly init'
});
}
} catch {
// If anything fails, just return empty - context is optional
}
return items;
}
/**
* Get detailed context with raw values (for doctor command)
* Returns more detailed information suitable for diagnostics
*
* @returns {Object} Detailed context object
*/
export function getDetailedContext() {
let cwd = process.cwd();
let globalConfigPath = join(process.env.VIZZLY_HOME || join(homedir(), '.vizzly'), 'config.json');
let context = {
tddServer: {
running: false,
port: null
},
project: {
hasConfig: false,
mapping: null
},
auth: {
loggedIn: false,
userName: null,
hasEnvToken: false
},
baselines: {
count: 0,
path: null
}
};
try {
// Load global config
let globalConfig = {};
try {
if (existsSync(globalConfigPath)) {
globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf8'));
}
} catch {
// Ignore
}
// Check for vizzly.config.js
context.project.hasConfig = existsSync(join(cwd, 'vizzly.config.js'));
// Check for baselines
try {
let metaPath = join(cwd, '.vizzly', 'baselines', 'metadata.json');
if (existsSync(metaPath)) {
let meta = JSON.parse(readFileSync(metaPath, 'utf8'));
context.baselines.count = meta.screenshots?.length || 0;
context.baselines.path = join(cwd, '.vizzly', 'baselines');
}
} catch {
// Ignore
}
// Check for TDD server
try {
let serverFile = join(cwd, '.vizzly', 'server.json');
if (existsSync(serverFile)) {
let serverInfo = JSON.parse(readFileSync(serverFile, 'utf8'));
context.tddServer.running = true;
context.tddServer.port = serverInfo.port;
}
} catch {
// Ignore
}
// Check for project mapping
// Traverse up to find project config, with bounds check for Windows compatibility
let checkPath = cwd;
let prevPath = null;
while (checkPath && checkPath !== prevPath) {
if (globalConfig.projects?.[checkPath]) {
context.project.mapping = globalConfig.projects[checkPath];
break;
}
prevPath = checkPath;
checkPath = dirname(checkPath);
}
// Check auth status
context.auth.loggedIn = !!globalConfig.auth?.accessToken;
context.auth.userName = globalConfig.auth?.user?.name || globalConfig.auth?.user?.email;
context.auth.hasEnvToken = !!process.env.VIZZLY_TOKEN;
} catch {
// If anything fails, return defaults
}
return context;
}