diff --git a/.github/workflows/jsdoc-automation.yml b/.github/workflows/jsdoc-automation.yml new file mode 100644 index 0000000000..2ad6625a4e --- /dev/null +++ b/.github/workflows/jsdoc-automation.yml @@ -0,0 +1,81 @@ +name: JSDoc Automation + +on: + workflow_dispatch: + inputs: + pull_number: + description: 'Pull Request Number (if not provided, scans root_directory) - PR must be merged to develop branch' + required: false + type: string + root_directory: + description: 'Only scans files in this directory (relative to repository root, e.g., packages/core/src)' + required: true + default: 'packages/core/src/test_resources' + type: string + excluded_directories: + description: 'Directories to exclude from scanning (comma-separated, relative to root_directory)' + required: true + default: 'node_modules,dist,test' + type: string + reviewers: + description: 'Pull Request Reviewers (comma-separated GitHub usernames)' + required: true + default: '' + type: string + +jobs: + generate-docs: + runs-on: ubuntu-latest + + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '23' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + + - name: Update lockfile + working-directory: packages/jsdoc-automation + run: | + echo "Updating lockfile..." + pnpm install --no-frozen-lockfile + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git add pnpm-lock.yaml + git commit -m "chore: update pnpm lockfile" || echo "No changes to commit" + git push || echo "No changes to push" + + - name: Install root dependencies + run: pnpm install --no-frozen-lockfile + + - name: Install package dependencies + working-directory: packages/jsdoc-automation + run: pnpm install --no-frozen-lockfile + + - name: Run documentation generator + working-directory: packages/jsdoc-automation + run: | + echo "Node version: $(node --version)" + echo "NPM version: $(npm --version)" + echo "Directory contents:" + ls -la + NODE_OPTIONS='--experimental-vm-modules --no-warnings' pnpm start + env: + INPUT_ROOT_DIRECTORY: ${{ inputs.root_directory }} + INPUT_PULL_NUMBER: ${{ inputs.pull_number }} + INPUT_EXCLUDED_DIRECTORIES: ${{ inputs.excluded_directories }} + INPUT_REVIEWERS: ${{ inputs.reviewers }} \ No newline at end of file diff --git a/scripts/jsdoc-automation/.example.env b/scripts/jsdoc-automation/.example.env new file mode 100644 index 0000000000..5c7a3b7f5e --- /dev/null +++ b/scripts/jsdoc-automation/.example.env @@ -0,0 +1,2 @@ +GITHUB_ACCESS_TOKEN= +OPENAI_API_KEY= \ No newline at end of file diff --git a/scripts/jsdoc-automation/.gitignore b/scripts/jsdoc-automation/.gitignore new file mode 100644 index 0000000000..ed03f6d96a --- /dev/null +++ b/scripts/jsdoc-automation/.gitignore @@ -0,0 +1,25 @@ +# Dependencies +node_modules +.pnpm-store + +# Build outputs +dist +build + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +*.log +npm-debug.log* +pnpm-debug.log* + +# Editor directories +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln \ No newline at end of file diff --git a/scripts/jsdoc-automation/README.md b/scripts/jsdoc-automation/README.md new file mode 100644 index 0000000000..9d9e141162 --- /dev/null +++ b/scripts/jsdoc-automation/README.md @@ -0,0 +1,92 @@ +# Codebase Documentation +- https://github.com/ai16z/eliza/issues/1110 + +## Set up +- Set GH_PAT & OPENAI_API_KEY in github actions as env variables + +## Summary +- To only scan files in a PR, ensure the files have been merged into the base branch (defaults to develop), and provide the PR number in the github Action input +- `root_directory` is still enforced when scanning a PR +- To scan a provided directory set `root_directory` and leave the PR number empty + +## GitHub Workflow Automation + +The codebase includes a GitHub Actions workflow (`JSDoc Automation`) that allows triggering the documentation generation process with custom parameters. The workflow is defined in the `.github/workflows` directory. + +# Workflow Dispatch Inputs + +The workflow can be triggered manually using the `workflow_dispatch` event, which accepts the following inputs: + +- `pull_number` (optional): The pull request number to generate documentation for. + - if not provided, the `root_directory` will be scanned + - PR must be merged to develop/main branch + +- `root_directory` (required): Only scans files in this directory. + - Description: Target directory relative to repository root (e.g., packages/core/src) + - Default: `packages/core/src/test_resources` - arbitrarily chose this because its small + +- `excluded_directories` (required): Directories to exclude from scanning. + - Description: Comma-separated list of directories to exclude, relative to root_directory + - Default: 'node_modules,dist,test' + +- `reviewers` (required): Pull Request Reviewers. + - Description: Comma-separated list of GitHub usernames + - Default: '' + +### Config File +The `src/Configuration.ts` handles configuration loading from environment variables with fallback to YAML workflow files. + +#### Default Values + +- **Repository**: ai16z/eliza +- **Branch**: develop +- **Commit Message**: "Generated JSDoc comments" +- **PR Title**: "JSDoc Generation" +- **PR Description**: "Automated JSDoc generation for the codebase" +- **PR Labels**: ["documentation", "automated-pr"] +- **Excluded Directories**: ["node_modules", "dist", "test"] +- **Excluded Files**: ["index.d.ts"] + + +### Environment Variables + +The following environment variables need to be added to the GitHub repository secrets: + +- `GH_PAT`: Personal Access Token with sufficient permissions to create branches, commit changes, and create pull requests in the repository. +- `OPENAI_API_KEY`: API key for accessing the OpenAI chat API used by the `AIService` to generate comments. + +# Codebase Documentation + +## `JsDocGenerator` Class +The `JsDocGenerator` class is responsible for generating JSDoc comments for code snippets and classes. It uses the `AIService` to generate comments based on the code provided. + +## `TypeScriptFileIdentifier` Class +The `TypeScriptFileIdentifier` class handles identifying and retrieving TypeScript files from a specified directory. It checks file extensions to determine if a file is a TypeScript file. + +## `TypeScriptParser` Class +The `TypeScriptParser` class parses TypeScript files using the `@typescript-eslint/parser`. It generates an abstract syntax tree (AST) representation of the parsed content. + +## `DocumentationGenerator` Class +The `DocumentationGenerator` class orchestrates the generation of JSDoc documentation for a codebase. It traverses the directory, identifies TypeScript files, parses them, analyzes existing JSDoc comments, generates missing comments using the `JsDocGenerator`, and updates the files with the generated documentation. It also handles creating git branches, committing changes, and creating pull requests. + +## `JsDocAnalyzer` Class +The `JsDocAnalyzer` class analyzes JSDoc comments in TypeScript code. It traverses the AST and identifies nodes that should have JSDoc comments. It also provides methods to check if a node is a class node and retrieve JSDoc comments associated with a node. + +## `AIService` Class +The `AIService` class is a service for interacting with the OpenAI chat API. It uses the `ChatOpenAI` class from the `@langchain/openai` package to generate comments based on provided prompts. + +## `DirectoryTraversal` Class +The `DirectoryTraversal` class handles traversing directories and files. It can traverse based on provided PR files or scan all files in a root directory. It filters files based on excluded directories and file extensions. + +## `GitManager` Class +The `GitManager` class manages operations related to interacting with a Git repository using the GitHub API. It can retrieve files in a pull request, create branches, commit files, and create pull requests. + +## `Configuration` Class +The `Configuration` class represents a configuration object that holds various settings for a project. It can load configuration data from a JSON file and save the current configuration data to a file. + +## `Main` Function +The `main` function is the entry point of the documentation generation process. It creates instances of necessary classes, loads the configuration, retrieves files from a pull request if specified, traverses the directory, parses TypeScript files, analyzes JSDoc comments, and generates documentation using the `DocumentationGenerator`. It also handles error logging. + +## Prompt Template Locations: +- DocumentationGenerator +- JsDocGenerator \ No newline at end of file diff --git a/scripts/jsdoc-automation/package.json b/scripts/jsdoc-automation/package.json new file mode 100644 index 0000000000..60902a2a41 --- /dev/null +++ b/scripts/jsdoc-automation/package.json @@ -0,0 +1,30 @@ +{ + "type": "module", + "name": "plugin-audix", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "start": "NODE_OPTIONS='--loader ts-node/esm' node src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1", + "clean": "rm -rf node_modules dist" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@langchain/openai": "^0.3.16", + "@octokit/rest": "^21.0.2", + "@types/node": "^20.11.0", + "dotenv": "^16.4.7", + "langchain": "^0.3.7", + "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "yaml": "^2.3.4" + }, + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "5.3.3" + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/pnpm-lock.yaml b/scripts/jsdoc-automation/pnpm-lock.yaml new file mode 100644 index 0000000000..4bf18e4f59 --- /dev/null +++ b/scripts/jsdoc-automation/pnpm-lock.yaml @@ -0,0 +1,1667 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@langchain/openai': + specifier: ^0.3.16 + version: 0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + '@octokit/rest': + specifier: ^21.0.2 + version: 21.0.2 + '@types/node': + specifier: ^20.11.0 + version: 20.17.10 + '@typescript-eslint/parser': + specifier: ^6.18.1 + version: 6.21.0(eslint@9.17.0)(typescript@5.3.3) + '@typescript-eslint/types': + specifier: ^6.18.1 + version: 6.21.0 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 + langchain: + specifier: ^0.3.7 + version: 0.3.7(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(openai@4.77.0(zod@3.24.1)) + yaml: + specifier: ^2.3.4 + version: 2.6.1 + devDependencies: + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.17.10)(typescript@5.3.3) + typescript: + specifier: 5.3.3 + version: 5.3.3 + +packages: + + '@cfworker/json-schema@4.0.3': + resolution: {integrity: sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.9.1': + resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.4': + resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@langchain/core@0.3.26': + resolution: {integrity: sha512-6RUQHEp8wv+JwtYIIEBYBzbLlcAQZFc7EDOgAM0ukExjh9HiXoJzoWpgMRRCrr/koIbtwXPJUqBprZK1I1CXHQ==} + engines: {node: '>=18'} + + '@langchain/openai@0.3.16': + resolution: {integrity: sha512-Om9HRlTeI0Ou6D4pfxbWHop4WGfkCdV/7v1W/+Jr7NSf0BNoA9jk5GqGms8ZtOYSGgPvizDu3i0TrM3B4cN4NA==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.26 <0.4.0' + + '@langchain/textsplitters@0.1.0': + resolution: {integrity: sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.21 <0.4.0' + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/plugin-paginate-rest@11.3.6': + resolution: {integrity: sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.2.6': + resolution: {integrity: sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@6.1.5': + resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.3': + resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + engines: {node: '>= 18'} + + '@octokit/rest@21.0.2': + resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} + engines: {node: '>= 18'} + + '@octokit/types@13.6.2': + resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node-fetch@2.6.12': + resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + + '@types/node@18.19.68': + resolution: {integrity: sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==} + + '@types/node@20.17.10': + resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.17.0: + resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tiktoken@1.0.16: + resolution: {integrity: sha512-nUVdO5k/M9llWpiaZlBBDdtmr6qWXwSD6fgaDu2zM8UP+OXxx9V37lFkI6w0/1IuaDx7WffZ37oYd9KvcWKElg==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + langchain@0.3.7: + resolution: {integrity: sha512-6/Gkk9Zez3HkbsETFxZVo1iKLmaK3OzkDseC5MYFKVmYFDXFAOyJR3srJ9P61xF8heVdsPixqYIsejBn7/9dXg==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/anthropic': '*' + '@langchain/aws': '*' + '@langchain/cohere': '*' + '@langchain/core': '>=0.2.21 <0.4.0' + '@langchain/google-genai': '*' + '@langchain/google-vertexai': '*' + '@langchain/groq': '*' + '@langchain/mistralai': '*' + '@langchain/ollama': '*' + axios: '*' + cheerio: '*' + handlebars: ^4.7.8 + peggy: ^3.0.2 + typeorm: '*' + peerDependenciesMeta: + '@langchain/anthropic': + optional: true + '@langchain/aws': + optional: true + '@langchain/cohere': + optional: true + '@langchain/google-genai': + optional: true + '@langchain/google-vertexai': + optional: true + '@langchain/groq': + optional: true + '@langchain/mistralai': + optional: true + '@langchain/ollama': + optional: true + axios: + optional: true + cheerio: + optional: true + handlebars: + optional: true + peggy: + optional: true + typeorm: + optional: true + + langsmith@0.2.13: + resolution: {integrity: sha512-16EOM5nhU6GlMCKGm5sgBIAKOKzS2d30qcDZmF21kSLZJiUhUNTROwvYdqgZLrGfIIzmSMJHCKA7RFd5qf50uw==} + peerDependencies: + openai: '*' + peerDependenciesMeta: + openai: + optional: true + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + openai@4.77.0: + resolution: {integrity: sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw==} + hasBin: true + peerDependencies: + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yaml@2.6.1: + resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} + engines: {node: '>= 14'} + hasBin: true + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-to-json-schema@3.24.1: + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} + peerDependencies: + zod: ^3.24.1 + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + +snapshots: + + '@cfworker/json-schema@4.0.3': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0)': + dependencies: + eslint: 9.17.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.17.0': {} + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))': + dependencies: + '@cfworker/json-schema': 4.0.3 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.16 + langsmith: 0.2.13(openai@4.77.0(zod@3.24.1)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) + transitivePeerDependencies: + - openai + + '@langchain/openai@0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + js-tiktoken: 1.0.16 + openai: 4.77.0(zod@3.24.1) + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) + transitivePeerDependencies: + - encoding + + '@langchain/textsplitters@0.1.0(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + js-tiktoken: 1.0.16 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@octokit/auth-token@5.1.1': {} + + '@octokit/core@6.1.2': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.2 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.1': + dependencies: + '@octokit/types': 13.6.2 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.1': + dependencies: + '@octokit/request': 9.1.3 + '@octokit/types': 13.6.2 + universal-user-agent: 7.0.2 + + '@octokit/openapi-types@22.2.0': {} + + '@octokit/plugin-paginate-rest@11.3.6(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.6.2 + + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + + '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.6.2 + + '@octokit/request-error@6.1.5': + dependencies: + '@octokit/types': 13.6.2 + + '@octokit/request@9.1.3': + dependencies: + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.2 + universal-user-agent: 7.0.2 + + '@octokit/rest@21.0.2': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.2) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.2) + + '@octokit/types@13.6.2': + dependencies: + '@octokit/openapi-types': 22.2.0 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/node-fetch@2.6.12': + dependencies: + '@types/node': 20.17.10 + form-data: 4.0.1 + + '@types/node@18.19.68': + dependencies: + undici-types: 5.26.5 + + '@types/node@20.17.10': + dependencies: + undici-types: 6.19.8 + + '@types/retry@0.12.0': {} + + '@types/uuid@10.0.0': {} + + '@typescript-eslint/parser@6.21.0(eslint@9.17.0)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 + eslint: 9.17.0 + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + arg@4.1.3: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asynckit@0.4.0: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + before-after-hook@3.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: {} + + concat-map@0.0.1: {} + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + diff@4.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dotenv@16.4.7: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.17.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.17.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.2 + keyv: 4.5.4 + + flatted@3.3.2: {} + + form-data-encoder@1.7.2: {} + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + has-flag@4.0.0: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + js-tiktoken@1.0.16: + dependencies: + base64-js: 1.5.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonpointer@5.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + langchain@0.3.7(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1)))(openai@4.77.0(zod@3.24.1)): + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(zod@3.24.1)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.26(openai@4.77.0(zod@3.24.1))) + js-tiktoken: 1.0.16 + js-yaml: 4.1.0 + jsonpointer: 5.0.1 + langsmith: 0.2.13(openai@4.77.0(zod@3.24.1)) + openapi-types: 12.1.3 + p-retry: 4.6.2 + uuid: 10.0.0 + yaml: 2.6.1 + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) + transitivePeerDependencies: + - encoding + - openai + + langsmith@0.2.13(openai@4.77.0(zod@3.24.1)): + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.3 + uuid: 10.0.0 + optionalDependencies: + openai: 4.77.0(zod@3.24.1) + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + make-error@1.3.6: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.3: {} + + mustache@4.2.0: {} + + natural-compare@1.4.0: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + openai@4.77.0(zod@3.24.1): + dependencies: + '@types/node': 18.19.68 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + zod: 3.24.1 + transitivePeerDependencies: + - encoding + + openapi-types@12.1.3: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-finally@1.0.0: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picomatch@2.3.1: {} + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + resolve-from@4.0.0: {} + + retry@0.13.1: {} + + reusify@1.0.4: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + slash@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-api-utils@1.4.3(typescript@5.3.3): + dependencies: + typescript: 5.3.3 + + ts-node@10.9.2(@types/node@20.17.10)(typescript@5.3.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.10 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript@5.3.3: {} + + undici-types@5.26.5: {} + + undici-types@6.19.8: {} + + universal-user-agent@7.0.2: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@10.0.0: {} + + v8-compile-cache-lib@3.0.1: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yaml@2.6.1: {} + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-to-json-schema@3.24.1(zod@3.24.1): + dependencies: + zod: 3.24.1 + + zod@3.24.1: {} diff --git a/scripts/jsdoc-automation/pnpm-workspace.yaml b/scripts/jsdoc-automation/pnpm-workspace.yaml new file mode 100644 index 0000000000..4340350e19 --- /dev/null +++ b/scripts/jsdoc-automation/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/AIService.ts b/scripts/jsdoc-automation/src/AIService.ts new file mode 100644 index 0000000000..2f7d7b8225 --- /dev/null +++ b/scripts/jsdoc-automation/src/AIService.ts @@ -0,0 +1,49 @@ +import { ChatOpenAI } from "@langchain/openai"; +import dotenv from 'dotenv'; + +dotenv.config(); + +/** + * Service for interacting with OpenAI chat API. + */ +export class AIService { + private chatModel: ChatOpenAI; + + /** + * Constructor for initializing the ChatOpenAI instance. + * + * @throws {Error} If OPENAI_API_KEY environment variable is not set. + */ + constructor() { + if (!process.env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY is not set'); + } + this.chatModel = new ChatOpenAI({ apiKey: process.env.OPENAI_API_KEY }); + } + + /** + * Generates a comment based on the specified prompt by invoking the chat model. + * @param {string} prompt - The prompt for which to generate a comment + * @returns {Promise} The generated comment + */ + public async generateComment(prompt: string): Promise { + try { + const response = await this.chatModel.invoke(prompt); + return response.content as string; + } catch (error) { + this.handleAPIError(error as Error); + return ''; + } + } + + /** + * Handle API errors by logging the error message and throwing the error. + * + * @param {Error} error The error object to handle + * @returns {void} + */ + public handleAPIError(error: Error): void { + console.error('API Error:', error.message); + throw error; + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/Configuration.ts b/scripts/jsdoc-automation/src/Configuration.ts new file mode 100644 index 0000000000..1584740f7e --- /dev/null +++ b/scripts/jsdoc-automation/src/Configuration.ts @@ -0,0 +1,148 @@ +// Configuration.ts +import * as fs from 'fs'; +import * as yaml from 'yaml'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { Repository } from './types/index.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** + * Gets the repository root path by going up two levels from the current file + * This assumes the code is in src/ directory of the package + */ +const getRepoRoot = () => path.resolve(__dirname, '../../../'); + +interface ConfigurationData { + rootDirectory: { + absolute: string; // Full path from filesystem root + relative: string; // Path relative to repository root + }; + excludedDirectories: string[]; + repository: Repository; + commitMessage: string; + pullRequestTitle: string; + pullRequestDescription: string; + pullRequestLabels: string[]; + pullRequestReviewers: string[]; + excludedFiles: string[]; +} + +/** + * Represents a configuration object that holds various settings for a project. + * Handles both absolute and relative paths for different operations. + */ +export class Configuration implements Omit { + private _rootDirectory!: ConfigurationData['rootDirectory']; + private readonly repoRoot: string; + + public excludedDirectories: string[] = []; + public repository: Repository = { + owner: 'elizaOS', + name: 'eliza', + pullNumber: undefined + }; + public commitMessage: string = 'Generated JSDoc comments'; + public pullRequestTitle: string = 'JSDoc Generation'; + public pullRequestDescription: string = 'Automated JSDoc generation for the codebase'; + public pullRequestLabels: string[] = ['documentation', 'automated-pr']; + public pullRequestReviewers: string[] = []; + public excludedFiles: string[] = ["index.d.ts"]; + public branch: string = 'develop'; + + constructor() { + this.repoRoot = getRepoRoot(); + this.loadConfiguration(); + } + + get rootDirectory(): ConfigurationData['rootDirectory'] { + return this._rootDirectory; + } + + get absolutePath(): string { + return this._rootDirectory.absolute; + } + + get relativePath(): string { + return this._rootDirectory.relative; + } + + public toRelativePath(absolutePath: string): string { + return path.relative(this.repoRoot, absolutePath); + } + + public toAbsolutePath(relativePath: string): string { + return path.resolve(this.repoRoot, relativePath); + } + + private loadConfiguration(): void { + // First try to get from environment variables + const rootDirectory = process.env.INPUT_ROOT_DIRECTORY; + let inputs; + + console.log('Environment variables:', { + rootDirectory: process.env.INPUT_ROOT_DIRECTORY, + pullNumber: process.env.INPUT_PULL_NUMBER, + excludedDirs: process.env.INPUT_EXCLUDED_DIRECTORIES, + reviewers: process.env.INPUT_REVIEWERS + }); + + if (rootDirectory) { + console.log('Using root directory from environment variable:', rootDirectory); + this._rootDirectory = { + absolute: path.resolve(this.repoRoot, rootDirectory), + relative: rootDirectory.replace(/^\/+/, '') + }; + } else { + console.log('Falling back to workflow file configuration'); + const workflowPath = join(this.repoRoot, '.github/workflows/jsdoc-automation.yml'); + if (!fs.existsSync(workflowPath)) { + throw new Error(`Workflow file not found at ${workflowPath}`); + } + const workflowContent = fs.readFileSync(workflowPath, 'utf8'); + const workflow = yaml.parse(workflowContent); + const inputs = workflow.on.workflow_dispatch.inputs; + + if (!inputs?.root_directory?.default) { + throw new Error('No root directory default found in workflow configuration'); + } + + const targetDir = inputs.root_directory.default; + console.log('Using default root directory from workflow:', targetDir); + this._rootDirectory = { + absolute: path.resolve(this.repoRoot, targetDir), + relative: targetDir.replace(/^\/+/, '') + }; + } + + console.log('Final root directory configuration:', { + absolute: this._rootDirectory.absolute, + relative: this._rootDirectory.relative + }); + + // Handle other inputs + if (process.env.INPUT_PULL_NUMBER) { + console.log('Setting pull number from env:', process.env.INPUT_PULL_NUMBER); + this.repository.pullNumber = parseInt(process.env.INPUT_PULL_NUMBER); + } + + this.excludedDirectories = this.parseCommaSeparatedInput( + process.env.INPUT_EXCLUDED_DIRECTORIES, + ['node_modules', 'dist', 'test'] + ); + + this.pullRequestReviewers = this.parseCommaSeparatedInput( + process.env.INPUT_REVIEWERS, + [] + ); + } + + private parseCommaSeparatedInput(input: string | undefined, defaultValue: string[]): string[] { + if (!input) return defaultValue; + return input + .split(',') + .map(item => item.trim()) + .filter(Boolean); + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/DirectoryTraversal.ts b/scripts/jsdoc-automation/src/DirectoryTraversal.ts new file mode 100644 index 0000000000..3398f8913b --- /dev/null +++ b/scripts/jsdoc-automation/src/DirectoryTraversal.ts @@ -0,0 +1,149 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Configuration } from './Configuration.js'; + + + +/** + * DirectoryTraversal class for traversing through directories and files. + * @class DirectoryTraversal + */ +export class DirectoryTraversal { + /** + * Directories that should always be excluded from scanning, + * regardless of configuration + */ + private static readonly FORCED_EXCLUDED_DIRS = [ + 'node_modules', + '.git', + 'dist', + 'build', + 'coverage', + '.next', + '.nuxt', + '.cache', + 'tmp', + 'temp', + '.turbo', + '.husky', + '.github', + '.vscode', + 'public', + 'static' + ]; + + /** + * Constructor for directory traversal + * @param {Configuration} config - Configuration object containing paths and exclusions + * @param {string[]} [prFiles=[]] - PR files to process + */ + constructor( + private config: Configuration, + public prFiles: string[] = [] + ) {} + + /** + * Gets the absolute path for a file + */ + public getAbsolutePath(filePath: string): string { + return this.config.toAbsolutePath(filePath); + } + + /** + * Gets the repository-relative path for a file + */ + public getRelativePath(filePath: string): string { + return this.config.toRelativePath(filePath); + } + + /** + * Traverses the directory based on PRFiles or all files in the root directory. + * If PRFiles are detected, processes only files from the PR. + * Otherwise, scans all files in the root directory for TypeScript files. + * + * + * @returns An array of string containing the files to process. + */ + public traverse(): string[] { + if (this.prFiles.length > 0) { + console.log('Detected PR Files:', this.prFiles); + + // PR files are already relative to repo root, filter and convert to absolute paths + const files = this.prFiles + .filter((file) => { + // Convert PR file (repo-relative) to absolute path + const absolutePath = this.config.toAbsolutePath(file); + + // Check if the file is within our target directory + const isInTargetDir = absolutePath.startsWith(this.config.absolutePath); + + return ( + isInTargetDir && + fs.existsSync(absolutePath) && + !this.isExcluded(absolutePath) && + path.extname(file) === '.ts' + ); + }) + .map(file => this.config.toAbsolutePath(file)); + + console.log('Files to process:', files); + return files; + } else { + console.log('No PR Files Detected, Scanning all files in root directory'); + const typeScriptFiles: string[] = []; + + const traverseDirectory = (currentDirectory: string) => { + const files = fs.readdirSync(currentDirectory); + + files.forEach((file) => { + const filePath = path.join(currentDirectory, file); + const stats = fs.statSync(filePath); + + if (stats.isDirectory()) { + if (!this.isExcluded(filePath)) { + traverseDirectory(filePath); + } + } else if (stats.isFile() && !this.isExcluded(filePath)) { + if (path.extname(file) === '.ts') { + typeScriptFiles.push(filePath); + } + } + }); + }; + + traverseDirectory(this.config.absolutePath); + return typeScriptFiles; + } + } + + /** + * Check if a file path is excluded based on the excluded directories and files + */ + private isExcluded(absolutePath: string): boolean { + // Get path relative to the target directory for exclusion checking + const relativeToTarget = path.relative(this.config.absolutePath, absolutePath); + + // First check forced excluded directories - these are always excluded + const isInForcedExcludedDir = DirectoryTraversal.FORCED_EXCLUDED_DIRS.some(dir => + absolutePath.includes(`${path.sep}${dir}${path.sep}`) || + absolutePath.includes(`${path.sep}${dir}`) || + absolutePath.startsWith(`${dir}${path.sep}`) + ); + + if (isInForcedExcludedDir) { + return true; + } + + // Check if path is in excluded directory + const isExcludedDir = this.config.excludedDirectories.some(dir => + relativeToTarget.split(path.sep)[0] === dir + ); + + // Check if file is excluded + const isExcludedFile = this.config.excludedFiles.some(file => + path.basename(absolutePath) === file + ); + + return isExcludedDir || isExcludedFile; + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/DocumentationGenerator.ts b/scripts/jsdoc-automation/src/DocumentationGenerator.ts new file mode 100644 index 0000000000..1503e62524 --- /dev/null +++ b/scripts/jsdoc-automation/src/DocumentationGenerator.ts @@ -0,0 +1,319 @@ +import { DirectoryTraversal } from './DirectoryTraversal.js'; +import { TypeScriptParser } from './TypeScriptParser.js'; +import { JsDocAnalyzer } from './JsDocAnalyzer.js'; +import { JsDocGenerator } from './JsDocGenerator.js'; +import type { TSESTree } from '@typescript-eslint/types'; +import { ASTQueueItem, FullModeFileChange, PrModeFileChange } from './types/index.js'; +import { GitManager } from './GitManager.js'; +import fs from 'fs'; +import { Configuration } from './Configuration.js'; +import path from 'path'; +import { AIService } from './AIService.js'; + +/** + * Class representing a Documentation Generator. + * + */ +export class DocumentationGenerator { + public missingJsDocQueue: ASTQueueItem[] = []; + public existingJsDocQueue: ASTQueueItem[] = []; + private hasChanges: boolean = false; + private fileContents: Map = new Map(); + private branchName: string = ''; + private fileOffsets: Map = new Map(); + + /** + * Constructor for initializing the object with necessary dependencies. + * + * @param {DirectoryTraversal} directoryTraversal - Instance of DirectoryTraversal class. + * @param {TypeScriptParser} typeScriptParser - Instance of TypeScriptParser class. + * @param {JsDocAnalyzer} jsDocAnalyzer - Instance of JsDocAnalyzer class. + * @param {JsDocGenerator} jsDocGenerator - Instance of JsDocGenerator class. + * @param {GitManager} gitManager - Instance of GitManager class. + * @param {Configuration} configuration - Instance of Configuration class. + * @param {AIService} aiService - Instance of AIService class. + */ + + constructor( + public directoryTraversal: DirectoryTraversal, + public typeScriptParser: TypeScriptParser, + public jsDocAnalyzer: JsDocAnalyzer, + public jsDocGenerator: JsDocGenerator, + public gitManager: GitManager, + public configuration: Configuration, + public aiService: AIService + ) { } + + /** + * Asynchronously generates JSDoc comments for the TypeScript files based on the given pull request number or full mode. + * + * @param pullNumber - Optional. The pull request number to generate JSDoc comments for. + * @returns A promise that resolves once the JSDoc generation process is completed. + */ + public async generate(pullNumber?: number): Promise { + let fileChanges: PrModeFileChange[] | FullModeFileChange[] = []; + this.fileOffsets.clear(); + + if (pullNumber) { + const prFiles = await this.gitManager.getFilesInPullRequest(pullNumber); + fileChanges = prFiles.filter(file => { + // Convert PR file path (which is repo-relative) to absolute path + const absolutePath = this.configuration.toAbsolutePath(file.filename); + + // Check if file is in target directory + const isInTargetDir = absolutePath.startsWith(this.configuration.absolutePath); + + // Get path relative to target directory for exclusion checking + const relativeToTarget = path.relative( + this.configuration.absolutePath, + absolutePath + ); + + // Check exclusions + const isExcluded = + // Check excluded directories + this.configuration.excludedDirectories.some(dir => + relativeToTarget.split(path.sep)[0] === dir + ) || + // Check excluded files + this.configuration.excludedFiles.some(excludedFile => + path.basename(absolutePath) === excludedFile + ); + + return isInTargetDir && !isExcluded; + }); + } else { + const typeScriptFiles = this.directoryTraversal.traverse(); + fileChanges = typeScriptFiles.map((file) => ({ + filename: this.configuration.toRelativePath(file), + status: 'modified', + })); + } + + // Process each TypeScript file + for (const fileChange of fileChanges) { + if (fileChange.status === 'deleted') continue; + + const filePath = this.configuration.toAbsolutePath(fileChange.filename); + console.log(`Processing file: ${filePath}`, 'resetting file offsets', 'from ', this.fileOffsets.get(filePath), 'to 0'); + this.fileOffsets.set(filePath, 0); + + // Load and store file content + if (fileChange.status === 'added' && 'contents_url' in fileChange) { + console.log('Getting file content from GitHub API'); + const fileContent = await this.getFileContent(fileChange.contents_url); + this.fileContents.set(filePath, fileContent); + } else { + console.log('Getting file content from local file system'); + const fileContent = fs.readFileSync(filePath, 'utf-8'); + this.fileContents.set(filePath, fileContent); + } + + const ast = this.typeScriptParser.parse(filePath); + if (!ast || !ast.body) { + console.log('Invalid AST found for file', filePath); + continue; + } + + this.jsDocAnalyzer.analyze(ast); + + // Process each node in the file + for (const node of ast.body) { + this.processNode(node, filePath, ast); + } + } + + // Process nodes that need JSDoc + if (this.missingJsDocQueue.length > 0) { + this.branchName = `docs-update-${pullNumber || 'full'}-${Date.now()}`; + await this.gitManager.createBranch(this.branchName, this.configuration.branch); + + // Process each node + for (const queueItem of this.missingJsDocQueue) { + let comment = ''; + if (queueItem.className !== undefined) { + comment = await this.jsDocGenerator.generateClassComment(queueItem); + } else { + comment = await this.jsDocGenerator.generateComment(queueItem); + } + await this.updateFileWithJSDoc(queueItem.filePath, comment, queueItem.startLine); + this.hasChanges = true; + } + + // Commit changes if any updates were made + if (this.hasChanges && this.branchName) { + for (const [filePath, content] of this.fileContents) { + await this.gitManager.commitFile( + this.branchName, + this.configuration.toRelativePath(filePath), + content, + `docs: Add JSDoc documentation to ${path.basename(filePath)}` + ); + } + + const prContent = await this.generatePRContent(pullNumber); + await this.gitManager.createPullRequest({ + title: prContent.title, + body: prContent.body, + head: this.branchName, + base: this.configuration.branch, + labels: ['documentation', 'automated-pr'], + reviewers: this.configuration.pullRequestReviewers || [] + }); + } + } + } + + /** + * Processes a single AST node and its children for JSDoc documentation + * @param node - The AST node to process + * @param filePath - Path to the source file + * @param ast - The complete AST + */ + private processNode(node: TSESTree.Node, filePath: string, ast: TSESTree.Program): void { + if (!this.jsDocAnalyzer.shouldHaveJSDoc(node)) return; + + // Process the main node + const jsDocComment = this.jsDocAnalyzer.getJSDocComment(node, ast.comments || []); + const queueItem = this.jsDocAnalyzer.createQueueItem( + node, + filePath, + this.getNodeCode(filePath, node) + ); + + if (jsDocComment) { + queueItem.jsDoc = jsDocComment; + this.existingJsDocQueue.push(queueItem); + } else { + this.missingJsDocQueue.push(queueItem); + } + + // Process any documentable children (like class methods) + const children = this.jsDocAnalyzer.getDocumentableChildren(node); + for (const child of children) { + const childJsDocComment = this.jsDocAnalyzer.getJSDocComment(child, ast.comments || []); + const childQueueItem = this.jsDocAnalyzer.createQueueItem( + child, + filePath, + this.getNodeCode(filePath, child) + ); + + if (childJsDocComment) { + childQueueItem.jsDoc = childJsDocComment; + this.existingJsDocQueue.push(childQueueItem); + } else { + this.missingJsDocQueue.push(childQueueItem); + } + } + } + + /** + * Updates a file with JSDoc at a specific position. + * @param {string} filePath - The path to the file to update. + * @param {string} jsDoc - The JSDoc to insert into the file. + * @param {number} insertLine - The line number where the JSDoc should be inserted. + * @returns {Promise} - A Promise that resolves once the file has been updated. + */ + private async updateFileWithJSDoc(filePath: string, jsDoc: string, insertLine: number): Promise { + const content = this.fileContents.get(filePath) || ''; + const lines = content.split('\n'); + const currentOffset = this.fileOffsets.get(filePath) || 0; + const newLines = (jsDoc.match(/\n/g) || []).length + 1; + const adjustedLine = insertLine + currentOffset; + + lines.splice(adjustedLine - 1, 0, jsDoc); + this.fileOffsets.set(filePath, currentOffset + newLines); + this.fileContents.set(filePath, lines.join('\n')); + } + + /** + * Retrieves the code of a specific node from a given file. + * + * @param {string} filePath - The path to the file containing the node. + * @param {TSESTree.Node} node - The node to extract the code from. + * @returns {string} The code belonging to the specified node. + */ + public getNodeCode(filePath: string, node: TSESTree.Node): string { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const lines = fileContent.split('\n'); + const startLine = node.loc?.start.line || 0; + const endLine = node.loc?.end.line || 0; + return lines.slice(startLine - 1, endLine).join('\n'); + } + + /** + * Retrieves the content of a file from the provided URL. + * + * @param {string} contentsUrl - The URL of the file contents + * @returns {Promise} The content of the file as a string + */ + private async getFileContent(contentsUrl: string): Promise { + try { + const response = await fetch(contentsUrl); + const data = await response.json(); + return Buffer.from(data.content, 'base64').toString('utf-8'); + } catch (error) { + console.error('Error fetching file content from GitHub API, ensure the PR has been merged'); + return ''; + } + } + + /** + * Asynchronously generates a pull request title and description for adding JSDoc documentation. + * @param {number} [pullNumber] - Optional pull request number that the JSDoc documentation is related to. + * @returns {Promise<{ title: string; body: string }>} - A promise that resolves to an object with a title and body for the pull request. + */ + private async generatePRContent(pullNumber?: number): Promise<{ title: string; body: string }> { + const modifiedFiles = Array.from(this.fileContents.keys()); + const filesContext = modifiedFiles.map(file => `- ${file}`).join('\n'); + + const prompt = `Generate a pull request title and description for adding JSDoc documentation. + Context: + - ${modifiedFiles.length} files were modified + - Files modified:\n${filesContext} + - This is ${pullNumber ? `related to PR #${pullNumber}` : 'a full repository documentation update'} + - This is an automated PR for adding JSDoc documentation + + Generate both a title and description. The description should be detailed and include: + 1. A clear summary of changes + 2. Summary of modified files + 3. Instructions for reviewers + + Format the response as a JSON object with 'title' and 'body' fields.`; + + const response = await this.aiService.generateComment(prompt); + try { + const content = JSON.parse(response); + return { + title: content.title, + body: content.body + }; + } catch (error) { + console.error('Error parsing AI response for PR content generation, using default values'); + return { + title: `docs: Add JSDoc documentation${pullNumber ? ` for PR #${pullNumber}` : ''}`, + body: this.generateDefaultPRBody() + }; + } + } + + /** + * Generates the default pull request body for adding JSDoc documentation to TypeScript files. + * + * @returns {string} The default pull request body containing information about the changes made. + */ + private generateDefaultPRBody(): string { + const changes = Array.from(this.fileContents.keys()) + .map(filePath => `- Added JSDoc documentation to \`${filePath}\``) + .join('\n'); + + return `## 📝 Documentation Updates + This PR adds JSDoc documentation to TypeScript files that were missing proper documentation. + + ### 🔍 Changes Made: + ${changes} + + ### 🤖 Generated by Documentation Bot + This is an automated PR created by the documentation generator tool.`; + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/GitManager.ts b/scripts/jsdoc-automation/src/GitManager.ts new file mode 100644 index 0000000000..366cdd2abe --- /dev/null +++ b/scripts/jsdoc-automation/src/GitManager.ts @@ -0,0 +1,165 @@ +import { Octokit } from "@octokit/rest"; +import { PrModeFileChange, Repository } from "./types/index.js"; +import dotenv from 'dotenv'; + +dotenv.config(); + +interface CreatePullRequestOptions { + title: string; + body: string; + head: string; + base: string; + labels?: string[]; + reviewers?: string[]; +} + +/** + * Manages operations related to interacting with a Git repository using the GitHub API. + */ +export class GitManager { + private octokit: Octokit; + + /** + * Constructor for a class that initializes the Octokit instance with the provided Repository and checks if the GITHUB_ACCESS_TOKEN is set in the environment. + * @param {Repository} repository - The repository instance to use + * @throws {Error} Throws an error if the GITHUB_ACCESS_TOKEN is not set + */ + constructor(public repository: Repository) { + if (!process.env.GITHUB_ACCESS_TOKEN) { + throw new Error('GITHUB_ACCESS_TOKEN is not set'); + } + this.octokit = new Octokit({ + auth: process.env.GITHUB_ACCESS_TOKEN, + }); + } + + /** + * Retrieve files in a specific pull request. + * @param {number} pullNumber - The number of the pull request to get files from. + * @returns {Promise} - Array of objects representing file changes in the pull request. + */ + public async getFilesInPullRequest(pullNumber: number): Promise { + const { data } = await this.octokit.pulls.listFiles({ + owner: this.repository.owner, + repo: this.repository.name, + pull_number: pullNumber, + }); + + return data.map((file: any) => ({ + filename: file.filename, + status: file.status, + additions: file.additions, + deletions: file.deletions, + changes: file.changes, + contents_url: file.contents_url, + })); + } + + /** + * Creates a new branch in the GitHub repository using the given branch name and base branch. + * + * @param {string} branchName - The name of the new branch to be created. + * @param {string} baseBranch - The name of the branch to base the new branch off of. + * @returns {Promise} - A Promise that resolves when the branch is successfully created. + */ + public async createBranch(branchName: string, baseBranch: string): Promise { + await this.octokit.git.createRef({ + owner: this.repository.owner, + repo: this.repository.name, + ref: `refs/heads/${branchName}`, + sha: (await this.octokit.git.getRef({ + owner: this.repository.owner, + repo: this.repository.name, + ref: `heads/${baseBranch}`, + })).data.object.sha, + }); + } + + /** + * Asynchronously commits a file to a repository using the GitHub API. + * + * @param {string} branchName - The name of the branch to commit the file to. + * @param {string} filePath - The path of the file to commit. + * @param {string} content - The content of the file to commit. + * @param {string} message - The commit message. + * @returns {Promise} A promise that resolves when the file is successfully committed. + */ + public async commitFile(branchName: string, filePath: string, content: string, message: string): Promise { + try { + const { data } = await this.octokit.repos.getContent({ + owner: this.repository.owner, + repo: this.repository.name, + path: filePath, + ref: branchName, + }); + + await this.octokit.repos.createOrUpdateFileContents({ + owner: this.repository.owner, + repo: this.repository.name, + path: filePath, + message: message, + content: Buffer.from(content).toString('base64'), + sha: (data as any).sha, + branch: branchName, + }); + } catch (error: any) { + if (error.status === 404) { + // File doesn't exist in the target branch, create a new file + await this.octokit.repos.createOrUpdateFileContents({ + owner: this.repository.owner, + repo: this.repository.name, + path: filePath, + message: message, + content: Buffer.from(content).toString('base64'), + branch: branchName, + }); + } else { + throw error; + } + } + } + + /** + * Create a pull request using the provided options. + * @param {CreatePullRequestOptions} options - The options for creating the pull request. + * @returns {Promise} A Promise that resolves once the pull request is successfully created. + */ + public async createPullRequest(options: CreatePullRequestOptions): Promise { + try { + // Create the pull request + const { data: pr } = await this.octokit.pulls.create({ + owner: this.repository.owner, + repo: this.repository.name, + title: options.title, + body: options.body, + head: options.head, + base: options.base + }); + + // Add labels if provided + if (options.labels && options.labels.length > 0) { + await this.octokit.issues.addLabels({ + owner: this.repository.owner, + repo: this.repository.name, + issue_number: pr.number, + labels: options.labels + }); + } + + // Add reviewers if provided + if (options.reviewers && options.reviewers.length > 0) { + await this.octokit.pulls.requestReviewers({ + owner: this.repository.owner, + repo: this.repository.name, + pull_number: pr.number, + reviewers: options.reviewers + }); + } + + console.log(`Created PR #${pr.number}: ${pr.html_url}`); + } catch (error) { + console.error('Error creating pull request:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/JsDocAnalyzer.ts b/scripts/jsdoc-automation/src/JsDocAnalyzer.ts new file mode 100644 index 0000000000..223d1893b4 --- /dev/null +++ b/scripts/jsdoc-automation/src/JsDocAnalyzer.ts @@ -0,0 +1,390 @@ +import type { TSESTree } from '@typescript-eslint/types'; +import { TypeScriptParser } from './TypeScriptParser.js'; +import { ASTQueueItem } from './types/index.js'; + +type AST_NODE_TYPES = { + ClassDeclaration: 'ClassDeclaration'; + FunctionDeclaration: 'FunctionDeclaration'; + TSTypeAliasDeclaration: 'TSTypeAliasDeclaration'; + TSEnumDeclaration: 'TSEnumDeclaration'; + MethodDefinition: 'MethodDefinition'; + TSMethodSignature: 'TSMethodSignature'; + TSInterfaceDeclaration: 'TSInterfaceDeclaration'; + TSPropertySignature: 'TSPropertySignature'; + ExportNamedDeclaration: 'ExportNamedDeclaration'; + Identifier: 'Identifier'; +}; + +const AST_NODE_TYPES = { + ClassDeclaration: 'ClassDeclaration', + FunctionDeclaration: 'FunctionDeclaration', + TSTypeAliasDeclaration: 'TSTypeAliasDeclaration', + TSEnumDeclaration: 'TSEnumDeclaration', + MethodDefinition: 'MethodDefinition', + TSMethodSignature: 'TSMethodSignature', + TSInterfaceDeclaration: 'TSInterfaceDeclaration', + TSPropertySignature: 'TSPropertySignature', + ExportNamedDeclaration: 'ExportNamedDeclaration', + Identifier: 'Identifier', +} as const; + +type DocumentableNodeType = + | 'ClassDeclaration' + | 'FunctionDeclaration' + | 'TSTypeAliasDeclaration' + | 'TSEnumDeclaration' + | 'MethodDefinition' + | 'TSMethodSignature' + | 'TSInterfaceDeclaration' + | 'TSPropertySignature'; + +interface Location { + start: number; + end: number; +} + +/** + * Class to analyze JSDoc comments in TypeScript code. + */ +export class JsDocAnalyzer { + + private documentableTypes: Set = new Set([ + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.TSTypeAliasDeclaration, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.TSMethodSignature, + AST_NODE_TYPES.TSPropertySignature, + AST_NODE_TYPES.TSInterfaceDeclaration + ]); + + /** + * Type guard to check if a node is a ClassDeclaration + */ + private isClassDeclaration(node: TSESTree.Node): node is TSESTree.ClassDeclaration { + return node.type === AST_NODE_TYPES.ClassDeclaration; + } + + /** + * Type guard to check if a node is an InterfaceDeclaration + */ + private isInterfaceDeclaration(node: TSESTree.Node): node is TSESTree.TSInterfaceDeclaration { + return node.type === 'TSInterfaceDeclaration'; // Changed to match AST + } + + /** + * Type guard to check if a node is a MethodDefinition + */ + private isMethodDefinition(node: TSESTree.Node): node is TSESTree.MethodDefinition { + return node.type === AST_NODE_TYPES.MethodDefinition; + } + + /** + * Type guard for interface method signatures + */ + private isMethodSignature(node: TSESTree.Node): node is TSESTree.TSMethodSignature { + return node.type === AST_NODE_TYPES.TSMethodSignature; + } + + /** + * Type guard for interface property signatures + */ + private isPropertySignature(node: TSESTree.Node): node is TSESTree.TSPropertySignature { + return node.type === AST_NODE_TYPES.TSPropertySignature; + } + + /** + * Type guard for ExportNamedDeclaration nodes + */ + private isExportNamedDeclaration(node: TSESTree.Node): node is TSESTree.ExportNamedDeclaration { + return node.type === AST_NODE_TYPES.ExportNamedDeclaration; + } + + /** + * Type guard to check if a node is an Identifier + * @param node - The node to check + */ + private isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { + return node.type === AST_NODE_TYPES.Identifier; + } + + /** + * Gets the actual node from either a regular node or an exported declaration + * @param node - The AST node to process + * @returns The actual declaration node + */ + private getActualNode(node: TSESTree.Node): TSESTree.Node { + if (this.isExportNamedDeclaration(node) && node.declaration) { + return node.declaration; + } + return node; + } + + /** + * Gets the method name from a MethodDefinition node + * @param node - The method definition node + * @returns The method name or undefined + */ + private getMethodName(node: TSESTree.MethodDefinition): string | undefined { + if (this.isIdentifier(node.key)) { + return node.key.name; + } + return undefined; + } + + /** + * Gets the name of a node if available + */ + private getNodeName(node: TSESTree.Node): string | undefined { + const actualNode = this.getActualNode(node); + + if (this.isMethodDefinition(actualNode)) { + return this.getMethodName(actualNode); + } + + if (this.isMethodSignature(actualNode) || this.isPropertySignature(actualNode)) { + return this.isIdentifier(actualNode.key) ? actualNode.key.name : undefined; + } + + if ('id' in actualNode && actualNode.id && this.isIdentifier(actualNode.id)) { + return actualNode.id.name; + } + + return undefined; + } + + + public missingJsDocNodes: TSESTree.Node[] = []; + + /** + * Constructor for initializing a new instance. + * @param {TypeScriptParser} typeScriptParser - An instance of TypeScriptParser used for parsing TypeScript code. + */ + constructor( + public typeScriptParser: TypeScriptParser, + ) { } + + + + /** + * Analyzes the Abstract Syntax Tree (AST) of a program. + * @param {TSESTree.Program} ast - The AST of the program to analyze. + * @returns {void} + */ + public analyze(ast: TSESTree.Program): void { + this.traverse(ast, ast.comments || []); + } + + /** + * Traverses the AST node and checks for JSDoc comments. + * + * @param {TSESTree.Node} node - The AST node to traverse. + * @param {TSESTree.Comment[]} [comments] - Optional array of comments associated with the node. + */ + private traverse(node: TSESTree.Node, comments?: TSESTree.Comment[]): void { + if (this.shouldHaveJSDoc(node)) { + const jsDocComment = this.getJSDocComment(node, comments || []); + if (!jsDocComment) { + this.missingJsDocNodes.push(node); + } + } + + // Handle specific node types that can have children + if ('body' in node) { + const body = Array.isArray(node.body) ? node.body : [node.body]; + body.forEach(child => { + if (child && typeof child === 'object') { + this.traverse(child as TSESTree.Node, comments); + } + }); + } + + // Handle other common child properties + ['consequent', 'alternate', 'init', 'test', 'update'].forEach(prop => { + if (prop in node && node[prop as keyof TSESTree.Node]) { + this.traverse(node[prop as keyof TSESTree.Node] as TSESTree.Node, comments); + } + }); + } + + /** + * Checks if a node should have JSDoc comments + * @param node - The node to check + * @returns True if the node should have JSDoc + */ + public shouldHaveJSDoc(node: TSESTree.Node): boolean { + const actualNode = this.getActualNode(node); + return this.documentableTypes.has(actualNode.type as DocumentableNodeType); + } + + /** + * Gets any child nodes that should be processed for JSDoc + * @param node - The parent node + * @returns Array of child nodes that need JSDoc + */ + public getDocumentableChildren(node: TSESTree.Node): TSESTree.Node[] { + const actualNode = this.getActualNode(node); + + if (this.isClassDeclaration(actualNode)) { + return actualNode.body.body.filter(this.isMethodDefinition); + } + + // For interfaces, return empty array since we only want to document the interface itself + if (this.isInterfaceDeclaration(actualNode)) { + return []; // Don't process interface members + } + + return []; + } + + /** + * Creates a queue item from a node + */ + public createQueueItem(node: TSESTree.Node, filePath: string, code: string): ASTQueueItem { + const actualNode = this.getActualNode(node); + const nodeName = this.getNodeName(node); + const parentInterface = this.isMethodSignature(actualNode) || this.isPropertySignature(actualNode) + ? this.getParentInterfaceName(node) + : undefined; + const parentClass = this.isMethodDefinition(actualNode) + ? this.getParentClassName(node) + : undefined; + + return { + filePath, + startLine: node.loc?.start.line || 0, + endLine: node.loc?.end.line || 0, + nodeType: actualNode.type, + className: parentClass || parentInterface, + methodName: (this.isMethodDefinition(actualNode) || this.isMethodSignature(actualNode) || this.isPropertySignature(actualNode)) + ? nodeName + : undefined, + name: nodeName!, + code: code, + }; + } + + /** + * Gets the parent class name for a method definition + * @param node - The method node + * @returns The parent class name or undefined + */ + private getParentClassName(node: TSESTree.Node): string | undefined { + let current = node.parent; + while (current) { + const actualNode = this.getActualNode(current); + if (this.isClassDeclaration(actualNode) && this.isIdentifier(actualNode.id!)) { + return actualNode.id.name; + } + current = current.parent; + } + return undefined; + } + + /** + * Gets the parent interface name for a method or property signature + */ + private getParentInterfaceName(node: TSESTree.Node): string | undefined { + let current = node.parent; + while (current) { + const actualNode = this.getActualNode(current); + if (this.isInterfaceDeclaration(actualNode) && this.isIdentifier(actualNode.id)) { + return actualNode.id.name; + } + current = current.parent; + } + return undefined; + } + + + + /** + * Check if the given node is a class node. + * + * @param {TSESTree.Node} node - The node to check + * @returns {boolean} Returns true if the node is a class node, false otherwise + */ + public isClassNode(node: TSESTree.Node): boolean { + if (node.type === 'ClassDeclaration') { + return true; + } + + if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'ClassDeclaration') { + return true; + } + + return false; + } + + /** + * Retrieves the JSDoc comment associated with the given node if properly formatted. + * @param node - The node to check for JSDoc comments + * @param comments - Array of comments to search through + * @returns The JSDoc comment if found and properly spaced, undefined otherwise + */ + public getJSDocComment(node: TSESTree.Node, comments: TSESTree.Comment[]): string | undefined { + if (!this.shouldHaveJSDoc(node)) { + return undefined; + } + + const functionStartLine = node.loc?.start.line; + + return comments.find((comment) => { + const commentEndLine = comment.loc?.end.line; + + // Must be a block comment starting with * (JSDoc style) + const isJSDocStyle = comment.type === 'Block' && comment.value.startsWith('*'); + + // Check if the comment is right before the node (no 1-2 line gaps) + const properSpacing = commentEndLine && functionStartLine && + (functionStartLine - commentEndLine > 2); + + return isJSDocStyle && properSpacing; + })?.value; + } + + /** + * Returns the start and end location of the given Node. + * + * @param {TSESTree.Node} node - The Node to get location from. + * @returns {Location} The start and end location of the Node. + */ + public getNodeLocation(node: TSESTree.Node): Location { + return { + start: node.loc.start.line, + end: node.loc.end.line, + }; + } + + /** + * Retrieves all methods of a specific class or all classes in a given file. + * @param filePath - The path of the file to parse. + * @param className - The name of the class to retrieve methods from. Optional. + * @returns An array of MethodDefinition nodes representing the methods found. + */ + public getClassMethods(filePath: string, className?: string): TSESTree.MethodDefinition[] { + const ast = this.typeScriptParser.parse(filePath); + if (!ast) return []; + + // Find all class declarations in the file + const classNodes = ast.body.filter( + (node: TSESTree.Node): node is TSESTree.ClassDeclaration => + node.type === 'ClassDeclaration' && + // If className is provided, match it, otherwise accept any class + (className ? node.id?.name === className : true) + ); + + // Collect methods from all matching classes + const methods: TSESTree.MethodDefinition[] = []; + for (const classNode of classNodes) { + const classMethods = classNode.body.body.filter( + (node: TSESTree.Node): node is TSESTree.MethodDefinition => + node.type === 'MethodDefinition' + ); + methods.push(...classMethods); + } + + return methods; + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/JsDocGenerator.ts b/scripts/jsdoc-automation/src/JsDocGenerator.ts new file mode 100644 index 0000000000..2aa1e608d3 --- /dev/null +++ b/scripts/jsdoc-automation/src/JsDocGenerator.ts @@ -0,0 +1,77 @@ +import { AIService } from './AIService.js'; +import { ASTQueueItem } from './types/index.js'; + +/** + * A class that generates JSDoc comments for code snippets and classes. + */ +export class JsDocGenerator { + /** + * Constructor for a class that takes in an AIService instance. + * @param {AIService} aiService - The AIService instance to be injected into the class. + */ + constructor(public aiService: AIService) { } + + /** + * Generates a comment based on the given ASTQueueItem. + * + * @param {ASTQueueItem} queueItem - The ASTQueueItem object to generate comment for. + * @returns {Promise} The generated comment. + */ + public async generateComment(queueItem: ASTQueueItem): Promise { + const prompt = this.buildPrompt(queueItem); + const comment = await this.aiService.generateComment(prompt); + return comment; + } + + /** + * Generates a comment for a class based on the given ASTQueueItem. + * + * @param {ASTQueueItem} queueItem - The ASTQueueItem to generate the comment for. + * @returns {Promise} The generated comment for the class. + */ + public async generateClassComment( + queueItem: ASTQueueItem, + ): Promise { + const prompt = this.buildClassPrompt(queueItem); + const comment = await this.aiService.generateComment(prompt); + return comment; + } + + /** + * Builds a prompt with the JSDoc comment for the provided ASTQueueItem code. + * + * @param {ASTQueueItem} queueItem The ASTQueueItem object containing the code to extract the JSDoc comment from. + * @returns {string} The JSDoc comment extracted from the code provided in the ASTQueueItem object. + */ + private buildPrompt(queueItem: ASTQueueItem): string { + return `Generate JSDoc comment for the following code: + + + \`\`\`typescript + ${queueItem.code} + \`\`\` + + Only return the JSDoc comment, not the code itself. + `; + } + + private buildClassPrompt( + queueItem: ASTQueueItem, + ): string { + return `Generate JSDoc comment for the following Class: + + Class name: ${queueItem.code.match(/class (\w+)/)?.[1]} + + Only return the JSDoc for the Class itself, not the methods or anything in the class. + + Only return the JSDoc comment for the class, no other text or code. + + Example: + \`\`\` + /** + * This is a class that does something. It has a method that does something. + */ + \`\`\` + `; + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/TypeScriptFileIdentifier.ts b/scripts/jsdoc-automation/src/TypeScriptFileIdentifier.ts new file mode 100644 index 0000000000..d897d70591 --- /dev/null +++ b/scripts/jsdoc-automation/src/TypeScriptFileIdentifier.ts @@ -0,0 +1,30 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Class representing a TypeScript file identifier. + */ +export class TypeScriptFileIdentifier { + + /** + * Check if the given file is a TypeScript file based on its extension. + * + * @param {string} file - The file to check. + * @returns {boolean} Returns true if the file is a TypeScript file (.ts or .tsx), otherwise false. + */ + public isTypeScriptFile(file: string): boolean { + const extension = path.extname(file); + return extension === '.ts' || extension === '.tsx'; + } + + /** + * Retrieves an array of TypeScript files from the specified directory. + * + * @param {string} directory - The directory path to search for TypeScript files. + * @returns {string[]} - An array of TypeScript files found in the directory. + */ + public getTypeScriptFiles(directory: string): string[] { + const files = fs.readdirSync(directory); + return files.filter((file) => this.isTypeScriptFile(file)); + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/TypeScriptParser.ts b/scripts/jsdoc-automation/src/TypeScriptParser.ts new file mode 100644 index 0000000000..2d40963042 --- /dev/null +++ b/scripts/jsdoc-automation/src/TypeScriptParser.ts @@ -0,0 +1,56 @@ +import * as fs from 'fs'; +import { parse, ParserOptions } from '@typescript-eslint/parser'; + +/** + * A class for parsing TypeScript files. + */ +export class TypeScriptParser { + /** + * Parses the content of a file using the given file path. + * + * @param {string} file - The file path containing the content to be parsed. + * @returns {any} The abstract syntax tree (AST) representation of the parsed content. + */ + public parse(file: string): any { + try { + const content = fs.readFileSync(file, 'utf-8'); + const parserOptions: ParserOptions = { + sourceType: 'module', + ecmaVersion: 2020, + ecmaFeatures: { + jsx: true + }, + range: true, + loc: true, + tokens: true, + comment: true, + errorOnUnknownASTType: false, + errorOnTypeScriptSyntacticAndSemanticIssues: false + }; + + const ast = parse(content, parserOptions); + if (!ast || typeof ast !== 'object') { + console.warn(`Warning: Invalid AST generated for file ${file}`); + return null; + } + return ast; + } catch (error) { + if (error instanceof Error) { + this.handleParseError(error); + } else { + console.error('Unknown error:', error); + } + return null; + } + } + + /** + * Handles a parse error that occurs during TypeScript parsing. + * + * @param {Error} error - The error that occurred during parsing + * @returns {void} + */ + public handleParseError(error: Error): void { + console.error('TypeScript Parsing Error:', error); + } +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/index.ts b/scripts/jsdoc-automation/src/index.ts new file mode 100644 index 0000000000..b3156e0608 --- /dev/null +++ b/scripts/jsdoc-automation/src/index.ts @@ -0,0 +1,93 @@ +import { DirectoryTraversal } from './DirectoryTraversal.js'; +import { TypeScriptParser } from './TypeScriptParser.js'; +import { JsDocAnalyzer } from './JsDocAnalyzer.js'; +import { JsDocGenerator } from './JsDocGenerator.js'; +import { DocumentationGenerator } from './DocumentationGenerator.js'; +import { Configuration } from './Configuration.js'; +import { AIService } from './AIService.js'; +import { GitManager } from './GitManager.js'; + +/** + * Main function for generating documentation. + * Uses configuration initialized from the GitHub workflow file. + * @async + */ +async function main() { + try { + const configuration = new Configuration(); + + const gitManager = new GitManager({ + owner: configuration.repository.owner, + name: configuration.repository.name + }); + + let prFiles: string[] = []; + if (typeof configuration.repository.pullNumber === 'number' + && !isNaN(configuration.repository.pullNumber) + ) { + console.log('Pull Request Number: ', configuration.repository.pullNumber); + try { + const files = await gitManager.getFilesInPullRequest(configuration.repository.pullNumber); + prFiles = files.map((file) => file.filename); + } catch (prError) { + console.error('Error fetching PR files:', { + error: prError, + pullNumber: configuration.repository.pullNumber, + repository: `${configuration.repository.owner}/${configuration.repository.name}` + }); + throw prError; + } + } + + try { + const directoryTraversal = new DirectoryTraversal( + configuration, + prFiles + ); + const typeScriptParser = new TypeScriptParser(); + const jsDocAnalyzer = new JsDocAnalyzer(typeScriptParser); + const aiService = new AIService(); + const jsDocGenerator = new JsDocGenerator(aiService); + + const documentationGenerator = new DocumentationGenerator( + directoryTraversal, + typeScriptParser, + jsDocAnalyzer, + jsDocGenerator, + gitManager, + configuration, + aiService + ); + + // Generate documentation + await documentationGenerator.generate(configuration.repository.pullNumber); + } catch (error) { + console.error('Error during documentation generation:', { + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + timestamp: new Date().toISOString() + }); + process.exit(1); + } + + } catch (error) { + console.error('Critical error during documentation generation:', { + error: error instanceof Error ? { + name: error.name, + message: error.message, + stack: error.stack, + } : error, + timestamp: new Date().toISOString(), + nodeVersion: process.version, + platform: process.platform + }); + process.exit(1); + } +} + + +// Simple error handling for the main function +main().catch(error => { + console.error('Fatal error:', error instanceof Error ? error.message : String(error)); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/jsdoc-automation/src/types/index.ts b/scripts/jsdoc-automation/src/types/index.ts new file mode 100644 index 0000000000..238403b4ae --- /dev/null +++ b/scripts/jsdoc-automation/src/types/index.ts @@ -0,0 +1,29 @@ +export interface ASTQueueItem { + name: string; + filePath: string; + startLine: number; + endLine: number; + nodeType: string; + code: string; + className?: string; + methodName?: string; + jsDoc?: string; +} + +export interface Repository { + owner: string; + name: string; + pullNumber?: number; +} + +export interface FullModeFileChange { + filename: string; + status: string; +} + +export interface PrModeFileChange extends FullModeFileChange { + additions: number; + deletions: number; + changes: number; + contents_url: string; +} \ No newline at end of file diff --git a/scripts/jsdoc-automation/tsconfig.json b/scripts/jsdoc-automation/tsconfig.json new file mode 100644 index 0000000000..777a040a61 --- /dev/null +++ b/scripts/jsdoc-automation/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "node16", + "esModuleInterop": true, + "target": "ES2020", + "moduleResolution": "node16", + "outDir": "dist", + "baseUrl": ".", + "sourceMap": true, + "strict": true + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file