d081af0422
* auto-claude: subtask-1-1 - Add GITHUB_AUTH_CHANGED IPC channel constant * auto-claude: subtask-1-2 - Add async getGitHubTokenForSubprocess() helper to utils.ts Add a new exported async function getGitHubTokenForSubprocess() that calls getTokenFromGhCli() to retrieve fresh GitHub tokens for subprocess use. This provides a clean interface for runner-env.ts to get tokens without caching, ensuring account changes are reflected immediately. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-3 - Update getRunnerEnv() to include GITHUB_TOKEN * auto-claude: subtask-2-1 - Add auth change detection and event emission to oauth-handlers.ts - Add GitHubAuthChangedPayload interface for auth change events - Add sendAuthChangedToRenderer() to broadcast auth changes to all windows - Add getCurrentGitHubUsername() helper to get current GitHub user - Modify registerStartGhAuth() to: - Capture username before auth starts - Get username after successful auth - Emit GITHUB_AUTH_CHANGED event if account changed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-3-1 - Add onGitHubAuthChanged listener to GitHubAPI interface - Add onGitHubAuthChanged to GitHubAPI interface in github-api.ts - Add implementation using createIpcListener with IPC_CHANNELS.GITHUB_AUTH_CHANGED - Add mock implementation in browser-mock.ts for testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-1 - Add GitHub auth change listener to pr-review-store * fix: Address PR review findings for GitHub auth handlers - Convert getCurrentGitHubUsername() to async using promisified execFile to avoid blocking Electron main thread during auth flow (finding #1) - Make getGitHubTokenForSubprocess() truly async by introducing async getTokenFromGhCliAsync() - the sync version is preserved for getGitHubConfig (finding #2) - Add warning log when username fetch fails after successful auth, handling the edge case where auth succeeds but account change detection fails (finding #3) - Remove unused timestamp field from GitHubAuthChangedPayload interface since the renderer callback only uses oldUsername/newUsername (finding #4) - Document the intentional extraEnv override behavior in getRunnerEnv() JSDoc comment (finding #5) All 5 findings from PR review were real issues. These fixes improve code quality by avoiding main thread blocking and clarifying edge cases. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: Fix oauth-handlers tests for async getCurrentGitHubUsername Update test mocks and add waitForAsyncSetup helper to handle the async changes in getCurrentGitHubUsername(). The function now uses promisified execFile instead of execFileSync to avoid blocking the main thread. Key changes: - Add mockExecFile mock for the promisified execFile function - Add waitForAsyncSetup helper to wait for async setup before emitting process events - Update all affected tests to use waitForAsyncSetup before emitting mock process events Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
366 lines
11 KiB
TypeScript
366 lines
11 KiB
TypeScript
/**
|
|
* Browser mock for window.electronAPI
|
|
* This allows the app to run in a regular browser for UI development/testing
|
|
*
|
|
* This module aggregates all mock implementations from separate modules
|
|
* for better code organization and maintainability.
|
|
*/
|
|
|
|
import type { ElectronAPI } from '../../shared/types';
|
|
import {
|
|
projectMock,
|
|
taskMock,
|
|
workspaceMock,
|
|
terminalMock,
|
|
claudeProfileMock,
|
|
contextMock,
|
|
integrationMock,
|
|
changelogMock,
|
|
insightsMock,
|
|
infrastructureMock,
|
|
settingsMock
|
|
} from './mocks';
|
|
|
|
// Check if we're in a browser (not Electron)
|
|
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined;
|
|
|
|
/**
|
|
* Create mock electronAPI for browser
|
|
* Aggregates all mock implementations from separate modules
|
|
*/
|
|
const browserMockAPI: ElectronAPI = {
|
|
// Project Operations
|
|
...projectMock,
|
|
|
|
// Task Operations
|
|
...taskMock,
|
|
|
|
// Workspace Management
|
|
...workspaceMock,
|
|
|
|
// Terminal Operations
|
|
...terminalMock,
|
|
|
|
// Claude Profile Management
|
|
...claudeProfileMock,
|
|
|
|
// Settings
|
|
...settingsMock,
|
|
|
|
// Roadmap Operations
|
|
getRoadmap: async () => ({
|
|
success: true,
|
|
data: null
|
|
}),
|
|
|
|
getRoadmapStatus: async () => ({
|
|
success: true,
|
|
data: { isRunning: false }
|
|
}),
|
|
|
|
saveRoadmap: async () => ({
|
|
success: true
|
|
}),
|
|
|
|
generateRoadmap: (_projectId: string, _enableCompetitorAnalysis?: boolean, _refreshCompetitorAnalysis?: boolean) => {
|
|
console.warn('[Browser Mock] generateRoadmap called');
|
|
},
|
|
|
|
refreshRoadmap: (_projectId: string, _enableCompetitorAnalysis?: boolean, _refreshCompetitorAnalysis?: boolean) => {
|
|
console.warn('[Browser Mock] refreshRoadmap called');
|
|
},
|
|
|
|
updateFeatureStatus: async () => ({ success: true }),
|
|
|
|
convertFeatureToSpec: async (projectId: string, _featureId: string) => ({
|
|
success: true,
|
|
data: {
|
|
id: `task-${Date.now()}`,
|
|
specId: '',
|
|
projectId,
|
|
title: 'Converted Feature',
|
|
description: 'Feature converted from roadmap',
|
|
status: 'backlog' as const,
|
|
subtasks: [],
|
|
logs: [],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
}
|
|
}),
|
|
|
|
stopRoadmap: async () => ({ success: true }),
|
|
|
|
// Roadmap Progress Persistence
|
|
saveRoadmapProgress: async () => ({ success: true }),
|
|
loadRoadmapProgress: async () => ({ success: true, data: null }),
|
|
clearRoadmapProgress: async () => ({ success: true }),
|
|
|
|
// Roadmap Event Listeners
|
|
onRoadmapProgress: () => () => {},
|
|
onRoadmapComplete: () => () => {},
|
|
onRoadmapError: () => () => {},
|
|
onRoadmapStopped: () => () => {},
|
|
// Context Operations
|
|
...contextMock,
|
|
|
|
// Environment Configuration & Integration Operations
|
|
...integrationMock,
|
|
|
|
// Changelog & Release Operations
|
|
...changelogMock,
|
|
|
|
// Insights Operations
|
|
...insightsMock,
|
|
|
|
// Infrastructure & Docker Operations
|
|
...infrastructureMock,
|
|
|
|
// API Profile Management (custom Anthropic-compatible endpoints)
|
|
getAPIProfiles: async () => ({
|
|
success: true,
|
|
data: {
|
|
profiles: [],
|
|
activeProfileId: null,
|
|
version: 1
|
|
}
|
|
}),
|
|
|
|
saveAPIProfile: async (profile) => ({
|
|
success: true,
|
|
data: {
|
|
id: `mock-profile-${Date.now()}`,
|
|
...profile,
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now()
|
|
}
|
|
}),
|
|
|
|
updateAPIProfile: async (profile) => ({
|
|
success: true,
|
|
data: {
|
|
...profile,
|
|
updatedAt: Date.now()
|
|
}
|
|
}),
|
|
|
|
deleteAPIProfile: async (_profileId: string) => ({
|
|
success: true
|
|
}),
|
|
|
|
setActiveAPIProfile: async (_profileId: string | null) => ({
|
|
success: true
|
|
}),
|
|
|
|
testConnection: async (_baseUrl: string, _apiKey: string, _signal?: AbortSignal) => ({
|
|
success: true,
|
|
data: {
|
|
success: true,
|
|
message: 'Connection successful (mock)'
|
|
}
|
|
}),
|
|
|
|
discoverModels: async (_baseUrl: string, _apiKey: string, _signal?: AbortSignal) => ({
|
|
success: true,
|
|
data: {
|
|
models: []
|
|
}
|
|
}),
|
|
|
|
// GitHub API
|
|
github: {
|
|
getGitHubRepositories: async () => ({ success: true, data: [] }),
|
|
getGitHubIssues: async () => ({ success: true, data: { issues: [], hasMore: false } }),
|
|
getGitHubIssue: async () => ({ success: true, data: null as any }),
|
|
getIssueComments: async () => ({ success: true, data: [] }),
|
|
checkGitHubConnection: async () => ({ success: true, data: { connected: false, repoFullName: undefined, error: undefined } }),
|
|
investigateGitHubIssue: () => {},
|
|
importGitHubIssues: async () => ({ success: true, data: { success: true, imported: 0, failed: 0, issues: [] } }),
|
|
createGitHubRelease: async () => ({ success: true, data: { url: '' } }),
|
|
suggestReleaseVersion: async () => ({ success: true, data: { suggestedVersion: '1.0.0', currentVersion: '0.0.0', bumpType: 'minor' as const, commitCount: 0, reason: 'Initial' } }),
|
|
checkGitHubCli: async () => ({ success: true, data: { installed: false } }),
|
|
checkGitHubAuth: async () => ({ success: true, data: { authenticated: false } }),
|
|
startGitHubAuth: async () => ({ success: true, data: { success: false } }),
|
|
getGitHubToken: async () => ({ success: true, data: { token: '' } }),
|
|
getGitHubUser: async () => ({ success: true, data: { username: '' } }),
|
|
listGitHubUserRepos: async () => ({ success: true, data: { repos: [] } }),
|
|
detectGitHubRepo: async () => ({ success: true, data: '' }),
|
|
getGitHubBranches: async () => ({ success: true, data: [] }),
|
|
createGitHubRepo: async () => ({ success: true, data: { fullName: '', url: '' } }),
|
|
addGitRemote: async () => ({ success: true, data: { remoteUrl: '' } }),
|
|
listGitHubOrgs: async () => ({ success: true, data: { orgs: [] } }),
|
|
onGitHubAuthDeviceCode: () => () => {},
|
|
onGitHubAuthChanged: () => () => {},
|
|
onGitHubInvestigationProgress: () => () => {},
|
|
onGitHubInvestigationComplete: () => () => {},
|
|
onGitHubInvestigationError: () => () => {},
|
|
getAutoFixConfig: async () => null,
|
|
saveAutoFixConfig: async () => true,
|
|
getAutoFixQueue: async () => [],
|
|
checkAutoFixLabels: async () => [],
|
|
checkNewIssues: async () => [],
|
|
startAutoFix: () => {},
|
|
onAutoFixProgress: () => () => {},
|
|
onAutoFixComplete: () => () => {},
|
|
onAutoFixError: () => () => {},
|
|
listPRs: async () => [],
|
|
getPR: async () => null,
|
|
runPRReview: () => {},
|
|
cancelPRReview: async () => true,
|
|
postPRReview: async () => true,
|
|
postPRComment: async () => true,
|
|
mergePR: async () => true,
|
|
assignPR: async () => true,
|
|
markReviewPosted: async () => true,
|
|
getPRReview: async () => null,
|
|
getPRReviewsBatch: async () => ({}),
|
|
deletePRReview: async () => true,
|
|
checkNewCommits: async () => ({ hasNewCommits: false, newCommitCount: 0 }),
|
|
checkMergeReadiness: async () => ({ isDraft: false, mergeable: 'UNKNOWN' as const, isBehind: false, ciStatus: 'none' as const, blockers: [] }),
|
|
updatePRBranch: async () => ({ success: true }),
|
|
runFollowupReview: () => {},
|
|
getPRLogs: async () => null,
|
|
getWorkflowsAwaitingApproval: async () => ({ awaiting_approval: 0, workflow_runs: [], can_approve: false }),
|
|
approveWorkflow: async () => true,
|
|
onPRReviewProgress: () => () => {},
|
|
onPRReviewComplete: () => () => {},
|
|
onPRReviewError: () => () => {},
|
|
batchAutoFix: () => {},
|
|
getBatches: async () => [],
|
|
onBatchProgress: () => () => {},
|
|
onBatchComplete: () => () => {},
|
|
onBatchError: () => () => {},
|
|
// Analyze & Group Issues (proactive workflow)
|
|
analyzeIssuesPreview: () => {},
|
|
approveBatches: async () => ({ success: true, batches: [] }),
|
|
onAnalyzePreviewProgress: () => () => {},
|
|
onAnalyzePreviewComplete: () => () => {},
|
|
onAnalyzePreviewError: () => () => {}
|
|
},
|
|
|
|
// Claude Code Operations
|
|
checkClaudeCodeVersion: async () => ({
|
|
success: true,
|
|
data: {
|
|
installed: '1.0.0',
|
|
latest: '1.0.0',
|
|
isOutdated: false,
|
|
path: '/usr/local/bin/claude',
|
|
detectionResult: {
|
|
found: true,
|
|
version: '1.0.0',
|
|
path: '/usr/local/bin/claude',
|
|
source: 'system-path' as const,
|
|
message: 'Claude Code CLI found'
|
|
}
|
|
}
|
|
}),
|
|
installClaudeCode: async () => ({
|
|
success: true,
|
|
data: { command: 'npm install -g @anthropic-ai/claude-code' }
|
|
}),
|
|
getClaudeCodeVersions: async () => ({
|
|
success: true,
|
|
data: {
|
|
versions: ['1.0.5', '1.0.4', '1.0.3', '1.0.2', '1.0.1', '1.0.0']
|
|
}
|
|
}),
|
|
installClaudeCodeVersion: async (version: string) => ({
|
|
success: true,
|
|
data: { command: `npm install -g @anthropic-ai/claude-code@${version}`, version }
|
|
}),
|
|
getClaudeCodeInstallations: async () => ({
|
|
success: true,
|
|
data: {
|
|
installations: [
|
|
{
|
|
path: '/usr/local/bin/claude',
|
|
version: '1.0.0',
|
|
source: 'system-path' as const,
|
|
isActive: true,
|
|
}
|
|
],
|
|
activePath: '/usr/local/bin/claude',
|
|
}
|
|
}),
|
|
setClaudeCodeActivePath: async (cliPath: string) => ({
|
|
success: true,
|
|
data: { path: cliPath }
|
|
}),
|
|
|
|
// Terminal Worktree Operations
|
|
createTerminalWorktree: async () => ({
|
|
success: false,
|
|
error: 'Not available in browser mode'
|
|
}),
|
|
listTerminalWorktrees: async () => ({
|
|
success: true,
|
|
data: []
|
|
}),
|
|
removeTerminalWorktree: async () => ({
|
|
success: false,
|
|
error: 'Not available in browser mode'
|
|
}),
|
|
listOtherWorktrees: async () => ({
|
|
success: true,
|
|
data: []
|
|
}),
|
|
|
|
// MCP Server Health Check Operations
|
|
checkMcpHealth: async (server) => ({
|
|
success: true,
|
|
data: {
|
|
serverId: server.id,
|
|
status: 'unknown' as const,
|
|
message: 'Health check not available in browser mode',
|
|
checkedAt: new Date().toISOString()
|
|
}
|
|
}),
|
|
testMcpConnection: async (server) => ({
|
|
success: true,
|
|
data: {
|
|
serverId: server.id,
|
|
success: false,
|
|
message: 'Connection test not available in browser mode'
|
|
}
|
|
}),
|
|
|
|
// Screenshot capture operations
|
|
getSources: async () => ({
|
|
success: true,
|
|
data: []
|
|
}),
|
|
capture: async (_options: { sourceId: string }) => ({
|
|
success: false,
|
|
error: 'Screenshot capture not available in browser mode'
|
|
}),
|
|
|
|
// Debug Operations
|
|
getDebugInfo: async () => ({
|
|
systemInfo: {
|
|
appVersion: '0.0.0-browser-mock',
|
|
platform: 'browser',
|
|
isPackaged: 'false'
|
|
},
|
|
recentErrors: [],
|
|
logsPath: '/mock/logs',
|
|
debugReport: '[Browser Mock] Debug report not available in browser mode'
|
|
}),
|
|
openLogsFolder: async () => ({ success: false, error: 'Not available in browser mode' }),
|
|
copyDebugInfo: async () => ({ success: false, error: 'Not available in browser mode' }),
|
|
getRecentErrors: async () => [],
|
|
listLogFiles: async () => []
|
|
};
|
|
|
|
/**
|
|
* Initialize browser mock if not running in Electron
|
|
*/
|
|
export function initBrowserMock(): void {
|
|
if (!isElectron) {
|
|
console.warn('%c[Browser Mock] Initializing mock electronAPI for browser preview', 'color: #f0ad4e; font-weight: bold;');
|
|
(window as Window & { electronAPI: ElectronAPI }).electronAPI = browserMockAPI;
|
|
}
|
|
}
|
|
|
|
// Auto-initialize
|
|
initBrowserMock();
|