diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..c4fe77b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,17 @@ +# Use the official TypeScript Node.js image as a base +FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm + +# Install additional features +RUN apt-get update && apt-get install -y \ + awscli \ + curl \ + exa \ + jq \ + fzf \ + locate \ + manpages \ + ripgrep \ + shellcheck \ + && rm -rf /var/lib/apt/lists/* + +ENV SHELL /bin/zsh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6e0cac0..61524fb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,31 @@ { "name": "Node.js & TypeScript", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "dockerComposeFile": "docker-compose.yml", + "service": "ai-code-reviewer", + "workspaceFolder": "/workspace", "features": { - "ghcr.io/devcontainers/features/aws-cli:1": {}, - "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}, - "ghcr.io/dhoeric/features/act:1": {} // "ghcr.io/guiyomh/features/just:0": {}, // "ghcr.io/jungaretti/features/ripgrep:1": {}, // "ghcr.io/lukewiwa/features/shellcheck:0": {}, }, "customizations": { "vscode": { - "extensions": ["yzhang.markdown-all-in-one"] + "extensions": [ + "eamodio.gitlens", + "fill-labs.dependi", + "GitHub.copilot", + "github.copilot-chat", + "github.vscode-pull-request-github", + "kaiwood.center-editor-window", + "ms-azuretools.vscode-docker", + "ms-vsliveshare.vsliveshare", + "timonwong.shellcheck", + "usernamehw.errorlens", + "visualstudioexptteam.vscodeintellicode", + "wenfangdu.jump", + "yzhang.markdown-all-in-one" + ] } } -} +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..5e728ee --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' + +services: + ai-code-reviewer: + build: + context: . + dockerfile: Dockerfile + volumes: + - ..:/workspace + command: sleep infinity \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4adf22e..7d4c612 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +.env .idea lib/**/* act/.secrets diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bbccbe1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Collect AI Feedback", + "type": "node", + "request": "launch", + "args": [ + "src/exportComments.ts", + "--owner", + "cds-snc", + "--repos", + "notification-terraform", + "notification-manifests", + "--author", + "github-actions[bot]", + "--since", + "2024-12-01", + "--until", + "2024-12-08", + ], + "runtimeArgs": [ + "-r", + "ts-node/register" + ], + "cwd": "${workspaceRoot}", + "protocol": "inspector", + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9889d2f..1abaf54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,17 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@actions/core": "^1.10.0", + "@actions/core": "^1.11.1", "@octokit/rest": "^19.0.7", - "axios": "1.7.7", + "axios": "^1.7.9", "csv-writer": "^1.6.0", + "dotenv": "^16.4.7", "minimatch": "^7.4.2", - "nock": "^13.5.5", - "openai": "^4.20.1", + "nock": "^13.5.6", + "openai": "^4.77.0", "parse-diff": "^0.11.1", - "ts-node": "^10.9.1", - "yargs": "17.7.2" + "ts-node": "^10.9.2", + "yargs": "^17.7.2" }, "devDependencies": { "@types/axios": "0.14.0", @@ -30,13 +31,22 @@ } }, "node_modules/@actions/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz", - "integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", "license": "MIT", "dependencies": { - "@actions/http-client": "^2.0.1", - "uuid": "^8.3.2" + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" } }, "node_modules/@actions/http-client": { @@ -48,6 +58,12 @@ "tunnel": "^0.0.6" } }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -404,9 +420,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -420,11 +436,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" - }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", @@ -440,15 +451,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -499,15 +501,6 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "license": "MIT" }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/csv-writer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", @@ -561,14 +554,16 @@ "node": ">=0.3.1" } }, - "node_modules/digest-fetch": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", - "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", - "license": "ISC", - "dependencies": { - "base-64": "^0.1.0", - "md5": "^2.3.0" + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/emoji-regex": { @@ -675,12 +670,6 @@ "ms": "^2.0.0" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -711,17 +700,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "license": "ISC" }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -765,9 +743,9 @@ "license": "MIT" }, "node_modules/nock": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.5.tgz", - "integrity": "sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -827,23 +805,29 @@ } }, "node_modules/openai": { - "version": "4.20.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.20.1.tgz", - "integrity": "sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA==", + "version": "4.77.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.77.0.tgz", + "integrity": "sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw==", "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", - "digest-fetch": "^1.3.0", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "node-fetch": "^2.6.7" }, "bin": { "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, "node_modules/parse-diff": { @@ -925,9 +909,9 @@ "license": "MIT" }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -995,30 +979,12 @@ "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", "license": "ISC" }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index a405b0e..fde9fa8 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,17 @@ "lint": "prettier --check ." }, "dependencies": { - "@actions/core": "^1.10.0", + "@actions/core": "^1.11.1", "@octokit/rest": "^19.0.7", - "axios": "1.7.7", + "axios": "^1.7.9", "csv-writer": "^1.6.0", + "dotenv": "^16.4.7", "minimatch": "^7.4.2", - "nock": "^13.5.5", - "openai": "^4.20.1", + "nock": "^13.5.6", + "openai": "^4.77.0", "parse-diff": "^0.11.1", - "ts-node": "^10.9.1", - "yargs": "17.7.2" + "ts-node": "^10.9.2", + "yargs": "^17.7.2" }, "devDependencies": { "@types/axios": "0.14.0", diff --git a/src/exportComments.ts b/src/exportComments.ts index 2ca5354..7413f4b 100644 --- a/src/exportComments.ts +++ b/src/exportComments.ts @@ -1,24 +1,38 @@ /** - * This script fetches comments from specified GitHub pull requests and exports them to a CSV file. + * This script fetches comments from specified GitHub repositories, for a given time frame, + * and exports these to a CSV file. + * * It uses the GitHub API to retrieve the comments and filters them based on the provided author (if specified). - * The resulting CSV file contains the pull request number, author, comment, and a link to the comment. + * The resulting CSV file contains the date, AI feedback, pull request number, author, comment, and a link to + * the comment. * * Usage: * npx ts-node exportComments.ts --token --owner --repo --prs [--author ] * * Options: - * --token, -t GitHub personal access token (can also be set via the GITHUB_TOKEN environment variable) - * It is recommended to use the environment variable to avoid exposing sensitive information. - * --owner, -o Repository owner - * --repo, -r Repository name - * --prs, -p Comma-separated list of pull request numbers - * --author, -a Author of the comments to filter with (optional) + * --owner, -o Repository owner (required) + * --repos, -r Repositories to search into (required) + * --author, -a Author of the comments to filter with (optional -- likely the AI bot author name) + * --since, -s Filter comments since the given date (YYYY-MM-DD) (required) + * --until, -u Filter comments until the given date (YYYY-MM-DD) (optional, defaults to current date) + * --token, -t GitHub personal access token (can also be set via the GITHUB_TOKEN environment variable or `.env` file) + * It is recommended to use the environment variable to avoid exposing sensitive information. * - * Example: - * npx ts-node exportComments.ts --owner cds-snc --repo cds-ai-codereviewer --prs 6,7,8 --author github-actions[bot] + * Examples: + * npx ts-node exportComments.ts --owner cds-snc --repos cds-ai-codereviewer --author 'github-actions[bot]' --since 2024-12-01 + * + * npx ts-node src/exportComments.ts --owner cds-snc --repos notification-terraform notification-api --author 'github-actions[bot]' --since 2024-12-01 --until 2024-12-31 * * Environment Variable: * GITHUB_TOKEN GitHub personal access token (recommended to use this instead of --token argument) + * + * The GITHUB_TOKEN can be configured using a .env file at the root of the project: + * + * Example .env file: + * + * ```txt + * GITHUB_TOKEN=your_actual_token_here + * ``` */ import axios from "axios"; @@ -26,12 +40,16 @@ import { createObjectCsvWriter } from "csv-writer"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; +// filepath: /workspace/src/exportComments.ts +import dotenv from 'dotenv'; +dotenv.config(); + const argv = yargs(hideBin(process.argv)) .option("token", { alias: "t", type: "string", description: "GitHub personal access token", - demandOption: false, + demandOption: true, default: process.env.GITHUB_TOKEN, }) .option("owner", { @@ -40,28 +58,41 @@ const argv = yargs(hideBin(process.argv)) description: "Repository owner", demandOption: true, }) - .option("repo", { + .option("repos", { alias: "r", - type: "string", - description: "Repository name", - demandOption: true, - }) - .option("prs", { - alias: "p", - type: "string", - description: "Comma-separated list of pull request numbers", + type: "array", + description: "List of repository names", demandOption: true, }) + .array("repos") + .string("repos") .option("author", { alias: "a", type: "string", description: "Author of the comments to filter with", demandOption: false, }) + .option("since", { + alias: "s", + type: "string", + description: "Filter comments since the given date (YYYY-MM-DD)", + demandOption: true, + coerce: coerceDate, + }) + .option("until", { + alias: "u", + type: "string", + description: "Filter comments until the given date (YYYY-MM-DD)", + demandOption: false, + coerce: coerceDate, + default: new Date(), + }) .parseSync(); // Use parseSync to ensure argv is not a Promise interface Comment { + date: string; author: string; + repository: string; prNumber: string; category: string[]; comment: string; @@ -71,7 +102,9 @@ interface Comment { const csvWriter = createObjectCsvWriter({ path: "pr_comments.csv", header: [ + { id: "date", title: "Date" }, { id: "author", title: "Author" }, + { id: "repository", title: "Repository" }, { id: "prNumber", title: "PR Number" }, { id: "category", title: "Category" }, { id: "comment", title: "Comment" }, @@ -79,41 +112,37 @@ const csvWriter = createObjectCsvWriter({ ], }); +function coerceDate(dateStr: string): Date { + const date = new Date(dateStr); + if (isNaN(date.getTime())) { + throw new Error("Invalid date format. Please use YYYY-MM-DD."); + } + return date; +} + const reactionToCategory: Record = { "+1": "Useful", eyes: "Noisy", confused: "Hallucination", rocket: "Teachable", "-1": "Incorrect", + null: "None", }; function extractCategories(reactions: Record): string[] { - return Object.keys(reactions) + const category = Object.keys(reactions) .filter( (reaction) => reactionToCategory[reaction] && reactions[reaction] > 0 ) .map((reaction) => reactionToCategory[reaction]); + const nonEmptyCategory = category?.length === 0 ? ["None"] : category; + return nonEmptyCategory; } -async function fetchComments(): Promise { - const prNumbers = argv.prs - .split(",") - .map((pr: string) => parseInt(pr.trim(), 10)); - let allComments: Comment[] = []; - - for (const prNumber of prNumbers) { - const comments = await fetchCommentsForPR(prNumber); - allComments = allComments.concat(comments); - } - - await csvWriter.writeRecords(allComments); - console.log("CSV file written successfully"); -} - -async function fetchCommentsForPR(prNumber: number): Promise { +async function fetchCommentsForPR(owner: string, repository: string, prNumber: number, author?: string): Promise { try { const response = await axios.get( - `https://api.github.com/repos/${argv.owner}/${argv.repo}/pulls/${prNumber}/comments`, + `https://api.github.com/repos/${owner}/${repository}/pulls/${prNumber}/comments`, { headers: { Authorization: `token ${argv.token}`, @@ -124,8 +153,11 @@ async function fetchCommentsForPR(prNumber: number): Promise { let comments: Comment[] = await Promise.all( response.data.map(async (comment: Record) => { const categories = extractCategories(comment.reactions); + console.debug(`Categories for comment ${repository}/pull/${prNumber}/${comment.id}:`, categories); return { + date: comment.created_at, author: comment.user.login, + repository: `${owner}/${repository}`, prNumber: prNumber.toString(), category: categories, comment: comment.body, @@ -134,8 +166,8 @@ async function fetchCommentsForPR(prNumber: number): Promise { }) ); - if (argv.author) { - comments = comments.filter((comment) => comment.author === argv.author); + if (author) { + comments = comments.filter((comment) => comment.author === author); } return comments; @@ -145,4 +177,55 @@ async function fetchCommentsForPR(prNumber: number): Promise { } } -fetchComments(); +// Function to fetch comments for multiple repositories +async function fetchCommentsForRepos(owner: string, repositories: string[], since: Date, until: Date, author?: string): Promise { + let allComments: Comment[] = []; + for (const repository of repositories) { + console.log(`Fetching comments for repository ${owner}/${repository}...`); + const pullRequests = await fetchPullRequests(owner, repository, since, until); + for (const pr of pullRequests) { + const prComments = await fetchCommentsForPR(owner, repository, pr.number, author); + allComments = allComments.concat(prComments); + } + } + await csvWriter.writeRecords(allComments); + console.log("CSV file written successfully"); + return allComments; +} + +async function fetchPullRequests(owner: string, repo: string, since: Date, until: Date) { + const pullRequests = []; + let page = 1; + let hasMore = true; + + console.debug(`Fetching pull requests for ${owner}/${repo}...`); + while (hasMore) { + const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/pulls`, { + headers: { + Authorization: `token ${argv.token}`, + Accept: 'application/vnd.github.v3+json', + }, + params: { + state: 'all', + per_page: 100, + page, + sort: 'updated', + direction: 'desc', + }, + }); + + const filteredPRs = response.data.filter((pr: any) => new Date(pr.updated_at) >= since && new Date(pr.updated_at) <= until); + pullRequests.push(...filteredPRs); + + if (response.data.length < 100) { + hasMore = false; + } else { + page++; + } + } + + console.debug(`Fetched ${pullRequests.length} pull requests for ${owner}/${repo}`); + return pullRequests; +} + +fetchCommentsForRepos(argv.owner, argv.repos, argv.since, argv.until, argv.author); diff --git a/yarn.lock b/yarn.lock index 9c071c5..ac753db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,20 @@ # yarn lockfile v1 -"@actions/core@^1.10.0": - version "1.10.0" - resolved "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz" - integrity sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug== +"@actions/core@^1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz" + integrity sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A== dependencies: + "@actions/exec" "^1.1.1" "@actions/http-client" "^2.0.1" - uuid "^8.3.2" + +"@actions/exec@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz" + integrity sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w== + dependencies: + "@actions/io" "^1.0.1" "@actions/http-client@^2.0.1": version "2.1.0" @@ -17,6 +24,11 @@ dependencies: tunnel "^0.0.6" +"@actions/io@^1.0.1": + version "1.1.3" + resolved "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz" + integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" @@ -246,10 +258,10 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@*, axios@1.7.7: - version "1.7.7" - resolved "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== +axios@*, axios@^1.7.9: + version "1.7.9" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -260,11 +272,6 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-64@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz" - integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== - before-after-hook@^2.2.0: version "2.2.3" resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz" @@ -277,11 +284,6 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz" - integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== - cliui@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" @@ -315,11 +317,6 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" - integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== - csv-writer@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz" @@ -347,13 +344,10 @@ diff@^4.0.1: resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -digest-fetch@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz" - integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== - dependencies: - base-64 "^0.1.0" - md5 "^2.3.0" +dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== emoji-regex@^8.0.0: version "8.0.0" @@ -409,11 +403,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -is-buffer@~1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -434,15 +423,6 @@ make-error@^1.1.1: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -md5@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" @@ -472,10 +452,10 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nock@^13.5.5: - version "13.5.5" - resolved "https://registry.npmjs.org/nock/-/nock-13.5.5.tgz" - integrity sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA== +nock@^13.5.6: + version "13.5.6" + resolved "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz" + integrity sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -500,20 +480,18 @@ once@^1.4.0: dependencies: wrappy "1" -openai@^4.20.1: - version "4.20.1" - resolved "https://registry.npmjs.org/openai/-/openai-4.20.1.tgz" - integrity sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA== +openai@^4.77.0: + version "4.77.0" + resolved "https://registry.npmjs.org/openai/-/openai-4.77.0.tgz" + integrity sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" abort-controller "^3.0.0" agentkeepalive "^4.2.1" - digest-fetch "^1.3.0" form-data-encoder "1.7.2" formdata-node "^4.3.2" node-fetch "^2.6.7" - web-streams-polyfill "^3.2.1" parse-diff@^0.11.1: version "0.11.1" @@ -561,10 +539,10 @@ tr46@~0.0.3: resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -595,21 +573,11 @@ universal-user-agent@^6.0.0: resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -web-streams-polyfill@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - web-streams-polyfill@4.0.0-beta.3: version "4.0.0-beta.3" resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz" @@ -652,7 +620,7 @@ yargs-parser@^21.1.1: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@17.7.2: +yargs@^17.7.2: version "17.7.2" resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==