diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b763d82 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +dist +Dockerfile diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4896b45 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @paritytech/opstooling diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..dc8e837 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,108 @@ +name: Publish package to GitHub Packages +on: + push: + branches: + - main + pull_request: + +env: + IMAGE_NAME: action + +jobs: + test-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.3.0 + - name: Check that the image builds + run: docker build . --file Dockerfile + + test-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.3.0 + - name: Extract package.json version + id: package_version + run: echo "VERSION=$(jq '.version' -r package.json)" >> $GITHUB_OUTPUT + - name: Extract action.yml version + uses: mikefarah/yq@master + id: action_image + with: + cmd: yq '.runs.image' 'action.yml' + - name: Parse action.yml version + id: action_version + run: | + echo "IMAGE_VERSION=$(echo $IMAGE_URL | cut -d: -f3)" >> $GITHUB_OUTPUT + env: + IMAGE_URL: ${{ steps.action_image.outputs.result }} + - name: Compare versions + run: | + echo "Verifying that $IMAGE_VERSION from action.yml is the same as $PACKAGE_VERSION from package.json" + [[ $IMAGE_VERSION == $PACKAGE_VERSION ]] + env: + IMAGE_VERSION: ${{ steps.action_version.outputs.IMAGE_VERSION }} + PACKAGE_VERSION: ${{ steps.package_version.outputs.VERSION }} + + tag: + if: github.event_name == 'push' + needs: [test-image, test-versions] + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + tagcreated: ${{ steps.autotag.outputs.tagcreated }} + tagname: ${{ steps.autotag.outputs.tagname }} + steps: + - uses: actions/checkout@v3.3.0 + with: + fetch-depth: 0 + - uses: butlerlogic/action-autotag@stable + id: autotag + with: + head_branch: master + tag_prefix: "v" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Changelog + uses: Bullrich/generate-release-changelog@2.0.2 + id: Changelog + env: + REPO: ${{ github.repository }} + - name: Create Release + if: steps.autotag.outputs.tagname != '' + uses: actions/create-release@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.autotag.outputs.tagname }} + release_name: Release ${{ steps.autotag.outputs.tagname }} + body: | + ${{ steps.Changelog.outputs.changelog }} + publish: + runs-on: ubuntu-latest + permissions: + packages: write + needs: [tag] + if: needs.tag.outputs.tagname != '' + steps: + - uses: actions/checkout@v3 + - name: Build image + run: docker build . --file Dockerfile --tag $IMAGE_NAME + - name: Log into registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin + - name: Push image + run: | + IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Strip "v" prefix from tag name + [[ ! -z $TAG ]] && VERSION=$(echo $TAG | sed -e 's/^v//') + # Use Docker `latest` tag convention + [ "$VERSION" == "main" ] && VERSION=latest + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION + env: + TAG: ${{ needs.tag.outputs.tagname }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10034a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/dist +yarn-error.log + +.DS_Store +.env* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..52ee472 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:18 as Builder + +WORKDIR /action + +COPY package.json yarn.lock ./ + +RUN yarn install --frozen-lockfile + +COPY . . + +RUN yarn run build + +FROM node:18-slim + +COPY --from=Builder /action/dist /action + +ENTRYPOINT ["node", "/action/index.js"] diff --git a/README.md b/README.md index e69305f..0add069 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,218 @@ -# stale-issues-finder -Finds outdated issues and reports them + +# Stale Issue Finder +Finds outdated issues and generates an output data & message. + +Intended to be used with a notification action (Slack/Discord/Email/etc look at the example usage). + +Works great with the [`workflow_dispatch`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) or [`schedule`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule) action events. + +## Why? + +This action is intended for the case where a repository (or an organization) needs to find out what issues have been stale for a while. + +By being agnostic on the result, users can use the output to generate a custom message on their favorite system. + +## Example usage + +You need to create a file in `.github/workflows` and add the following: + +```yml +name: Find stale issues + +on: + workflow_dispatch: + +jobs: + fetch: + permissions: + issues: read + runs-on: ubuntu-latest + steps: + - name: Fetch issues from here + # We add the id to access to this step outputs + id: stale + uses: paritytech/stale-issues-finder@main + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # optional, how many days since the last action for it to be stale + # defaults to 5 + days-stale: 10 + # example showing how to use the content + - name: Produce result + run: | + echo "There are $AMOUNT stale issues in this repository" + echo "$ACTION_ISSUES" + env: + # a number with the amount of stale issues in the repository + AMOUNT: ${{ steps.stale.outputs.stale }}" + # a formatted markdown message + ACTION_ISSUES: ${{ steps.stale.outputs.message }}" +``` + +### Inputs +You can find all the inputs in [the action file](./action.yml) but let's walk through each one of them: + +- `GITHUB_TOKEN`: Token to access to the repository issues. If you are access a different repository be sure to read the [`accessing other repositories`](#accessing-other-repositories) section. + - **required** + - If using on the same repo, you can simply use `${{ github.token }}`. +- `repo`: name of the repository. Example: `https://github.com/paritytech/REPO-NAME-GOES-HERE` + - **defaults** to the repo where this action will be run. + - Setting this value and `owner` allows you to run this action in other repositories (useful if you want to aggregate all the stale issues) + - If set, be sure to read the [`accessing other repositories`](#accessing-other-repositories) section. +- `owner`: name of the organization/user where the repository is. Example: `https://github.com/OWNER-NAME/stale-issues-finder` + - **defaults** to the organization where this action is ran. +- `days-stale`: Amount of days since the last activity for an issue to be considered *stale*. + - **default**: 5 + +#### Accessing other repositories + +The action has the ability to access other repositories but if it can read it or not depends of the repository's visibility. + +The default `${{ github.token }}` variable has enough permissions to read the issues in **public repositories**. +If you want this action to access to the issues in a private repository, then you will need a `Personal Access Token` with `repo` permissions. + +### Outputs +Outputs are needed for your chained actions. If you want to use this information, remember to set an `id` field in the step so you can access it. +You can find all the outputs in [the action file](./action.yml) but let's walk through each one of them: +- `stale`: Amount of stale issues found in the step. It's only the number (`0`, `4`, etc) +- `repo`: Organization and repo name. Written in the format of `owner/repo`. +- `message`: A markdown message with a list of all the stale issues. See the example below. + - If no stale issues were found, it will be `## Repo owner/repo has no stale issues` instead. +- `data`: A json object with the data of the stale issues. See the example below for the format of the data. + +**The `message` and `data` objects are sorted from oldest last change to newest.** + +#### Markdown message + +An example of how the markdown would be produced for this repository: +### Repo paritytech/action-project-sync has 3 stale issues + - [Stop AI from controlling the world](https://github.com/paritytech/stale-issues-finder/issues/15) - Stale for 25 days + - [Lint the repo](https://github.com/paritytech/stale-issues-finder/issues/12) - Stale for 21 days + - [Help me with reading](https://github.com/paritytech/stale-issues-finder/issues/3) - Stale for 18 days + +You can send the data in this format to a Slack/Discord/Matrix server. You can also create a new GitHub issue with this format. + +#### JSON Data +```json +[ + { + "url": "https://github.com/paritytech/stale-issues-finder/issues/15", + "title": "Stop AI from controlling the world", + "daysStale": "25" + }, + { + "url": "https://github.com/paritytech/stale-issues-finder/issues/12", + "title": "Lint the repo", + "daysStale": "21" + }, + { + "url": "https://github.com/paritytech/stale-issues-finder/issues/3", + "title": "Help me with reading", + "daysStale": "18" + } +] +``` + +### Using a GitHub app instead of a PAT +In some cases, specially in big organizations, it is more organized to use a GitHub app to authenticate, as it allows us to give it permissions per repository and we can fine-grain them even better. If you wish to do that, you need to create a GitHub app with the following permissions: +- Repository permissions: + - Issues + - [x] Read + +Because this project is intended to be used with a token we need to do an extra step to generate one from the GitHub app: +- After you create the app, copy the *App ID* and the *private key* and set them as secrets. +- Then you need to modify the workflow file to have an extra step: +```yml + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.PRIVATE_KEY }} + - name: Fetch issues from here + id: stale + uses: paritytech/stale-issues-finder@main + with: + days-stale: 10 + # The previous step generates a token which is used as the input for this action + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} +``` + +Be aware that this is needed only to read issues from **external private repositories**. +If the issue is in the same repository, or the target repository is public, the default `${{ github.token }}` has enough access to read the issues. + +## Example workflow + +Let's make an example. We want to have a workflow that runs every Monday at 9 in the morning and it informs through a slack message in a channel. We can also trigger it manually if we want to. + +This issue needs to run on 3 different repositories: +- The current repository +- `example/abc` repository +- `example/xyz` repository + +```yml +name: Find stale issues + +on: + workflow_dispatch: + schedule: + - cron: '0 9 * * 1' + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Fetch issues from here + id: local + uses: paritytech/stale-issues-finder@main + with: + GITHUB_TOKEN: ${{ github.token }} + - name: Fetch abc issues + id: abc + uses: paritytech/stale-issues-finder@main + with: + GITHUB_TOKEN: ${{ github.token }} + owner: example + repo: abc + - name: Fetch xyz issues + id: xyz + uses: paritytech/stale-issues-finder@main + with: + GITHUB_TOKEN: ${{ github.token }} + owner: example + repo: xyz + - name: Post to a Slack channel + id: slack + uses: slackapi/slack-github-action@v1.23.0 + with: + channel-id: 'CHANNEL_ID,ANOTHER_CHANNEL_ID' + slack-message: "Stale issues this week: \n$LOCAL_ISSUES \n$ABC_ISSUES \n$XYZ_ISSUES" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + LOCAL_ISSUES: ${{ steps.local.outputs.message }}" + ABC_ISSUES: ${{ steps.abc.outputs.message }}" + XYZ_ISSUES: ${{ steps.xyz.outputs.message }}" +``` + +This will produce a message similar to the following: + +Stale issues this week: +### Repo example/local has 1 stale issues + - [Stop AI from controlling the world](https://github.com/example/local/issues/15) - Stale for 25 days +### Repo example/abc has 2 stale issues + - [Lint the repo](https://github.com/example/abc/issues/12) - Stale for 21 days + - [Help me with reading](https://github.com/example/abc/issues/3) - Stale for 18 days +### Repo example/xyz has 3 stale issues + - [La la la](https://github.com/example/xyz/issues/15) - Stale for 25 days + - [Help with lalilulelo](https://github.comexample/xyz/issues/12) - Stale for 21 days + - [Fix the issue with the word 'Patriot'](https://github.com/example/xyz/issues/3) - Stale for 18 days + +## Development +To work on this app, you require +- `Node 18.x` +- `yarn` + +Use `yarn install` to set up the project. + +`yarn build` compiles the TypeScript code to JavaScript. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..d0da8e5 --- /dev/null +++ b/action.yml @@ -0,0 +1,33 @@ +name: "Stale Issues Finder" +description: "Find what issues have been stale for a given time" +author: paritytech +branding: + icon: zoom-in + color: white +inputs: + GITHUB_TOKEN: + required: true + description: The token to access the repo + repo: + required: false + description: The repository to fetch the issues from + owner: + required: false + description: The name of the org/user that owns the repository + days-stale: + required: false + description: How many days have to pass to consider an action "stale" + default: '5' +outputs: + repo: + description: 'The name of the repo in owner/repo pattern' + data: + description: 'A JSON object with the data' + message: + description: 'A markdown formatted message' + stale: + description: 'Amount of stale issues. 0 if none found.' + +runs: + using: 'docker' + image: 'docker://ghcr.io/paritytech/stale-issues-finder/action:0.0.1' diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7985a0 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "stale-issues-finder", + "version": "0.0.1", + "description": "Find what issues have been stale for a given time", + "main": "dist/index.js", + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "build": "ncc build src/index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/stale-issues-finder.git" + }, + "keywords": [ + "github", + "action", + "stale" + ], + "author": "Javier Bullrich ", + "license": "ISC", + "bugs": { + "url": "https://github.com/paritytech/stale-issues-finder/issues" + }, + "homepage": "https://github.com/paritytech/stale-issues-finder#readme", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/github": "^5.1.1", + "moment": "^2.29.4" + }, + "devDependencies": { + "@types/moment": "^2.13.0", + "@types/node": "^18.15.11", + "@vercel/ncc": "^0.36.1", + "typescript": "^5.0.2" + } +} diff --git a/src/github/issuesParser.ts b/src/github/issuesParser.ts new file mode 100644 index 0000000..c0fb677 --- /dev/null +++ b/src/github/issuesParser.ts @@ -0,0 +1,33 @@ +import { debug } from "@actions/core"; +import { GitHub } from "@actions/github/lib/utils"; +import moment from "moment"; + +export interface IssueData { + html_url: string; + title: string; + created_at: string; + updated_at: string; + number: number; +} + +interface Repo { + owner: string, + repo: string; +} + +export const fetchIssues = async (octokit: InstanceType, daysStale: number, repo: Repo): Promise => { + const issues = await octokit.rest.issues.listForRepo({ ...repo, per_page: 100, state: "open" }); + debug(`Found elements ${issues.data.length}`); + + // filter old actions + const filtered = issues.data.filter(od => moment().diff(moment(od.updated_at), "days") > daysStale); + if (filtered.length < 1) { + return [] + } + + // order them from stalest to most recent + const orderedDates = filtered.sort((a, b) => { + return b.updated_at > a.updated_at ? -1 : b.updated_at < a.updated_at ? 1 : 0 + }); + return orderedDates; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3f9117d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,65 @@ +import { getInput, info, setOutput } from "@actions/core"; +import { context, getOctokit } from "@actions/github"; +import { Context } from "@actions/github/lib/context"; +import moment from "moment"; +import { fetchIssues, IssueData } from "./github/issuesParser"; + +const daysSinceDate = (date: string): number => { + return moment().diff(moment(date), 'days') +} + +const generateMarkdownMessage = (issues: IssueData[], repo: { owner: string, repo: string; }) => { + const messages = issues.map(issue => { + return ` - [${issue.title}](${issue.html_url}) - Stale for ${daysSinceDate(issue.updated_at)} days`; + }); + const markdownMessage = `### Repo ${repo.owner}/${repo.repo} has ${issues.length} stale issues\n${messages.join("\n")}`; + return markdownMessage; +} + +const getRepo = (ctx: Context): { owner: string, repo: string } => { + let repo = getInput("repo", { required: false }); + if (!repo) { + repo = ctx.repo.repo; + } + + let owner = getInput("owner", { required: false }); + if (!owner) { + owner = ctx.repo.owner; + } + + return { repo, owner }; +} + +const runAction = async (ctx: Context) => { + const repo = getRepo(ctx); + const token = getInput("GITHUB_TOKEN", { required: true }); + const inputDays = Number.parseInt(getInput("days-stale", { required: false })); + const daysStale = isNaN(inputDays) ? 5 : inputDays; + + const octokit = getOctokit(token); + const staleIssues = await fetchIssues(octokit, daysStale, repo); + + const amountOfStaleIssues = staleIssues.length; + + info(`Found ${amountOfStaleIssues} stale issues.`); + setOutput("repo", `${repo.owner}/${repo.repo}`); + setOutput("stale", amountOfStaleIssues); + + if (amountOfStaleIssues > 0) { + const cleanedData = staleIssues.map(issue => { + return { + url: issue.html_url, + title: issue.title, + daysStale: daysSinceDate(issue.updated_at) + } + }); + + setOutput("data", JSON.stringify(cleanedData)); + const message = generateMarkdownMessage(staleIssues, repo); + setOutput("message", message); + } else { + setOutput("message", `### Repo ${repo.owner}/${repo.repo} has no stale issues`); + } +} + +runAction(context); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a09a8ee --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true + }, + "exclude": [ + "node_modules", + "**/*.test.ts" + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..4f42919 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,208 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@actions/core@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.0.tgz#44551c3c71163949a2f06e94d9ca2157a0cfac4f" + integrity sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug== + dependencies: + "@actions/http-client" "^2.0.1" + uuid "^8.3.2" + +"@actions/github@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@actions/github/-/github-5.1.1.tgz#40b9b9e1323a5efcf4ff7dadd33d8ea51651bbcb" + integrity sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g== + dependencies: + "@actions/http-client" "^2.0.1" + "@octokit/core" "^3.6.0" + "@octokit/plugin-paginate-rest" "^2.17.0" + "@octokit/plugin-rest-endpoint-methods" "^5.13.0" + +"@actions/http-client@^2.0.1": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.1.0.tgz#b6d8c3934727d6a50d10d19f00a711a964599a9f" + integrity sha512-BonhODnXr3amchh4qkmjPMUO8mFi/zLaaCeCAJZqch8iQqyDnVIkySjB38VHAC8IJ+bnlgfOqlhpyCUZHlQsqw== + dependencies: + tunnel "^0.0.6" + +"@octokit/auth-token@^2.4.4": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.6.0.tgz#3376cb9f3008d9b3d110370d90e0a1fcd5fe6085" + integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.3" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^12.11.0": + version "12.11.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz#da5638d64f2b919bca89ce6602d059f1b52d3ef0" + integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== + +"@octokit/plugin-paginate-rest@^2.17.0": + version "2.21.3" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz#7f12532797775640dbb8224da577da7dc210c87e" + integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== + dependencies: + "@octokit/types" "^6.40.0" + +"@octokit/plugin-rest-endpoint-methods@^5.13.0": + version "5.16.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz#7ee8bf586df97dd6868cf68f641354e908c25342" + integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== + dependencies: + "@octokit/types" "^6.39.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.3.tgz#19a022515a5bba965ac06c9d1334514eb50c48b0" + integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": + version "6.41.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.41.0.tgz#e58ef78d78596d2fb7df9c6259802464b5f84a04" + integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== + dependencies: + "@octokit/openapi-types" "^12.11.0" + +"@types/moment@^2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@types/moment/-/moment-2.13.0.tgz#604ebd189bc3bc34a1548689404e61a2a4aac896" + integrity sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ== + dependencies: + moment "*" + +"@types/node@^18.15.11": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + +"@vercel/ncc@^0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.36.1.tgz#d4c01fdbbe909d128d1bf11c7f8b5431654c5b95" + integrity sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw== + +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +moment@*, moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + +node-fetch@^2.6.7: + version "2.6.9" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" + integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + dependencies: + whatwg-url "^5.0.0" + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +typescript@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" + integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==