mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
Merge remote-tracking branch 'origin/v0.14.0rc' into feature/v0.14.0rc-1768981415660-tt2v
# Conflicts: # apps/ui/src/components/views/project-settings-view/config/navigation.ts # apps/ui/src/components/views/project-settings-view/hooks/use-project-settings-view.ts
This commit is contained in:
@@ -2063,6 +2063,52 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Test runner methods
|
||||
startTests: async (
|
||||
worktreePath: string,
|
||||
options?: { projectPath?: string; testFile?: string }
|
||||
) => {
|
||||
console.log('[Mock] Starting tests:', { worktreePath, options });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
sessionId: 'mock-session-123',
|
||||
worktreePath,
|
||||
command: 'npm run test',
|
||||
status: 'running' as const,
|
||||
testFile: options?.testFile,
|
||||
message: 'Tests started (mock)',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
stopTests: async (sessionId: string) => {
|
||||
console.log('[Mock] Stopping tests:', { sessionId });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
sessionId,
|
||||
message: 'Tests stopped (mock)',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
getTestLogs: async (worktreePath?: string, sessionId?: string) => {
|
||||
console.log('[Mock] Getting test logs:', { worktreePath, sessionId });
|
||||
return {
|
||||
success: false,
|
||||
error: 'No test sessions found (mock)',
|
||||
};
|
||||
},
|
||||
|
||||
onTestRunnerEvent: (callback) => {
|
||||
console.log('[Mock] Subscribing to test runner events');
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
console.log('[Mock] Unsubscribing from test runner events');
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3412,6 +3458,11 @@ export interface Project {
|
||||
* If a phase is not present, the global setting is used.
|
||||
*/
|
||||
phaseModelOverrides?: Partial<import('@automaker/types').PhaseModelConfig>;
|
||||
/**
|
||||
* Override the default model for new feature cards in this project.
|
||||
* If not specified, falls back to the global defaultFeatureModel setting.
|
||||
*/
|
||||
defaultFeatureModel?: import('@automaker/types').PhaseModelEntry;
|
||||
}
|
||||
|
||||
export interface TrashedProject extends Project {
|
||||
|
||||
@@ -562,6 +562,9 @@ type EventType =
|
||||
| 'dev-server:started'
|
||||
| 'dev-server:output'
|
||||
| 'dev-server:stopped'
|
||||
| 'test-runner:started'
|
||||
| 'test-runner:output'
|
||||
| 'test-runner:completed'
|
||||
| 'notification:created';
|
||||
|
||||
/**
|
||||
@@ -593,6 +596,44 @@ export type DevServerLogEvent =
|
||||
| { type: 'dev-server:output'; payload: DevServerOutputEvent }
|
||||
| { type: 'dev-server:stopped'; payload: DevServerStoppedEvent };
|
||||
|
||||
/**
|
||||
* Test runner event payloads for WebSocket streaming
|
||||
*/
|
||||
export type TestRunStatus = 'pending' | 'running' | 'passed' | 'failed' | 'cancelled' | 'error';
|
||||
|
||||
export interface TestRunnerStartedEvent {
|
||||
sessionId: string;
|
||||
worktreePath: string;
|
||||
/** The test command being run (from project settings) */
|
||||
command: string;
|
||||
testFile?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface TestRunnerOutputEvent {
|
||||
sessionId: string;
|
||||
worktreePath: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface TestRunnerCompletedEvent {
|
||||
sessionId: string;
|
||||
worktreePath: string;
|
||||
/** The test command that was run */
|
||||
command: string;
|
||||
status: TestRunStatus;
|
||||
testFile?: string;
|
||||
exitCode: number | null;
|
||||
duration: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export type TestRunnerEvent =
|
||||
| { type: 'test-runner:started'; payload: TestRunnerStartedEvent }
|
||||
| { type: 'test-runner:output'; payload: TestRunnerOutputEvent }
|
||||
| { type: 'test-runner:completed'; payload: TestRunnerCompletedEvent };
|
||||
|
||||
/**
|
||||
* Response type for fetching dev server logs
|
||||
*/
|
||||
@@ -608,6 +649,26 @@ export interface DevServerLogsResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response type for fetching test logs
|
||||
*/
|
||||
export interface TestLogsResponse {
|
||||
success: boolean;
|
||||
result?: {
|
||||
sessionId: string;
|
||||
worktreePath: string;
|
||||
/** The test command that was/is being run */
|
||||
command: string;
|
||||
status: TestRunStatus;
|
||||
testFile?: string;
|
||||
logs: string;
|
||||
startedAt: string;
|
||||
finishedAt: string | null;
|
||||
exitCode: number | null;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
type EventCallback = (payload: unknown) => void;
|
||||
|
||||
interface EnhancePromptResult {
|
||||
@@ -2001,6 +2062,32 @@ export class HttpApiClient implements ElectronAPI {
|
||||
unsub3();
|
||||
};
|
||||
},
|
||||
// Test runner methods
|
||||
startTests: (worktreePath: string, options?: { projectPath?: string; testFile?: string }) =>
|
||||
this.post('/api/worktree/start-tests', { worktreePath, ...options }),
|
||||
stopTests: (sessionId: string) => this.post('/api/worktree/stop-tests', { sessionId }),
|
||||
getTestLogs: (worktreePath?: string, sessionId?: string): Promise<TestLogsResponse> => {
|
||||
const params = new URLSearchParams();
|
||||
if (worktreePath) params.append('worktreePath', worktreePath);
|
||||
if (sessionId) params.append('sessionId', sessionId);
|
||||
return this.get(`/api/worktree/test-logs?${params.toString()}`);
|
||||
},
|
||||
onTestRunnerEvent: (callback: (event: TestRunnerEvent) => void) => {
|
||||
const unsub1 = this.subscribeToEvent('test-runner:started', (payload) =>
|
||||
callback({ type: 'test-runner:started', payload: payload as TestRunnerStartedEvent })
|
||||
);
|
||||
const unsub2 = this.subscribeToEvent('test-runner:output', (payload) =>
|
||||
callback({ type: 'test-runner:output', payload: payload as TestRunnerOutputEvent })
|
||||
);
|
||||
const unsub3 = this.subscribeToEvent('test-runner:completed', (payload) =>
|
||||
callback({ type: 'test-runner:completed', payload: payload as TestRunnerCompletedEvent })
|
||||
);
|
||||
return () => {
|
||||
unsub1();
|
||||
unsub2();
|
||||
unsub3();
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Git API
|
||||
@@ -2362,6 +2449,7 @@ export class HttpApiClient implements ElectronAPI {
|
||||
defaultDeleteBranchWithWorktree?: boolean;
|
||||
autoDismissInitScriptIndicator?: boolean;
|
||||
lastSelectedSessionId?: string;
|
||||
testCommand?: string;
|
||||
};
|
||||
error?: string;
|
||||
}> => this.post('/api/settings/project', { projectPath }),
|
||||
|
||||
@@ -156,35 +156,23 @@ export function sanitizeForTestId(name: string): string {
|
||||
/**
|
||||
* Generate a UUID v4 string.
|
||||
*
|
||||
* Uses crypto.randomUUID() when available (secure contexts: HTTPS or localhost).
|
||||
* Falls back to crypto.getRandomValues() for non-secure contexts (e.g., Docker via HTTP).
|
||||
* Uses crypto.getRandomValues() which works in all modern browsers,
|
||||
* including non-secure contexts (e.g., Docker via HTTP).
|
||||
*
|
||||
* @returns A RFC 4122 compliant UUID v4 string (e.g., "550e8400-e29b-41d4-a716-446655440000")
|
||||
*/
|
||||
export function generateUUID(): string {
|
||||
// Use native randomUUID if available (secure contexts: HTTPS or localhost)
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
if (typeof crypto === 'undefined' || typeof crypto.getRandomValues === 'undefined') {
|
||||
throw new Error('Cryptographically secure random number generator not available.');
|
||||
}
|
||||
const bytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
// Fallback using crypto.getRandomValues() (works in all modern browsers, including non-secure contexts)
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
||||
const bytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(bytes);
|
||||
// Set version (4) and variant (RFC 4122) bits
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC 4122
|
||||
|
||||
// Set version (4) and variant (RFC 4122) bits
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC 4122
|
||||
|
||||
// Convert to hex string with proper UUID format
|
||||
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
// Last resort fallback using Math.random() - less secure but ensures functionality
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
// Convert to hex string with proper UUID format
|
||||
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user