diff --git a/bun.lockb b/bun.lockb index c5df9fd..7fe6a9b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 05bf4e5..9ee928f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,9 @@ "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-node-resolve": "^16.0.0", "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", "@types/js-yaml": "^4.0.9", + "@types/mocha": "^10.0.10", "@types/node": "^22.13.4", "@types/nodemailer": "^6.4.17", "@types/nodemailer-express-handlebars": "^4.0.5", diff --git a/src/configs/app.config.ts b/src/configs/app.config.ts index 2bfaeb6..7c5fbe8 100644 --- a/src/configs/app.config.ts +++ b/src/configs/app.config.ts @@ -2,7 +2,6 @@ export const appConfig = { drGitApiKeyName: 'DR_GIT_API_KEY', configFileNames: ['.drgit.yml', '.drgit.yaml'], metadata: { - // Don't change these strings on production walkthrough: '[drgit]: walkthrough', diagram: '[drgit]: diagram', summary: '[drgit]: summary' diff --git a/src/handlers/app.handler.ts b/src/handlers/app.handler.ts index 3b179df..c654c94 100644 --- a/src/handlers/app.handler.ts +++ b/src/handlers/app.handler.ts @@ -88,7 +88,7 @@ export class AppHandler { new UserErrorListener().listen(); if (envs.isDevelopment) { - await this.services.cacheService.flush(); + await this.services.cacheService.flushByPattern('user/*'); systemLogger.info('Cache has been flushed'); } @@ -103,6 +103,7 @@ export class AppHandler { config = await this.services.configService.getConfig(context, apiKey); await this.services.appService.init(context); + if (!apiKey || !config) return; await callback(apiKey, config); } catch (error) { if ('repository' in context.payload) { diff --git a/src/services/cache.service.ts b/src/services/cache.service.ts index 3939f43..748e341 100644 --- a/src/services/cache.service.ts +++ b/src/services/cache.service.ts @@ -103,6 +103,18 @@ export class CacheService { }); } + async flushByPattern(pattern: string): Promise { + return this.wrapError('flush cache by pattern', undefined, async () => { + const keys = await this.client.keys(pattern); + + if (keys.length === 0) { + return 0; + } + + return this.client.del(...keys); + }); + } + async close(): Promise { return this.wrapError('close connection', undefined, async () => { const result = await this.client.quit(); diff --git a/src/services/github.service.ts b/src/services/github.service.ts index e25cfcd..3dda4a0 100644 --- a/src/services/github.service.ts +++ b/src/services/github.service.ts @@ -138,7 +138,7 @@ export class GitHubService { pull_number }); - const filePromises = files.data.map(async (file) => { + const filePromises = files.data.map(async (file: RestEndpointMethodTypes['pulls']['listFiles']['response']['data'][number]) => { const content = await context.octokit.repos.getContent({ owner, repo, diff --git a/src/services/queue.service.ts b/src/services/queue.service.ts index 66b060c..4c75bb0 100644 --- a/src/services/queue.service.ts +++ b/src/services/queue.service.ts @@ -1,21 +1,21 @@ -import pQueue, { Options, QueueAddOptions } from 'p-queue'; -import PriorityQueue from 'p-queue/dist/priority-queue'; +import pQueue from 'p-queue'; import pRetry, { Options as pRetryOption } from 'p-retry'; type TaskFunction = () => Promise; +type PQueueOptions = ConstructorParameters[0]; export class QueueService { private static instance: QueueService; private queue: pQueue; private defaultRetryOptions: pRetryOption; - private constructor(queueOptions: Options, retryOptions: pRetryOption) { + private constructor(queueOptions: PQueueOptions, retryOptions: pRetryOption) { this.queue = new pQueue(queueOptions); this.defaultRetryOptions = retryOptions; } public static getInstance( - queueOptions: Options = { concurrency: 5, interval: 3000 }, + queueOptions: PQueueOptions = { concurrency: 5, interval: 3000 }, retryOptions: pRetryOption = { minTimeout: 1000, maxTimeout: 5000, retries: 3, factor: 2 } ): QueueService { if (!QueueService.instance) { diff --git a/src/utils/chat-config.util.ts b/src/utils/chat-config.util.ts index 7b44649..415a7d7 100644 --- a/src/utils/chat-config.util.ts +++ b/src/utils/chat-config.util.ts @@ -56,7 +56,7 @@ export class ChatConfig { } else if (isInlineComment(context.payload) || isPullRequestComment(context.payload)) { return config.chat['stop-on']['pull-requests'].closed; } else if (isIssue(context.payload) || isIssueComment(context.payload)) { - return config.chat['stop-on'].issues.locked; + return config.chat['stop-on'].issues.closed; } else { throw new SystemError('Unhandled case'); } diff --git a/src/utils/github.util.ts b/src/utils/github.util.ts index 60c8f6d..441a032 100644 --- a/src/utils/github.util.ts +++ b/src/utils/github.util.ts @@ -1,37 +1,42 @@ import { DiffLine } from '~common/types'; -const hunkRegex = /@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@([\s\S]*?)(?=@@|$)/g; - export const extractDiffsFromPatch = (patch: string): DiffLine[] => { - const diffLines = []; + const diffLines: DiffLine[] = []; + const hunkRegex = /@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@([\s\S]*?)(?=@@|$)/g; let match; while ((match = hunkRegex.exec(patch)) !== null) { - const allCleanedLines = match[3].split('\n').flatMap((line) => (!!line.trim() ? line.trim() : [])); - const firstAdditionIndex = allCleanedLines.findIndex((line) => line.startsWith('+')); - const totalLinesBeforeFirstAddition = allCleanedLines.filter( - (line, lineIndex) => /^[^-+]/.test(line) && lineIndex < firstAdditionIndex - ).length; - const firstDeletionIndex = allCleanedLines.findIndex((line) => line.startsWith('-')); - const totalLinesAfterLastAddition = allCleanedLines.filter( - (line, lineIndex) => /^[^-+]/.test(line) && lineIndex < firstDeletionIndex - ).length; - - const start = parseInt(match[1], 10) + totalLinesBeforeFirstAddition; - const lineCount = match[2] ? parseInt(match[2], 10) : 1; - const end = start - totalLinesBeforeFirstAddition + lineCount - 1 - totalLinesAfterLastAddition; - - const addition = allCleanedLines - .filter((line) => line.startsWith('+')) - .map((line) => line.slice(1)) - .join('\n'); - - const deletion = allCleanedLines - .filter((line) => line.startsWith('-')) - .map((line) => line.slice(1)) - .join('\n'); - - diffLines.push({ start, end, addition, deletion }); + let lineNum = parseInt(match[1], 10); + let startLine: number | null = null; + let endLine: number | null = null; + const addedLines: string[] = []; + const deletedLines: string[] = []; + + const lines = match[2].split('\n'); + + for (const line of lines) { + if (line.startsWith('+')) { + if (startLine === null) startLine = lineNum; + endLine = lineNum; + addedLines.push(line.slice(1)); + lineNum++; + } else if (line.startsWith('-')) { + deletedLines.push(line.slice(1)); + // Deleted lines do not advance the new-file line counter + } else if (line.trim() !== '') { + // Context line — counts in both old and new file + lineNum++; + } + } + + if (startLine !== null && endLine !== null) { + diffLines.push({ + start: startLine, + end: endLine, + addition: addedLines.join('\n'), + deletion: deletedLines.join('\n') + }); + } } return diffLines; diff --git a/tests/utils/chat-config.util.test.ts b/tests/utils/chat-config.util.test.ts index 60268ff..f392586 100644 --- a/tests/utils/chat-config.util.test.ts +++ b/tests/utils/chat-config.util.test.ts @@ -259,7 +259,7 @@ describe('ChatConfig', () => { }, 'stop-on': { discussions: { locked: true, closed: false }, - issues: { locked: false, draft: true }, + issues: { locked: false, draft: true, closed: true }, 'pull-requests': { locked: false, draft: false, merged: true, closed: false } }, allow: { @@ -290,7 +290,7 @@ describe('ChatConfig', () => { expect(ChatConfig.getChatStopOnClosed(contextDiscussion, config)).toBe(false); expect(ChatConfig.getChatStopOnClosed(contextInlineComment, config)).toBe(false); - expect(ChatConfig.getChatStopOnClosed(contextIssue, config)).toBe(false); + expect(ChatConfig.getChatStopOnClosed(contextIssue, config)).toBe(true); // fixed: reads .closed not .locked expect(ChatConfig.getChatAllow(contextDiscussion, config)).toBe(true); expect(ChatConfig.getChatAllow(contextInlineComment, config)).toBe(true); diff --git a/tsconfig.json b/tsconfig.json index 724cc80..e818416 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,9 @@ { + "ignoreDeprecations": true, "compilerOptions": { "target": "ES2022", "module": "ES2022", - "moduleResolution": "Node", + "moduleResolution": "Bundler", "incremental": true, "strict": true, "sourceMap": true, @@ -26,10 +27,15 @@ "declaration": false, "outDir": "dist", "rootDir": "src", - "typeRoots": ["types", "./node_modules/@types"], + "typeRoots": [ + "types", + "./node_modules/@types" + ], "baseUrl": "./", "paths": { - "~*": ["./src/*"] + "~*": [ + "./src/*" + ] } }, "exclude": [ @@ -40,5 +46,5 @@ "include": [ "src", "types" - ] + ], }