mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-17 22:13:08 +00:00
* feat: Add PR review comments and resolution endpoints, improve prompt handling * Feature: File Editor (#789) * feat: Add file management feature * feat: Add auto-save functionality to file editor * fix: Replace HardDriveDownload icon with Save icon for consistency * fix: Prevent recursive copy/move and improve shell injection prevention * refactor: Extract editor settings form into separate component * ``` fix: Improve error handling and stabilize async operations - Add error event handlers to GraphQL process spawns to prevent unhandled rejections - Replace execAsync with execFile for safer command execution and better control - Fix timeout cleanup in withTimeout generator to prevent memory leaks - Improve outdated comment detection logic by removing redundant condition - Use resolveModelString for consistent model string handling - Replace || with ?? for proper falsy value handling in dialog initialization - Add comments clarifying branch name resolution logic for local branches with slashes - Add catch handler for project selection to handle async errors gracefully ``` * refactor: Extract PR review comments logic to dedicated service * fix: Improve robustness and UX for PR review and file operations * fix: Consolidate exec utilities and improve type safety * refactor: Replace ScrollArea with div and improve file tree layout
104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
/**
|
|
* GitHub PR Comment Service
|
|
*
|
|
* Domain logic for resolving/unresolving PR review threads via the
|
|
* GitHub GraphQL API. Extracted from the route handler so the route
|
|
* only deals with request/response plumbing.
|
|
*/
|
|
|
|
import { spawn } from 'child_process';
|
|
import { execEnv } from '../lib/exec-utils.js';
|
|
|
|
/** Timeout for GitHub GraphQL API requests in milliseconds */
|
|
const GITHUB_API_TIMEOUT_MS = 30000;
|
|
|
|
interface GraphQLMutationResponse {
|
|
data?: {
|
|
resolveReviewThread?: {
|
|
thread?: { isResolved: boolean; id: string } | null;
|
|
} | null;
|
|
unresolveReviewThread?: {
|
|
thread?: { isResolved: boolean; id: string } | null;
|
|
} | null;
|
|
};
|
|
errors?: Array<{ message: string }>;
|
|
}
|
|
|
|
/**
|
|
* Execute a GraphQL mutation to resolve or unresolve a review thread.
|
|
*/
|
|
export async function executeReviewThreadMutation(
|
|
projectPath: string,
|
|
threadId: string,
|
|
resolve: boolean
|
|
): Promise<{ isResolved: boolean }> {
|
|
const mutationName = resolve ? 'resolveReviewThread' : 'unresolveReviewThread';
|
|
|
|
const mutation = `
|
|
mutation ${resolve ? 'ResolveThread' : 'UnresolveThread'}($threadId: ID!) {
|
|
${mutationName}(input: { threadId: $threadId }) {
|
|
thread {
|
|
id
|
|
isResolved
|
|
}
|
|
}
|
|
}`;
|
|
|
|
const variables = { threadId };
|
|
const requestBody = JSON.stringify({ query: mutation, variables });
|
|
|
|
// Declare timeoutId before registering the error handler to avoid TDZ confusion
|
|
let timeoutId: NodeJS.Timeout | undefined;
|
|
|
|
const response = await new Promise<GraphQLMutationResponse>((res, rej) => {
|
|
const gh = spawn('gh', ['api', 'graphql', '--input', '-'], {
|
|
cwd: projectPath,
|
|
env: execEnv,
|
|
});
|
|
|
|
gh.on('error', (err) => {
|
|
clearTimeout(timeoutId);
|
|
rej(err);
|
|
});
|
|
|
|
timeoutId = setTimeout(() => {
|
|
gh.kill();
|
|
rej(new Error('GitHub GraphQL API request timed out'));
|
|
}, GITHUB_API_TIMEOUT_MS);
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
gh.stdout.on('data', (data: Buffer) => (stdout += data.toString()));
|
|
gh.stderr.on('data', (data: Buffer) => (stderr += data.toString()));
|
|
|
|
gh.on('close', (code) => {
|
|
clearTimeout(timeoutId);
|
|
if (code !== 0) {
|
|
return rej(new Error(`gh process exited with code ${code}: ${stderr}`));
|
|
}
|
|
try {
|
|
res(JSON.parse(stdout));
|
|
} catch (e) {
|
|
rej(e);
|
|
}
|
|
});
|
|
|
|
gh.stdin.write(requestBody);
|
|
gh.stdin.end();
|
|
});
|
|
|
|
if (response.errors && response.errors.length > 0) {
|
|
throw new Error(response.errors[0].message);
|
|
}
|
|
|
|
const threadData = resolve
|
|
? response.data?.resolveReviewThread?.thread
|
|
: response.data?.unresolveReviewThread?.thread;
|
|
|
|
if (!threadData) {
|
|
throw new Error('No thread data returned from GitHub API');
|
|
}
|
|
|
|
return { isResolved: threadData.isResolved };
|
|
}
|