diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..45f65408 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,163 @@ +# Private Mirrors App + +This app enables development of public repositories in a private context. + +## Concepts + +### Upstream/Downstream Repositories + +Every repository using this app is part of a pair of repositories (called `upstream` and `downstream`). +The `downstream` repository is where feature development happens (in private). +Features are 'released' from the `downstream` repository to branches in the `upstream` repository. + +A given `upstream` could have multiple `downstream` repositories (though this should not be necessary), but a `downstream` repository can only have one `upstream` repository. + +### Root Repository + +The `upstream` repository could be a fork of another repository (which may itself be a fork). +In this case, contributions to the `upstream` repository are likely intended to be contributions to some repository in its network of forks (the `root` repository). + +If the `upstream` repository is NOT a fork, then the `root` repository is the same as the `upstream` repository. + +### Branch Conventions + +The `downstream` repositories have a very strict branch convention: + +- `root` - The `root` branch follows the default branch of the `root` repository. This provides: + 1. A starting point for upstream branches. + 2. A convenient point to branch from when making new feature branches. + 3. A good choice of default branch for the `downstream` repository. +- `feature/` - The feature branches represent internal development on the `downstream` repository. Contents of these branches are not directly reflected on the `upstream` repository. +- `upstream/` - An `upstream/` branch is created whenever a `feature/` branch is created. It is initialised to the current HEAD of the `root` branch. + +The `upstream` repositories also have a (less strict) branch convention: + +- `/` - These follow the `upstream/` branches in `downstream` repositories. + +## Repository Setup + +### Creating an `upstream` repository + +An `upstream` repository can be any public repository in an organization where the app is installed. It does not need any special configuration. + +### Creating a `downstream` repository + +To designate a repository as a `downstream` repository, set its `upstream-repository` custom repository property to the (HTTP) clone URL of the repository you want to use as the `upstream`. For GitHub `upstream` repositories, a short-form of `/` may be used. Currently, only GitHub repositories are supported as upstreams. + +> Beware, setting the `upstream-repository` custom property will overwrite branches to configure the repository as a `downstream` repository, and may incur data loss. + +### Setting the `root` repository + +When the `upstream` repository is a fork, the app assumes that the `root` repository is the parent repository in the chain of forks. That is, navigate up the 'fork-of' relationship chain until you reach a non-fork repository. + +This may not always be the intended `root` repository. The `root-repository` custom repository property can be used to directly set the `root` repository. Similar to the `upstream-repository` property, this takes (HTTP) clone URL or `/`. + +## Developing Features + +The process below uses the following assumptions: + +- The feature name is `foo`. In reality, this would be more descriptive. +- The downstream repository is `corp-internal-mirrors/repo`. +- The upstream repository is `corp/repo`. +- If there is an upstream fork, it is `third-party/repo`. + +### High Level Diagram + +```mermaid +flowchart + subgraph corp-internal-mirrors/repo + corp-internal-mirrors/repo:feature/foo[feature/foo branch] + corp-internal-mirrors/repo:upstream/foo[upstream/foo branch] + end + + subgraph corp/repo + corp/repo:repo/foo[repository/foo branch] + corp/repo:default[default branch] + end + + subgraph third-party/repo + third-party/repo:main[default branch] + end + + corp-internal-mirrors/repo:feature/foo -- Internal Pull Request --> corp-internal-mirrors/repo:upstream/foo + corp-internal-mirrors/repo:upstream/foo -- Private Mirrors App syncs to upstream --> corp/repo:repo/foo + corp/repo:repo/foo -- Pull Request --> corp/repo:default + corp/repo:repo/foo -. Pull Request (if fork) .-> third-party/repo:main +``` + +### Create the feature branch + +1. Ensure the `root` branch of the downstream repository is in sync with the `root` repository. Use the `Resync` button in the GitHub check of the HEAD of the `root` branch if necessary. +2. Checkout the `root` branch of the `downstream` repository. +3. Create a feature branch: `git branch feature/foo`. +4. Develop your feature, and commit: `git commit`. +5. Push your feature: `git push`. + +Observe that pushing the `feature/` branch caused: + +- An `upstream/foo` branch to be created in the `downstream` repository. +- A `repo/foo` branch to be created in the `upstream` repository. + +Also note that your feature is not on either of the branches above. + +### Pushing the feature to the upstream repository. + +1. Create a Pull Request on the `downstream` repository from `feature/foo` to `upstream/foo`. +2. Get the Pull Request approved and merged. + +Observe that your feature is now present on: + +- The `upstream/foo` branch in the `downstream` repository +- The `repo/foo` branch in the `upstream` repository. + +### Contributing the feature to the root repository. + +At this point, the app is no longer involved in the process. +The feature is available to be referenced as a normal Pull Request on github.com. +However, the steps below are mentioned for completeness. + +These operations will need to be performed as a github.com (not EMU) user. +They do NOT require you to be a member of the organization containing the `upstream` repository though. + +1. Create a Pull Request from the `repo/foo` branch of the `upstream` repository to the default branch of the `root` repository (`root` and `upstream` repositories may be the same). +2. Get the Pull Request approved and merged. + +### Cleaning up + +Once the contribution is accepted, it is common to remove the `repo/foo` branch from the `upstream` repository. +After that, it is recommended to remove the `feature/foo` and `upstream/foo` branches from the `downstream` repository. + +## Exceptions + +Sometimes the process is not as smooth as the one described above. +Here are some anticipated exceptions, and recommended approaches to resolving them. + +### Root branch changes + +If the process above takes some time, and the `root` repository's default branch changes (especially in ways that cause merge conflicts), then those changes will need to be included in your branch. + +1. Find the 'Sync' check suite for the `root` branch of the `downstream` repository. +2. Click the 'Resync' button. This will update the `root` branch to match the default branch of the `root` repository. +3. Checkout the `feature/foo` and `root` branches of the `downstream` repositories. +4. Rebase the `feature/foo` branch on top of the updated `root` branch. +5. Push the changes to `feature/foo`. +6. Create a Pull Request from `feature/foo` to `upstream/foo` in the `downstream` repository. +7. Get the Pull Request approved and merged. + +Observe that merging the Pull Request applies the change to the `my-mirror/foo` branch. + +### Feature updates + +You may find that the feature requires some modification before it is accepted as a contribution to the `root` repository. In this case you would: + +1. Checkout the `feature/foo` branch of the `downstream` repository. +2. Make and commit your changes. +3. Push the changes to the `feature/foo` branch of the downstream repository. +4. Create a Pull Request from `feature/foo` to `upstream/foo` in the `downstream` repository. +5. Get the Pull Request approved and merged. + +Observe that merging the Pull Request applies the change to the `my-mirror/foo` branch. + +### Combining changes in the fork + +If you have multiple changes being contributed to an upstream repository, it may be convenient to merge them into the `upstream` repository's default branch, to give you a place to reference a (completely) patched repository. diff --git a/package-lock.json b/package-lock.json index c2517348..a4db8a62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.726.0", "@octokit/auth-app": "6.1.1", "@octokit/graphql-schema": "15.25.0", "@primer/octicons-react": "19.13.0", @@ -18,11 +19,12 @@ "@trpc/client": "10.45.2", "@trpc/react-query": "10.45.2", "@trpc/server": "10.45.2", + "defaults": "^3.0.0", "fast-safe-stringify": "2.1.1", "fuse.js": "7.0.0", "next": "14.2.15", "next-auth": "4.24.11", - "octokit": "3.2.1", + "octokit": "^4.0.2", "probot": "13.4.1", "proxy-agent": "6.5.0", "react": "18.3.1", @@ -31,6 +33,7 @@ "styled-components": "5.3.11", "superjson": "2.2.2", "tempy": "1.0.1", + "tmp-promise": "^3.0.3", "tslog": "4.9.3", "undici": "6.21.0", "zod": "3.23.8" @@ -57,6 +60,7 @@ "prettier": "3.4.2", "ts-jest": "29.2.5", "ts-node": "10.9.2", + "type-fest": "^4.32.0", "typescript": "5.7.2", "typescript-eslint": "7.16.1" }, @@ -85,6 +89,688 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.726.0.tgz", + "integrity": "sha512-n3U1MtSFbY9k3G8oyvy8PlVGIjuAzRmtVx/62/WJ+T2WMzFbTZZlWseJh3DNNFoshxaGDIy+5Wvr3k9GtD+Kdg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.726.0", + "@aws-sdk/client-sts": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.726.0.tgz", + "integrity": "sha512-NM5pjv2qglEc4XN3nnDqtqGsSGv1k5YTmzDo3W3pObItHmpS8grSeNfX9zSH+aVl0Q8hE4ZIgvTPNZ+GzwVlqg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz", + "integrity": "sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.726.0.tgz", + "integrity": "sha512-047EqXv2BAn/43eP92zsozPnR3paFFMsj5gjytx9kGNtp+WV0fUZNztCOobtouAxBY0ZQ8Xx5RFnmjpRb6Kjsg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.723.0.tgz", + "integrity": "sha512-UraXNmvqj3vScSsTkjMwQkhei30BhXlW5WxX6JacMKVtl95c7z0qOXquTWeTalYkFfulfdirUhvSZrl+hcyqTw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.723.0.tgz", + "integrity": "sha512-OuH2yULYUHTVDUotBoP/9AEUIJPn81GQ/YBtZLoo2QyezRJ2QiO/1epVtbJlhNZRwXrToLEDmQGA2QfC8c7pbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.723.0.tgz", + "integrity": "sha512-DTsKC6xo/kz/ZSs1IcdbQMTgiYbpGTGEd83kngFc1bzmw7AmK92DBZKNZpumf8R/UfSpTcj9zzUUmrWz1kD0eQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-stream": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.726.0.tgz", + "integrity": "sha512-seTtcKL2+gZX6yK1QRPr5mDJIBOatrpoyrO8D5b8plYtV/PDbDW3mtDJSWFHet29G61ZmlNElyXRqQCXn9WX+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.726.0.tgz", + "integrity": "sha512-jjsewBcw/uLi24x8JbnuDjJad4VA9ROCE94uVRbEnGmUEsds75FWOKp3fWZLQlmjLtzsIbJOZLALkZP86liPaw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-ini": "3.726.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.723.0.tgz", + "integrity": "sha512-fgupvUjz1+jeoCBA7GMv0L6xEk92IN6VdF4YcFhsgRHlHvNgm7ayaoKQg7pz2JAAhG/3jPX6fp0ASNy+xOhmPA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.726.0.tgz", + "integrity": "sha512-WxkN76WeB08j2yw7jUH9yCMPxmT9eBFd9ZA/aACG7yzOIlsz7gvG3P2FQ0tVg25GHM0E4PdU3p/ByTOawzcOAg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/token-providers": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.723.0.tgz", + "integrity": "sha512-tl7pojbFbr3qLcOE6xWaNCf1zEfZrIdSJtOPeSXfV/thFMMAvIjgf3YN6Zo1a6cxGee8zrV/C8PgOH33n+Ev/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.723.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.723.0.tgz", + "integrity": "sha512-LLVzLvk299pd7v4jN9yOSaWDZDfH0SnBPb6q+FDPaOCMGBY8kuwQso7e/ozIKSmZHRMGO3IZrflasHM+rI+2YQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.723.0.tgz", + "integrity": "sha512-chASQfDG5NJ8s5smydOEnNK7N0gDMyuPbx7dYYcm1t/PKtnVfvWF+DHCTrRC2Ej76gLJVCVizlAJKM8v8Kg3cg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.723.0.tgz", + "integrity": "sha512-7usZMtoynT9/jxL/rkuDOFQ0C2mhXl4yCm67Rg7GNTstl67u7w5WN1aIRImMeztaKlw8ExjoTyo6WTs1Kceh7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.726.0.tgz", + "integrity": "sha512-hZvzuE5S0JmFie1r68K2wQvJbzyxJFdzltj9skgnnwdvLe8F/tz7MqLkm28uV0m4jeHk0LpiBo6eZaPkQiwsZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@smithy/core": "^3.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz", + "integrity": "sha512-tGF/Cvch3uQjZIj34LY2mg8M2Dr4kYG8VU8Yd0dFnB1ybOEOveIK/9ypUo9ycZpB9oO6q01KRe5ijBaxNueUQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.723.0.tgz", + "integrity": "sha512-hniWi1x4JHVwKElANh9afKIMUhAutHVBRD8zo6usr0PAoj+Waf220+1ULS74GXtLXAPCiNXl5Og+PHA7xT8ElQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.723.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.723.0.tgz", + "integrity": "sha512-LmK3kwiMZG1y5g3LGihT9mNkeNOmwEyPk6HGcJqh0wOSV4QpWoKu2epyKE4MLQNUUlz2kOVbVbOrwmI6ZcteuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.726.0.tgz", + "integrity": "sha512-sLd30ASsPMoPn3XBK50oe/bkpJ4N8Bpb7SbhoxcY3Lk+fSASaWxbbXE81nbvCnkxrZCvkPOiDHzJCp1E2im71A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.723.0.tgz", + "integrity": "sha512-Wh9I6j2jLhNFq6fmXydIpqD1WyQLyTfSxjW9B+PXSnPyk3jtQW8AKQur7p97rO8LAUzVI0bv8kb3ZzDEVbquIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.726.0.tgz", + "integrity": "sha512-iEj6KX9o6IQf23oziorveRqyzyclWai95oZHDJtYav3fvLJKStwSjygO4xSF7ycHcTYeCHSLO1FFOHgGVs4Viw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -2610,26 +3296,259 @@ } }, "node_modules/@octokit/app": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", - "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", - "dependencies": { - "@octokit/auth-app": "^6.0.0", - "@octokit/auth-unauthenticated": "^5.0.0", - "@octokit/core": "^5.0.0", - "@octokit/oauth-app": "^6.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/types": "^12.0.0", - "@octokit/webhooks": "^12.0.4" + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.2.tgz", + "integrity": "sha512-6aKmKvqnJKoVK+kx0mLlBMKmQYoziPw4Rd/PWr0j65QVQlrDXlu6hGU8fmTXt7tNkf/DsubdIaTT4fkoWzCh5g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^7.1.4", + "@octokit/auth-unauthenticated": "^6.1.1", + "@octokit/core": "^6.1.3", + "@octokit/oauth-app": "^7.1.5", + "@octokit/plugin-paginate-rest": "^11.3.6", + "@octokit/types": "^13.6.2", + "@octokit/webhooks": "^13.4.2" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/auth-app": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.1.tgz", - "integrity": "sha512-VrTtzRpyuT5nYGUWeGWQqH//hqEZDV+/yb6+w5wmWpmmUA1Tx950XsAc2mBBfvusfcdF2E7w8jZ1r1WwvfZ9pA==", + "node_modules/@octokit/app/node_modules/@octokit/auth-app": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.4.tgz", + "integrity": "sha512-5F+3l/maq9JfWQ4bV28jT2G/K8eu9OJ317yzXPTGe4Kw+lKDhFaS4dQ3Ltmb6xImKxfCQdqDqMXODhc9YLipLw==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^8.1.2", + "@octokit/auth-oauth-user": "^5.1.2", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-app": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.2.tgz", + "integrity": "sha512-3woNZgq5/S6RS+9ZTq+JdymxVr7E0s4EYxF20ugQvgX3pomdPUL5r/XdTY9wALoBM2eHVy4ettr5fKpatyTyHw==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^7.1.2", + "@octokit/auth-oauth-user": "^5.1.2", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-device": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.2.tgz", + "integrity": "sha512-gTOIzDeV36OhVfxCl69FmvJix7tJIiU6dlxuzLVAzle7fYfO8UDyddr9B+o4CFQVaMBLMGZ9ak2CWMYcGeZnPw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^5.1.3", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-user": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.2.tgz", + "integrity": "sha512-PgVDDPJgZYb3qSEXK4moksA23tfn68zwSAsQKZ1uH6IV9IaNEYx35OXXI80STQaLYnmEE86AgU0tC1YkM4WjsA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^7.1.2", + "@octokit/oauth-methods": "^5.1.2", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/auth-unauthenticated": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.1.tgz", + "integrity": "sha512-bGXqdN6RhSFHvpPq46SL8sN+F3odQ6oMNLWc875IgoqcC3qus+fOL2th6Tkl94wvdSTy8/OeHzWy/lZebmnhog==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/core": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz", + "integrity": "sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.1.2", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/graphql": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/openapi-types": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" + }, + "node_modules/@octokit/app/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.0.tgz", + "integrity": "sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/types": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.7.0.tgz", + "integrity": "sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^23.0.1" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/webhooks": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.4.2.tgz", + "integrity": "sha512-fakbgkCScapQXPxyqx2jZs/Y3jGlyezwUp7ATL7oLAGJ0+SqBKWKstoKZpiQ+REeHutKpYjY9UtxdLSurwl2Tg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-webhooks-types": "8.5.1", + "@octokit/request-error": "^6.1.6", + "@octokit/webhooks-methods": "^5.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/@octokit/webhooks-methods": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", + "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/app/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" + }, + "node_modules/@octokit/app/node_modules/universal-github-app-jwt": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", + "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==", + "license": "MIT" + }, + "node_modules/@octokit/app/node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "license": "ISC" + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.1.tgz", + "integrity": "sha512-VrTtzRpyuT5nYGUWeGWQqH//hqEZDV+/yb6+w5wmWpmmUA1Tx950XsAc2mBBfvusfcdF2E7w8jZ1r1WwvfZ9pA==", "dependencies": { "@octokit/auth-oauth-app": "^7.1.0", "@octokit/auth-oauth-user": "^4.1.0", @@ -2846,154 +3765,306 @@ } }, "node_modules/@octokit/oauth-app": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", - "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.5.tgz", + "integrity": "sha512-/Y2MiwWDlGUK4blKKfjJiwjzu/FzwKTTTfTZAAQ0QbdBIDEGJPWhOFH6muSN86zaa4tNheB4YS3oWIR2e4ydzA==", + "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^7.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/auth-unauthenticated": "^5.0.0", - "@octokit/core": "^5.0.0", - "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/oauth-methods": "^4.0.0", + "@octokit/auth-oauth-app": "^8.1.2", + "@octokit/auth-oauth-user": "^5.1.2", + "@octokit/auth-unauthenticated": "^6.1.1", + "@octokit/core": "^6.1.3", + "@octokit/oauth-authorization-url": "^7.1.1", + "@octokit/oauth-methods": "^5.1.3", "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^6.0.0" + "universal-user-agent": "^7.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/oauth-methods": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", - "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-app": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.2.tgz", + "integrity": "sha512-3woNZgq5/S6RS+9ZTq+JdymxVr7E0s4EYxF20ugQvgX3pomdPUL5r/XdTY9wALoBM2eHVy4ettr5fKpatyTyHw==", + "license": "MIT", "dependencies": { - "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.0.0", - "btoa-lite": "^1.0.0" + "@octokit/auth-oauth-device": "^7.1.2", + "@octokit/auth-oauth-user": "^5.1.2", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-device": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.2.tgz", + "integrity": "sha512-gTOIzDeV36OhVfxCl69FmvJix7tJIiU6dlxuzLVAzle7fYfO8UDyddr9B+o4CFQVaMBLMGZ9ak2CWMYcGeZnPw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^5.1.3", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-user": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.2.tgz", + "integrity": "sha512-PgVDDPJgZYb3qSEXK4moksA23tfn68zwSAsQKZ1uH6IV9IaNEYx35OXXI80STQaLYnmEE86AgU0tC1YkM4WjsA==", + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "@octokit/auth-oauth-device": "^7.1.2", + "@octokit/oauth-methods": "^5.1.2", + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", - "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "license": "MIT", "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", - "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", - "dev": true, + "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-unauthenticated": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.1.tgz", + "integrity": "sha512-bGXqdN6RhSFHvpPq46SL8sN+F3odQ6oMNLWc875IgoqcC3qus+fOL2th6Tkl94wvdSTy8/OeHzWy/lZebmnhog==", + "license": "MIT", "dependencies": { - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/request": "^9.1.0", - "@octokit/request-error": "^6.1.0", - "@octokit/types": "^13.0.0" + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "dev": true, + "node_modules/@octokit/oauth-app/node_modules/@octokit/core": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz", + "integrity": "sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==", + "license": "MIT", "dependencies": { - "@octokit/types": "^13.0.0", + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.1.2", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-app/node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2", "universal-user-agent": "^7.0.2" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/oauth-authorization-url": { + "node_modules/@octokit/oauth-app/node_modules/@octokit/graphql": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-app/node_modules/@octokit/oauth-authorization-url": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true + "node_modules/@octokit/oauth-app/node_modules/@octokit/openapi-types": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/request": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", - "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", - "dev": true, + "node_modules/@octokit/oauth-app/node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "license": "MIT", "dependencies": { "@octokit/endpoint": "^10.0.0", "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", - "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", - "dev": true, + "node_modules/@octokit/oauth-app/node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "license": "MIT", "dependencies": { - "@octokit/types": "^13.0.0" + "@octokit/types": "^13.6.2" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", - "dev": true, + "node_modules/@octokit/oauth-app/node_modules/@octokit/types": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.7.0.tgz", + "integrity": "sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==", + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "@octokit/openapi-types": "^23.0.1" } }, - "node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": { + "node_modules/@octokit/oauth-app/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" + }, + "node_modules/@octokit/oauth-app/node_modules/universal-user-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "dev": true - }, - "node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + "license": "ISC" + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.3.tgz", + "integrity": "sha512-M+bDBi5H8FnH0xhCTg0m9hvcnppdDnxUqbZyOkxlLblKpLAR+eT2nbDPvJDp0eLrvJWA1I8OX0KHf/sBMQARRA==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/oauth-authorization-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", + "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.7.0.tgz", + "integrity": "sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^23.0.1" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "license": "ISC" + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" }, "node_modules/@octokit/openapi-webhooks-types": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz", - "integrity": "sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==", - "dev": true + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.5.1.tgz", + "integrity": "sha512-i3h1b5zpGSB39ffBbYdSGuAd0NhBAwPyA3QV3LYi/lx4lsbZiu7u2UHgXVUR6EpvOI8REOuVh1DZTRfHoJDvuQ==", + "license": "MIT" }, "node_modules/@octokit/plugin-enterprise-compatibility": { "version": "4.1.0", @@ -3007,17 +4078,6 @@ "node": ">= 18" } }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", - "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=5" - } - }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.1.5", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", @@ -3440,141 +4500,719 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", + "node_modules/@smithy/abort-controller": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", + "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", + "node_modules/@smithy/config-resolver": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", + "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", + "node_modules/@smithy/core": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.0.tgz", + "integrity": "sha512-swFv0wQiK7TGHeuAp6lfF5Kw1dHWsTrCuc+yh4Kh05gEShjsE2RUxHucEerR9ih9JITNtaHcSpUThn5Y/vDw0A==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/middleware-serde": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", + "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", + "license": "Apache-2.0", "dependencies": { - "object-assign": "^4.1.1" + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" - }, - "node_modules/@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", + "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", + "node_modules/@smithy/hash-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", + "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", + "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/props": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/props/-/props-5.1.5.tgz", - "integrity": "sha512-FXhbzq2KueZpGaHxaDm8dowIEWqIMcgsKs6tBl6Y6S0njG9vC8dBMI6WSLDnzMoSqIX3nSKHmOmpzpoihdDewg==", + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", + "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", + "license": "Apache-2.0", "dependencies": { - "styled-system": "^5.1.5" + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.1.tgz", + "integrity": "sha512-hCCOPu9+sRI7Wj0rZKKnGylKXBEd9cQJetzjQqe8cT4PWvtQAbvNVa6cgAONiZg9m8LaXtP9/waxm3C3eO4hiw==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/core": "^3.1.0", + "@smithy/middleware-serde": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", + "node_modules/@smithy/middleware-retry": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.1.tgz", + "integrity": "sha512-n3g2zZFgOWaz2ZYCy8+4wxSmq+HSTD8QKkRhFDv+nkxY1o7gzyp4PDz/+tOdcNPMPZ/A6Mt4aVECYNjQNiaHJw==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.0", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/theme-get": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/theme-get/-/theme-get-5.1.2.tgz", - "integrity": "sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ==", - "dependencies": { - "@styled-system/core": "^5.1.2" + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", + "node_modules/@smithy/middleware-serde": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.1.tgz", + "integrity": "sha512-Fh0E2SOF+S+P1+CsgKyiBInAt3o2b6Qk7YOp2W0Qx2XnfTdfMuSDKUEcnrtpxCzgKJnqXeLUZYqtThaP0VGqtA==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2" + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", + "node_modules/@smithy/middleware-stack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", + "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", + "license": "Apache-2.0", "dependencies": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + "node_modules/@smithy/node-config-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", + "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "node_modules/@smithy/node-http-handler": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.1.tgz", + "integrity": "sha512-ddQc7tvXiVLC5c3QKraGWde761KSk+mboCheZoWtuqnXh5l0WKyFy3NfDIM/dsKrI9HlLVH/21pi9wWK2gUFFA==", + "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@t3-oss/env-core": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.11.1.tgz", + "node_modules/@smithy/property-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", + "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", + "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", + "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", + "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", + "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", + "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", + "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.0.tgz", + "integrity": "sha512-NiboZnrsrZY+Cy5hQNbYi+nVNssXVi2I+yL4CIKNIanOhH8kpC5PKQ2jx/MQpwVr21a3XcVoQBArlpRF36OeEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.1.0", + "@smithy/middleware-endpoint": "^4.0.1", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", + "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.1.tgz", + "integrity": "sha512-nkQifWzWUHw/D0aLPgyKut+QnJ5X+5E8wBvGfvrYLLZ86xPfVO6MoqfQo/9s4bF3Xscefua1M6KLZtobHMWrBg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.0", + "@smithy/types": "^4.1.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.1.tgz", + "integrity": "sha512-LeAx2faB83litC9vaOdwFaldtto2gczUHxfFf8yoRwDU3cwL4/pDm7i0hxsuBCRk5mzHsrVGw+3EVCj32UZMdw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", + "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", + "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", + "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.0.1.tgz", + "integrity": "sha512-Js16gOgU6Qht6qTPfuJgb+1YD4AEO+5Y1UPGWKSp3BNo8ONl/qhXSYDhFKJtwybRJynlCqvP5IeiaBsUmkSPTQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@styled-system/background": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", + "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/border": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", + "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/color": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", + "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", + "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@styled-system/css": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", + "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" + }, + "node_modules/@styled-system/flexbox": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", + "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/grid": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", + "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/layout": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", + "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/position": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", + "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/props": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@styled-system/props/-/props-5.1.5.tgz", + "integrity": "sha512-FXhbzq2KueZpGaHxaDm8dowIEWqIMcgsKs6tBl6Y6S0njG9vC8dBMI6WSLDnzMoSqIX3nSKHmOmpzpoihdDewg==", + "dependencies": { + "styled-system": "^5.1.5" + } + }, + "node_modules/@styled-system/shadow": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", + "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/space": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", + "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/theme-get": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/theme-get/-/theme-get-5.1.2.tgz", + "integrity": "sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/typography": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", + "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/variant": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", + "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", + "dependencies": { + "@styled-system/core": "^5.1.2", + "@styled-system/css": "^5.1.5" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@t3-oss/env-core": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.11.1.tgz", "integrity": "sha512-MaxOwEoG1ntCFoKJsS7nqwgcxLW1SJw238AJwfJeaz3P/8GtkxXZsPPolsz1AdYvUTbe3XvqZ/VCdfjt+3zmKw==", "peerDependencies": { "typescript": ">=5.0.0", @@ -3741,9 +5379,10 @@ "dev": true }, "node_modules/@types/aws-lambda": { - "version": "8.10.137", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.137.tgz", - "integrity": "sha512-YNFwzVarXAOXkjuFxONyDw1vgRNzyH8AuyN19s0bM+ChSu/bzxb5XPxYFLXoqoM+tvgzwR3k7fXcEOW125yJxg==" + "version": "8.10.147", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.147.tgz", + "integrity": "sha512-nD0Z9fNIZcxYX5Mai2CTmFD7wX7UldCkW2ezCF8D1T5hdiLsnTWDGRpfRYntU6VjTdLQjOvyszru7I1c1oCQew==", + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4064,6 +5703,12 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -5379,6 +7024,12 @@ "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6150,6 +7801,18 @@ "node": ">=0.10.0" } }, + "node_modules/defaults": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-3.0.0.tgz", + "integrity": "sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -7230,6 +8893,22 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7287,6 +8966,28 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -7530,447 +9231,114 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", - "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/github-app-webhook-relay-polling": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-app-webhook-relay-polling/-/github-app-webhook-relay-polling-2.0.0.tgz", - "integrity": "sha512-R5y4kC+lUO6hDu8j6qorHoLcIDmZMjzyAbJ1UbVE+EVBGLIkkNVNuYdMXZTnlnvghsgMlzm+PQPN519TJGJ23A==", - "dev": true, - "dependencies": { - "@octokit/app": "^15.1.0", - "@octokit/webhooks-types": "^7.5.1" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/app": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.0.tgz", - "integrity": "sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==", - "dev": true, - "dependencies": { - "@octokit/auth-app": "^7.0.0", - "@octokit/auth-unauthenticated": "^6.0.0", - "@octokit/core": "^6.1.2", - "@octokit/oauth-app": "^7.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/types": "^13.0.0", - "@octokit/webhooks": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-app": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz", - "integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==", - "dev": true, - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.0", - "@octokit/auth-oauth-user": "^5.1.0", - "@octokit/request": "^9.1.1", - "@octokit/request-error": "^6.1.1", - "@octokit/types": "^13.4.1", - "lru-cache": "^10.0.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-app": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz", - "integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==", - "dev": true, - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-device": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz", - "integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==", - "dev": true, - "dependencies": { - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-oauth-user": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz", - "integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==", - "dev": true, - "dependencies": { - "@octokit/auth-oauth-device": "^7.0.1", - "@octokit/oauth-methods": "^5.0.0", - "@octokit/request": "^9.0.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", - "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/auth-unauthenticated": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz", - "integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==", - "dev": true, - "dependencies": { - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", - "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", - "dev": true, - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "dev": true, - "dependencies": { - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", - "dev": true, - "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/oauth-app": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.2.tgz", - "integrity": "sha512-4ntCOZIiTozKwuYQroX/ZD722tzMH8Eicv/cgDM/3F3lyrlwENHDv4flTCBpSJbfK546B2SrkKMWB+/HbS84zQ==", - "dev": true, - "dependencies": { - "@octokit/auth-oauth-app": "^8.0.0", - "@octokit/auth-oauth-user": "^5.0.1", - "@octokit/auth-unauthenticated": "^6.0.0-beta.1", - "@octokit/core": "^6.0.0", - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/oauth-methods": "^5.0.0", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/oauth-authorization-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", - "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.0.tgz", - "integrity": "sha512-n4znWfRinnUQF6TPyxs7EctSAA3yVSP4qlJP2YgI3g9d4Ae2n5F3XDOjbUluKRxPU3rfsgpOboI4O4VtPc6Ilg==", - "dev": true, - "dependencies": { - "@octokit/types": "^13.5.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/request": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz", - "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==", - "dev": true, - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/request-error": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz", - "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==", - "dev": true, - "dependencies": { - "@octokit/types": "^13.0.0" - }, + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "engines": { - "node": ">= 18" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, - "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/webhooks": { - "version": "13.2.7", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.2.7.tgz", - "integrity": "sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "@octokit/openapi-webhooks-types": "8.2.1", - "@octokit/request-error": "^6.0.1", - "@octokit/webhooks-methods": "^5.0.0", - "aggregate-error": "^5.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/@octokit/webhooks-methods": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz", - "integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "engines": { - "node": ">= 18" + "node": ">=8.0.0" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true - }, - "node_modules/github-app-webhook-relay-polling/node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "escape-string-regexp": "5.0.0" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=14.16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14" } }, - "node_modules/github-app-webhook-relay-polling/node_modules/universal-github-app-jwt": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz", - "integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==", - "dev": true - }, - "node_modules/github-app-webhook-relay-polling/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "dev": true + "node_modules/github-app-webhook-relay-polling": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-app-webhook-relay-polling/-/github-app-webhook-relay-polling-2.0.0.tgz", + "integrity": "sha512-R5y4kC+lUO6hDu8j6qorHoLcIDmZMjzyAbJ1UbVE+EVBGLIkkNVNuYdMXZTnlnvghsgMlzm+PQPN519TJGJ23A==", + "dev": true, + "dependencies": { + "@octokit/app": "^15.1.0", + "@octokit/webhooks-types": "^7.5.1" + } }, "node_modules/glob": { "version": "7.1.7", @@ -8018,6 +9386,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -11362,20 +12743,21 @@ } }, "node_modules/octokit": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.2.1.tgz", - "integrity": "sha512-u+XuSejhe3NdIvty3Jod00JvTdAE/0/+XbhIDhefHbu+2OcTRHd80aCiH6TX19ZybJmwPQBKFQmHGxp0i9mJrg==", - "dependencies": { - "@octokit/app": "^14.0.2", - "@octokit/core": "^5.0.0", - "@octokit/oauth-app": "^6.0.0", - "@octokit/plugin-paginate-graphql": "^4.0.0", - "@octokit/plugin-paginate-rest": "11.3.1", - "@octokit/plugin-rest-endpoint-methods": "13.2.2", - "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^8.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^13.0.0" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.1.0.tgz", + "integrity": "sha512-/UrQAOSvkc+lUUWKNzy4ByAgYU9KpFzZQt8DnC962YmQuDiZb1SNJ90YukCCK5aMzKqqCA+z1kkAlmzYvdYKag==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^15.1.2", + "@octokit/core": "^6.1.3", + "@octokit/oauth-app": "^7.1.4", + "@octokit/plugin-paginate-graphql": "^5.2.4", + "@octokit/plugin-paginate-rest": "^11.4.0", + "@octokit/plugin-rest-endpoint-methods": "^13.3.0", + "@octokit/plugin-retry": "^7.1.3", + "@octokit/plugin-throttling": "^9.4.0", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.7.0" }, "engines": { "node": ">= 18" @@ -11439,47 +12821,190 @@ "node": "18 >=18.20 || 20 || >=22" } }, + "node_modules/octokit/node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit/node_modules/@octokit/core": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz", + "integrity": "sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.1.2", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit/node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit/node_modules/@octokit/graphql": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/octokit/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" + }, + "node_modules/octokit/node_modules/@octokit/plugin-paginate-graphql": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.4.tgz", + "integrity": "sha512-pLZES1jWaOynXKHOqdnwZ5ULeVR6tVVCMm+AUbp0htdcyXDU95WbkYdU4R2ej1wKj5Tu94Mee2Ne0PjPO9cCyA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } }, "node_modules/octokit/node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", - "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.0.tgz", + "integrity": "sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==", + "license": "MIT", "dependencies": { - "@octokit/types": "^13.5.0" + "@octokit/types": "^13.7.0" }, "engines": { "node": ">= 18" }, "peerDependencies": { - "@octokit/core": "5" + "@octokit/core": ">=6" } }, "node_modules/octokit/node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", - "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.0.tgz", + "integrity": "sha512-LUm44shlmkp/6VC+qQgHl3W5vzUP99ZM54zH6BuqkJK4DqfFLhegANd+fM4YRLapTvPm4049iG7F3haANKMYvQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/octokit/node_modules/@octokit/plugin-retry": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.3.tgz", + "integrity": "sha512-8nKOXvYWnzv89gSyIvgFHmCBAxfQAOPRlkacUHL9r5oWtp5Whxl8Skb2n3ACZd+X6cYijD6uvmrQuPH/UCL5zQ==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/octokit/node_modules/@octokit/plugin-throttling": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz", + "integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==", + "license": "MIT", "dependencies": { - "@octokit/types": "^13.5.0" + "@octokit/types": "^13.7.0", + "bottleneck": "^2.15.3" }, "engines": { "node": ">= 18" }, "peerDependencies": { - "@octokit/core": "^5" + "@octokit/core": "^6.1.3" + } + }, + "node_modules/octokit/node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit/node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" } }, "node_modules/octokit/node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.7.0.tgz", + "integrity": "sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==", + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "@octokit/openapi-types": "^23.0.1" } }, + "node_modules/octokit/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" + }, + "node_modules/octokit/node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "license": "ISC" + }, "node_modules/oidc-token-hash": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", @@ -13471,6 +14996,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/style-to-object": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", @@ -13681,6 +15212,24 @@ "real-require": "^0.2.0" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13698,6 +15247,15 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -13916,12 +15474,13 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", + "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 1c20cac5..e43f6ae6 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,13 @@ "webhook": "dotenv-load node scripts/webhook-relay.mjs", "build": "SKIP_ENV_VALIDATIONS='true' next build", "start": "next start", - "lint": "SKIP_ENV_VALIDATIONS='true' next lint && prettier --check .", - "lint:fix": "SKIP_ENV_VALIDATIONS='true' next lint --fix && prettier --write .", "bot:build": "tsc ./src/bot/index.ts --noEmit false --esModuleInterop --outDir ./build", "bot:start": "probot run ./build/index.js", "test": "TEST_LOGGING=1 ts-node -O '{\"module\":\"commonjs\"}' node_modules/jest/bin/jest.js", "prepare": "test -d node_modules/husky && husky || echo \"husky is not installed\"" }, "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.726.0", "@octokit/auth-app": "6.1.1", "@octokit/graphql-schema": "15.25.0", "@primer/octicons-react": "19.13.0", @@ -28,11 +27,12 @@ "@trpc/client": "10.45.2", "@trpc/react-query": "10.45.2", "@trpc/server": "10.45.2", + "defaults": "^3.0.0", "fast-safe-stringify": "2.1.1", "fuse.js": "7.0.0", "next": "14.2.15", "next-auth": "4.24.11", - "octokit": "3.2.1", + "octokit": "^4.0.2", "probot": "13.4.1", "proxy-agent": "6.5.0", "react": "18.3.1", @@ -41,6 +41,7 @@ "styled-components": "5.3.11", "superjson": "2.2.2", "tempy": "1.0.1", + "tmp-promise": "^3.0.3", "tslog": "4.9.3", "undici": "6.21.0", "zod": "3.23.8" @@ -67,6 +68,7 @@ "prettier": "3.4.2", "ts-jest": "29.2.5", "ts-node": "10.9.2", + "type-fest": "^4.32.0", "typescript": "5.7.2", "typescript-eslint": "7.16.1" }, @@ -74,6 +76,6 @@ "node": ">= 18" }, "lint-staged": { - "*": "npm run lint:fix" + "*": "" } } diff --git a/src/GitHubCheck.ts b/src/GitHubCheck.ts new file mode 100644 index 00000000..6f47aaf3 --- /dev/null +++ b/src/GitHubCheck.ts @@ -0,0 +1,81 @@ +import { Octokit } from '@octokit/rest'; +import defaults from 'defaults'; +import { PartialDeep } from 'type-fest'; +import { setTimeout } from 'node:timers/promises'; + +type CheckFn = Octokit['checks']['create']; +type CheckParams = NonNullable[0]>; +type CheckReturn = ReturnType; +type PartialCheckParams = PartialDeep; + +interface Context { + octokit: Octokit; + getRemainingTimeInMillis?: () => number; +} + +export class GitHubCheck { + private params: PartialCheckParams; + + private context: Context; + + constructor(params: PartialCheckParams, context: Context) { + this.params = params; + this.context = context; + } + + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + async with(fn: () => Promise): Promise { + const wrapper = async () => { + try { + const result = await fn(); + await this.sendSuccess(); + return result; + } catch (error) { + await this.sendError(error); + throw error; + } + }; + + await this.send({ status: 'in_progress' }); + return Promise.race([wrapper(), this.hookTimeout()]); + } + + async send(overrides: PartialCheckParams): CheckReturn { + console.log('Sending check:', JSON.stringify(defaults(overrides, this.params), null, 2)); + return this.context.octokit.checks.create(defaults(overrides, this.params) as CheckParams); + } + + async sendSuccess(): CheckReturn { + return this.send({ status: 'completed', conclusion: 'success' }); + } + + async sendError(error: unknown): CheckReturn { + return this.send({ + status: 'completed', + conclusion: 'failure', + output: { + text: [this.params.output?.text, error].filter(x => x !== undefined).join('\n\n') + } + }); + } + + private async hookTimeout() { + if (!this.context.getRemainingTimeInMillis) return; + + // Schedule a warning 5 seconds before the function times out + const timeoutWarning = this.context.getRemainingTimeInMillis() - 5000; + + await setTimeout(timeoutWarning); + + const remainingTime = this.context.getRemainingTimeInMillis(); + await this.send({ + status: 'completed', + conclusion: 'timed_out', + output: { + text: [this.params.output?.text, `The check will be timing out in ${remainingTime}ms`] + .filter(x => x !== undefined) + .join('\n\n') + } + }); + } +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 00000000..9175d6ed --- /dev/null +++ b/src/app.ts @@ -0,0 +1,361 @@ +import { Probot, ProbotOctokit, Context, ApplicationFunctionOptions } from 'probot'; +import { Context as LambdaContext } from 'aws-lambda'; +import { Octokit } from 'octokit'; +import { dir } from 'tmp-promise'; +import git from 'isomorphic-git'; +import http from 'isomorphic-git/http/node/index.js'; +import fs from 'node:fs'; +import { DefaultCheck, FeatureCheck, RootCheck, UpstreamCheck } from './checks'; + +const UPSTREAM_PROPERTY_NAME = 'upstream-repository'; +const ROOT_PROPERTY_NAME = 'root-repository'; + +interface LambdaApplicationFunctionOptions extends ApplicationFunctionOptions { + context?: LambdaContext; +} + +export default function bot(app: Probot, options: LambdaApplicationFunctionOptions) { + app.on('custom_property_values.updated', async context => { + const oldUpstreamProp = + context.payload.old_property_values.find(e => e.property_name === UPSTREAM_PROPERTY_NAME)?.value ?? undefined; + const newUpstreamProp = + context.payload.new_property_values.find(e => e.property_name === UPSTREAM_PROPERTY_NAME)?.value ?? undefined; + const changedUpstreamProp = oldUpstreamProp !== newUpstreamProp; + if (changedUpstreamProp) context.log.info(`The ${UPSTREAM_PROPERTY_NAME} property has been updated.`); + + const oldRootProp = + context.payload.old_property_values.find(e => e.property_name === ROOT_PROPERTY_NAME)?.value ?? undefined; + const newRootProp = + context.payload.new_property_values.find(e => e.property_name === ROOT_PROPERTY_NAME)?.value ?? undefined; + const changedRootProp = oldRootProp !== newRootProp; + if (changedRootProp) context.log.info(`The ${ROOT_PROPERTY_NAME} property has been updated.`); + + if (changedUpstreamProp || changedRootProp) { + const defaultCheck = await DefaultCheck.fromContexts(context, options.context); + + await defaultCheck.with(async () => { + // If the upstream property has changed, we need to detach the repository from the old upstream. + if (oldUpstreamProp) await detachUpstream(oldUpstreamProp, context); + // Unless the upstream prop has been removed, we need to (re-)attach the repository to the new upstream. + if (Boolean(newUpstreamProp) || changedRootProp) await attachUpstream(context); + }); + } + }); + + app.on('push', async context => { + context.log.trace(JSON.stringify(context.payload, null, 2)); + + if (!context.payload.repository.custom_properties[UPSTREAM_PROPERTY_NAME]) { + context.log.debug(`Ignoring push for repository without ${UPSTREAM_PROPERTY_NAME} property.`); + return; + } + + const defaultCheck = await DefaultCheck.fromContexts(context, options.context); + await defaultCheck.with(async () => { + // Pushes can either be... + if (context.payload.ref === 'refs/heads/root') { + // ...updating the root branch... + + // ...in which case we make the root check float to the HEAD of the root branch + const check = await RootCheck.fromContexts(context, options.context); + await check.sendSuccess(); + } else if (context.payload.ref.startsWith('refs/heads/feature/')) { + // ...or updating a feature/ branch... + + // ...in which case we ensure an upstream/ branch exists. + const check = await FeatureCheck.fromContexts(context, options.context); + await check.with(async () => ensureUpstreamBranch(context)); + } else if (context.payload.ref.startsWith('refs/heads/upstream/')) { + // ...updating an upstream branch... + + // ...in which case we push the changes to the upstream repository + const check = await UpstreamCheck.fromContexts(context, options.context); + await check.with(async () => pushToUpstream(context)); + } + }); + }); + + /** + * When a resync is requested, we reattach the repository to the upstream repository. + */ + app.on(['check_run.requested_action'], async context => { + // Don't run if the repository does not have an upstream property. + if (!context.payload.repository.custom_properties[UPSTREAM_PROPERTY_NAME]) return; + + // Don't run if the request is not for our app. + const { data: app } = await context.octokit.apps.getAuthenticated(); + if (context.payload.check_run.app.id !== app.id) return; + + const defaultCheck = await DefaultCheck.fromContexts(context, options.context); + await defaultCheck.with(async () => { + if (context.payload.requested_action.identifier === 'resync') await attachUpstream(context); + }); + }); +} + +/** + * This function should be idempotent. + * It should ensure the repository is well connected to the upstream repository, and up-to-date with source. + */ +async function attachUpstream( + context: Context<'custom_property_values.updated' | 'check_run.requested_action'> +): Promise { + context.log.info({ ...context.repo(), msg: 'Disabling dependabot security fixes' }); + try { + await context.octokit.repos.disableAutomatedSecurityFixes(context.repo()); + } catch (e) { + context.log.warn({ + ...context.repo(), + msg: 'Failed to disable automated security fixes. Ignoring and continuing.', + error: e + }); + // A failure to disable here is tolerable. + } + + context.log.info({ ...context.repo(), msg: 'Disabling GitHub Actions' }); + await context.octokit.actions.setGithubActionsPermissionsRepository({ ...context.repo(), enabled: false }); + + const repoDir = await cloneRepository(context); + + context.log.info({ + ...context.repo(), + msg: "Fetching the root repository's default branch." + }); + await git.fetch({ + fs, + dir: repoDir, + http, + remote: 'root', + ref: 'refs/remotes/root/HEAD', + remoteRef: 'HEAD', + singleBranch: true + }); + + context.log.info({ + ...context.repo(), + msg: "Force-pushing the root repository's default branch to the downstream repository's root branch." + }); + await git.push({ + fs, + dir: repoDir, + http, + remote: 'origin', + ref: 'refs/remotes/root/HEAD', + remoteRef: 'refs/heads/root', + force: true + }); + + const { upstreamURL, rootURL } = await getRepositoryURLs(context); + let description = `This repository facilitates contributions to ${rootURL.toString()}`; + if (upstreamURL !== rootURL) description += ` via ${upstreamURL.toString()}.`; + + context.log.info({ ...context.repo(), msg: 'Setting description' }); + await context.octokit.rest.repos.update({ ...context.repo(), description }); +} + +async function detachUpstream( + upstreamProp: string | string[], + context: Context<'custom_property_values.updated'> +): Promise { + context.log.info({ ...context.repo(), upstream: upstreamProp, msg: 'Detaching repository from upstream' }); + + context.log.info({ ...context.repo(), msg: 'Deleting any old upstream/ branches on the repository.' }); + const { data: branches } = await context.octokit.rest.repos.listBranches(context.repo()); + const upstreamBranches = branches.filter(b => b.name.startsWith('upstream/')); + for (const upstreamBranch of upstreamBranches) { + await context.octokit.rest.git.deleteRef({ ...context.repo(), ref: `heads/${upstreamBranch.name}` }); + } + + try { + if (Array.isArray(upstreamProp)) throw new Error(`The ${UPSTREAM_PROPERTY_NAME} property must be a single value.`); + const upstreamURL = parseRepositoryURL(upstreamProp); + const upstreamContext = await getUpstreamContext(context, upstreamURL); + + context.log.info('Deleting any old / branches on the upstream repository.'); + const { data: branches } = await upstreamContext.octokit.rest.repos.listBranches(upstreamContext.repo()); + const downstreamBranches = branches.filter(b => b.name.startsWith(`${context.payload.repository.name}/`)); + for (const downstreamBranch of downstreamBranches) { + await upstreamContext.octokit.rest.git.deleteRef({ + ...upstreamContext.repo(), + ref: `heads/${downstreamBranch.name}` + }); + } + } catch (e) { + context.log.warn( + 'Failed to cleanup / branches on the upstream repository. Ignoring and continuing.', + e + ); + // A failure to cleanup here is tolerable. + } +} + +async function pushToUpstream(context: Context<'push'>) { + const repoDir = await cloneRepository(context); + + const branchName = context.payload.ref.slice(20); + + const ref = `refs/remotes/origin/${branchName}`; + const remoteRef = `refs/heads/${context.payload.repository.name}/${branchName}`; + + context.log.info({ + ...context.repo(), + ref, + remoteRef, + msg: 'Fetching the upstream/ branch from the repository.' + }); + await git.fetch({ + fs, + dir: repoDir, + http, + remote: 'origin', + ref: `refs/remotes/origin/upstream/${branchName}`, + remoteRef: `upstream/${branchName}`, + singleBranch: true + }); + + context.log.info({ + ...context.repo(), + ref, + remoteRef, + msg: 'Pushing the upstream/ branch to the upstream repository.' + }); + await git.push({ + fs, + dir: repoDir, + http, + ref: `refs/remotes/origin/upstream/${branchName}`, + remoteRef: `refs/heads/${context.payload.repository.name}/${branchName}`, + remote: 'upstream' + }); +} + +async function ensureUpstreamBranch(context: Context<'push'>) { + const branchName = context.payload.ref; + if (!branchName.startsWith('refs/heads/feature/')) throw new Error(`Expected ${branchName} to be a feature branch`); + const upstreamBranchName = `upstream/${context.payload.ref.slice(19)}`; + + const { data: branches } = await context.octokit.rest.repos.listBranches(context.repo()); + const upstreamBranch = branches.find(branch => branch.name === upstreamBranchName); + if (upstreamBranch) { + context.log.info({ ...context.repo(), branch: upstreamBranchName, msg: 'Branch already exists on the upstream.' }); + return; + } + + const { data: root } = await context.octokit.rest.git.getRef({ ...context.repo(), ref: 'heads/root' }); + await context.octokit.git.createRef({ + ...context.repo(), + ref: `refs/heads/${upstreamBranchName}`, + sha: root.object.sha + }); +} + +async function getRepositoryURLs(context: Context<'push'>): Promise<{ upstreamURL: URL; rootURL: URL }> { + const upstreamProp = context.payload.repository.custom_properties[UPSTREAM_PROPERTY_NAME]; + + if (!upstreamProp) throw new Error(`The repository does not have the ${UPSTREAM_PROPERTY_NAME} property.`); + if (Array.isArray(upstreamProp)) throw new Error(`The ${UPSTREAM_PROPERTY_NAME} property must be a single value.`); + const upstreamURL = parseRepositoryURL(upstreamProp); + + const rootProp = context.payload.repository.custom_properties[ROOT_PROPERTY_NAME]; + if (Array.isArray(rootProp)) throw new Error(`The ${ROOT_PROPERTY_NAME} property must be a single value.`); + + let rootURL: URL; + if (rootProp) { + rootURL = parseRepositoryURL(rootProp); + } else { + const upstreamContext = await getUpstreamContext(context, upstreamURL); + const { data: upstreamRepository } = await upstreamContext.octokit.rest.repos.get(upstreamContext.repo()); + rootURL = new URL( + upstreamRepository.source?.clone_url ? upstreamRepository.source.clone_url : upstreamRepository.clone_url + ); + } + + return { upstreamURL, rootURL }; +} + +function parseRepositoryURL(url: string): URL { + try { + return new URL(url); + } catch (e) { + if (url.includes('/')) { + return new URL(`https://github.com/${url}.git`); + } else { + throw e; + } + } +} + +function authenticatedURL(url: URL, token: string): URL { + const authenticatedURL = new URL(url); + authenticatedURL.username = 'x-access-token'; + authenticatedURL.password = token; + return authenticatedURL; +} + +function repoFromURL(url: URL): { owner: string; repo: string } { + const components = url.pathname.split('/', 3); + return { + owner: components[1], + repo: components[2].endsWith('.git') ? components[2].slice(0, -4) : components[2] + }; +} + +async function getInstallationToken( + octokitRestApi: ProbotOctokit['rest'], + repositoryURL: URL | string +): Promise { + const repo = repoFromURL(new URL(repositoryURL)); + const { data: installation } = await octokitRestApi.apps.getRepoInstallation(repo); + + const { + data: { token } + } = await octokitRestApi.apps.createInstallationAccessToken({ + installation_id: installation.id, + repositories: [repo.repo] + }); + + return token; +} + +async function cloneRepository(context: Context<'push'>) { + const repoDir = await dir({ unsafeCleanup: true }); + + const originURL = new URL(context.payload.repository.clone_url); + const { upstreamURL, rootURL } = await getRepositoryURLs(context); + + const [authenticatedOriginURL, authenticatedUpstreamURL] = await Promise.all( + [originURL, upstreamURL].map(async url => authenticatedURL(url, await getInstallationToken(context.octokit, url))) + ); + + context.log.info({ + ...context.repo(), + msg: 'Cloning repository', + origin: originURL, + upstream: upstreamURL, + root: rootURL + }); + await git.clone({ + fs, + dir: repoDir.path, + http, + url: authenticatedOriginURL.toString(), + noCheckout: true, + noTags: true, + singleBranch: true, + remote: 'origin' + }); + await git.addRemote({ fs, dir: repoDir.path, remote: 'upstream', url: authenticatedUpstreamURL.toString() }); + await git.addRemote({ fs, dir: repoDir.path, remote: 'root', url: rootURL.toString() }); + + return repoDir.path; +} + +async function getUpstreamContext( + context: Context<'push'>, + upstreamURL: URL +): Promise<{ octokit: Octokit; repo: () => { owner: string; repo: string } }> { + return { + octokit: new Octokit({ auth: await getInstallationToken(context.octokit, upstreamURL) }), + repo: () => repoFromURL(upstreamURL) + }; +} diff --git a/src/app/[organizationId]/forks/[forkId]/page.tsx b/src/app/[organizationId]/forks/[forkId]/page.tsx deleted file mode 100644 index e58debbd..00000000 --- a/src/app/[organizationId]/forks/[forkId]/page.tsx +++ /dev/null @@ -1,678 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -'use client' - -import { useParams } from 'next/navigation' -import { trpc } from '../../../../utils/trpc' - -import { - KebabHorizontalIcon, - PencilIcon, - RepoIcon, - TrashIcon, -} from '@primer/octicons-react' -import { - ActionList, - ActionMenu, - Box, - IconButton, - Link, - Octicon, - RelativeTime, -} from '@primer/react' -import Blankslate from '@primer/react/lib-esm/Blankslate/Blankslate' -import { DataTable, Table } from '@primer/react/lib-esm/DataTable' -import { Stack } from '@primer/react/lib-esm/Stack' -import { ForkBreadcrumbs } from 'app/components/breadcrumbs/ForkBreadcrumbs' -import { CreateMirrorDialog } from 'app/components/dialog/CreateMirrorDialog' -import { DeleteMirrorDialog } from 'app/components/dialog/DeleteMirrorDialog' -import { EditMirrorDialog } from 'app/components/dialog/EditMirrorDialog' -import { AppNotInstalledFlash } from 'app/components/flash/AppNotInstalledFlash' -import { ErrorFlash } from 'app/components/flash/ErrorFlash' -import { SuccessFlash } from 'app/components/flash/SuccessFlash' -import { ForkHeader } from 'app/components/header/ForkHeader' -import { Loading } from 'app/components/loading/Loading' -import { SearchWithCreate } from 'app/components/search/SearchWithCreate' -import Fuse from 'fuse.js' -import { useForkData } from 'hooks/useFork' -import { useOrgData } from 'hooks/useOrganization' -import { useCallback, useState } from 'react' - -const Fork = () => { - const { organizationId } = useParams() - const { data, isLoading } = trpc.checkInstallation.useQuery({ - orgId: organizationId as string, - }) - - const orgData = useOrgData() - const forkData = useForkData() - - const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) - const closeCreateDialog = useCallback( - () => setIsCreateDialogOpen(false), - [setIsCreateDialogOpen], - ) - const openCreateDialog = useCallback( - () => setIsCreateDialogOpen(true), - [setIsCreateDialogOpen], - ) - - const [editMirrorName, setEditMirrorName] = useState(null) - const closeEditDialog = useCallback( - () => setEditMirrorName(null), - [setEditMirrorName], - ) - const openEditDialog = useCallback( - (mirrorName: string) => setEditMirrorName(mirrorName), - [setEditMirrorName], - ) - - const [deleteMirrorName, setDeleteMirrorName] = useState(null) - const [numberOfMirrorsOnPage, setNumberOfMirrorsOnPage] = useState(0) - - const closeDeleteDialog = useCallback( - () => setDeleteMirrorName(null), - [setDeleteMirrorName], - ) - const openDeleteDialog = useCallback( - (mirrorName: string, numberOfMirrorsOnPage: number) => { - setDeleteMirrorName(mirrorName) - setNumberOfMirrorsOnPage(numberOfMirrorsOnPage) - }, - [setDeleteMirrorName, setNumberOfMirrorsOnPage], - ) - - const [isCreateErrorFlashOpen, setIsCreateErrorFlashOpen] = useState(false) - const closeCreateErrorFlash = useCallback( - () => setIsCreateErrorFlashOpen(false), - [setIsCreateErrorFlashOpen], - ) - const openCreateErrorFlash = useCallback( - () => setIsCreateErrorFlashOpen(true), - [setIsCreateErrorFlashOpen], - ) - - const [isEditErrorFlashOpen, setIsEditErrorFlashOpen] = useState(false) - const closeEditErrorFlash = useCallback( - () => setIsEditErrorFlashOpen(false), - [setIsEditErrorFlashOpen], - ) - const openEditErrorFlash = useCallback( - () => setIsEditErrorFlashOpen(true), - [setIsEditErrorFlashOpen], - ) - - const [isDeleteErrorFlashOpen, setIsDeleteErrorFlashOpen] = useState(false) - const closeDeleteErrorFlash = useCallback( - () => setIsDeleteErrorFlashOpen(false), - [setIsDeleteErrorFlashOpen], - ) - const openDeleteErrorFlash = useCallback( - () => setIsDeleteErrorFlashOpen(true), - [setIsDeleteErrorFlashOpen], - ) - - const [isCreateSuccessFlashOpen, setIsCreateSuccessFlashOpen] = - useState(false) - const closeCreateSuccessFlash = useCallback( - () => setIsCreateSuccessFlashOpen(false), - [setIsCreateSuccessFlashOpen], - ) - const openCreateSuccessFlash = useCallback( - () => setIsCreateSuccessFlashOpen(true), - [setIsCreateSuccessFlashOpen], - ) - - const [isEditSuccessFlashOpen, setIsEditSuccessFlashOpen] = useState(false) - const closeEditSuccessFlash = useCallback( - () => setIsEditSuccessFlashOpen(false), - [setIsEditSuccessFlashOpen], - ) - const openEditSuccessFlash = useCallback( - () => setIsEditSuccessFlashOpen(true), - [setIsEditSuccessFlashOpen], - ) - - const closeAllFlashes = useCallback(() => { - closeCreateErrorFlash() - closeCreateSuccessFlash() - closeEditErrorFlash() - closeEditSuccessFlash() - closeDeleteErrorFlash() - }, [ - closeCreateErrorFlash, - closeCreateSuccessFlash, - closeEditErrorFlash, - closeEditSuccessFlash, - closeDeleteErrorFlash, - ]) - - // set search value to be empty string by default - const [searchValue, setSearchValue] = useState('') - - // values for pagination - const pageSize = 10 - const [pageIndex, setPageIndex] = useState(0) - const start = pageIndex * pageSize - const end = start + pageSize - - const { - data: getConfigData, - isLoading: configLoading, - error: configError, - } = trpc.getConfig.useQuery({ - orgId: organizationId as string, - }) - - const orgLogin = getConfigData?.privateOrg ?? orgData?.data?.login - - const { - data: createMirrorData, - isLoading: createMirrorLoading, - mutateAsync: createMirror, - error: createMirrorError, - } = trpc.createMirror.useMutation() - - const { - data: mirrors, - isLoading: mirrorsLoading, - refetch: refetchMirrors, - error: listMirrorsError, - } = trpc.listMirrors.useQuery( - { - orgId: organizationId as string, - forkName: forkData?.data?.name ?? '', - }, - { - enabled: Boolean(organizationId) && Boolean(forkData?.data?.name), - }, - ) - - const { - data: editMirrorData, - isLoading: editMirrorLoading, - mutateAsync: editMirror, - error: editMirrorError, - } = trpc.editMirror.useMutation() - - const { - isLoading: deleteMirrorLoading, - mutateAsync: deleteMirror, - error: deleteMirrorError, - } = trpc.deleteMirror.useMutation() - - const handleOnCreateMirror = useCallback( - async ({ - repoName, - branchName, - }: { - repoName: string - branchName: string - }) => { - // close other flashes and dialogs when this is opened - closeAllFlashes() - closeCreateDialog() - - await createMirror({ - newRepoName: repoName, - newBranchName: branchName, - orgId: String(orgData?.data?.id), - forkRepoName: forkData?.data?.name ?? '', - forkRepoOwner: forkData?.data?.owner.login ?? '', - forkId: String(forkData?.data?.id), - }) - .then((res) => { - if (res.success) { - openCreateSuccessFlash() - } - }) - .catch((error) => { - openCreateErrorFlash() - console.error((error as Error).message) - }) - - refetchMirrors() - }, - [ - closeAllFlashes, - closeCreateDialog, - createMirror, - refetchMirrors, - openCreateSuccessFlash, - openCreateErrorFlash, - orgData, - forkData, - ], - ) - - const handleOnEditMirror = useCallback( - async ({ - mirrorName, - newMirrorName, - }: { - mirrorName: string - newMirrorName: string - }) => { - // close other flashes and dialogs when this is opened - closeAllFlashes() - closeEditDialog() - - await editMirror({ - orgId: String(orgData?.data?.id), - mirrorName, - newMirrorName, - }) - .then((res) => { - if (res.success) { - openEditSuccessFlash() - } - }) - .catch((error) => { - openEditErrorFlash() - console.error((error as Error).message) - }) - - refetchMirrors() - }, - [ - closeAllFlashes, - closeEditDialog, - editMirror, - refetchMirrors, - openEditErrorFlash, - openEditSuccessFlash, - orgData, - ], - ) - - const handleOnDeleteMirror = useCallback( - async ({ mirrorName }: { mirrorName: string }) => { - // close other flashes and dialogs when this is opened - closeAllFlashes() - closeDeleteDialog() - - await deleteMirror({ - mirrorName, - orgId: String(orgData?.data?.id), - }).catch((error) => { - openDeleteErrorFlash() - console.error((error as Error).message) - }) - - refetchMirrors() - - // if the mirror being deleted is the only mirror on the page reload the page - if (numberOfMirrorsOnPage === 1) { - window.location.reload() - } - }, - [ - closeAllFlashes, - closeDeleteDialog, - deleteMirror, - openDeleteErrorFlash, - refetchMirrors, - numberOfMirrorsOnPage, - orgData, - ], - ) - - // show loading table - if (mirrorsLoading || configLoading) { - return ( - - - - - - - - - - ) - } - - // show blankslate if no mirrors are found - if (!mirrors || mirrors.length === 0) { - return ( - - - - {!isLoading && !data?.installed && ( - - )} - - - {createMirrorLoading && } - - - {listMirrorsError && ( - - )} - - - {isCreateErrorFlashOpen && ( - - )} - - - {createMirrorData && - createMirrorData.success && - isCreateSuccessFlashOpen && ( - - )} - - - - - - - - - - - No mirrors found - - Please create a mirror for this fork. - - - - - - ) - } - - // set up search - const fuse = new Fuse(mirrors, { - keys: ['name', 'owner.name', 'owner.login'], - threshold: 0.2, - }) - - // perform search if there is a search value - let mirrorSet = [] - if (searchValue) { - mirrorSet = fuse.search(searchValue).map((result) => result.item) - } else { - mirrorSet = mirrors - } - - // slice the data based on the pagination - const mirrorPaginationSet = mirrorSet.slice(start, end) - - return ( - - - - {!isLoading && !data?.installed && ( - - )} - - - {createMirrorLoading && } - - - {editMirrorLoading && } - - - {deleteMirrorLoading && } - - - {configError && ( - - )} - - - {listMirrorsError && ( - - )} - - - {isCreateErrorFlashOpen && ( - - )} - - - {isEditErrorFlashOpen && ( - - )} - - - {isDeleteErrorFlashOpen && ( - - )} - - - {createMirrorData && - createMirrorData.success && - isCreateSuccessFlashOpen && ( - - )} - - - {editMirrorData && editMirrorData.success && isEditSuccessFlashOpen && ( - - )} - - - - - { - return ( - - {row.name} - - ) - }, - }, - { - header: 'Last updated', - field: 'updated_at', - sortBy: 'datetime', - width: 'auto', - renderCell: (row) => { - return ( - - ) - }, - }, - { - id: 'actions', - header: '', - width: '50px', - align: 'end', - renderCell: (row) => { - return ( - - - - - - - { - openEditDialog(row.name) - }} - > - - - - - Edit mirror - - - { - openDeleteDialog( - row.name, - mirrorPaginationSet.length, - ) - }} - > - - - - - Delete mirror - - - - - - ) - }, - }, - ]} - cellPadding="spacious" - /> - { - setPageIndex(pageIndex) - }} - /> - - - - - - ) -} - -export default Fork diff --git a/src/app/[organizationId]/layout.tsx b/src/app/[organizationId]/layout.tsx deleted file mode 100644 index 220d1b89..00000000 --- a/src/app/[organizationId]/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client' - -import { Box } from '@primer/react' - -const DashLayout = ({ children }: { children: React.ReactNode }) => { - return {children} -} - -export default DashLayout diff --git a/src/app/[organizationId]/page.tsx b/src/app/[organizationId]/page.tsx deleted file mode 100644 index cd3bd643..00000000 --- a/src/app/[organizationId]/page.tsx +++ /dev/null @@ -1,296 +0,0 @@ -'use client' - -import { useParams } from 'next/navigation' -import { trpc } from '../../utils/trpc' - -import { DotFillIcon, GitBranchIcon, RepoIcon } from '@primer/octicons-react' -import { - Avatar, - Box, - Label, - Link, - Octicon, - RelativeTime, - Text, -} from '@primer/react' -import Blankslate from '@primer/react/lib-esm/Blankslate/Blankslate' -import { DataTable, Table } from '@primer/react/lib-esm/DataTable' -import { Stack } from '@primer/react/lib-esm/Stack' -import { AppNotInstalledFlash } from 'app/components/flash/AppNotInstalledFlash' -import { useForksData } from 'hooks/useForks' -import { useOrgData } from 'hooks/useOrganization' -import { useState } from 'react' -import { Search } from 'app/components/search/Search' -import Fuse from 'fuse.js' -import { OrgHeader } from 'app/components/header/OrgHeader' -import { OrgBreadcrumbs } from 'app/components/breadcrumbs/OrgBreadcrumbs' -import { ErrorFlash } from 'app/components/flash/ErrorFlash' - -const Organization = () => { - const { organizationId } = useParams() - const { data, isLoading } = trpc.checkInstallation.useQuery({ - orgId: organizationId as string, - }) - - const orgData = useOrgData() - const forksData = useForksData(orgData?.data?.login) - - // set search value to be empty string by default - const [searchValue, setSearchValue] = useState('') - - // values for pagination - const pageSize = 10 - const [pageIndex, setPageIndex] = useState(0) - const start = pageIndex * pageSize - const end = start + pageSize - - // show loading table - if (forksData.isLoading) { - return ( - - - - - - - - - - ) - } - - // show blankslate if no forks are found - if ( - !forksData.data || - forksData.data.organization.repositories.totalCount === 0 - ) { - return ( - - - - {forksData.error && ( - - )} - - - - - - - - - - - No forks found - - Please fork a repo into your organization to get started. - - - - - ) - } - - const forks = forksData.data?.organization.repositories.nodes - - // set up search - const fuse = new Fuse(forks, { - keys: ['name', 'owner.login', 'parent.name', 'parent.owner.login'], - threshold: 0.2, - }) - - // perform search if there is a search value - let forksSet = [] - if (searchValue) { - forksSet = fuse.search(searchValue).map((result) => result.item) - } else { - forksSet = forks - } - - // slice the data based on the pagination - const forksPaginationSet = forksSet.slice(start, end) - - return ( - - - - {!isLoading && !data?.installed && ( - - )} - - - - - { - return ( - - - - - - - - {row.name} - - - - - - Forked from{' '} - - {row.parent.owner.login}/{row.parent.name} - - - - - - ) - }, - }, - { - header: 'Branches', - field: 'refs.totalCount', - width: 'auto', - renderCell: (row) => { - return ( - - - - - - {row.refs.totalCount} - - - - - ) - }, - }, - { - header: 'Languages', - field: 'languages', - width: 'auto', - renderCell: (row) => { - const languages = row.languages.nodes - - return ( - - {languages.map((lang) => ( - - - - {lang.name} - - - ))} - - ) - }, - }, - { - header: 'Updated', - field: 'updatedAt', - sortBy: 'datetime', - width: 'auto', - renderCell: (row) => { - return ( - - ) - }, - }, - ]} - cellPadding="spacious" - /> - { - setPageIndex(pageIndex) - }} - /> - - - ) -} - -export default Organization diff --git a/src/app/api/auth/[...nextauth]/route.tsx b/src/app/api/auth/[...nextauth]/route.tsx deleted file mode 100644 index 5e7485e5..00000000 --- a/src/app/api/auth/[...nextauth]/route.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import NextAuth from 'next-auth' -import { NextRequest, NextResponse } from 'next/server' -import { nextAuthOptions } from '../lib/nextauth-options' - -const handler = NextAuth(nextAuthOptions) as ( - req: NextRequest, - res: NextResponse, -) => Promise - -export { handler as GET, handler as POST } diff --git a/src/app/api/auth/lib/nextauth-options.ts b/src/app/api/auth/lib/nextauth-options.ts deleted file mode 100644 index a9da74d4..00000000 --- a/src/app/api/auth/lib/nextauth-options.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { personalOctokit } from 'bot/octokit' -import { AuthOptions, Profile } from 'next-auth' -import { JWT } from 'next-auth/jwt' -import GitHub from 'next-auth/providers/github' -import { logger } from '../../../../utils/logger' - -import 'utils/proxy' - -const authLogger = logger.getSubLogger({ name: 'auth' }) - -/** - * Converts seconds until expiration to date in milliseconds - * @param seconds Seconds until expiration to convert - * @returns number — Expiration date in milliseconds - */ -const normalizeExpirationDate = (seconds: number) => { - return Date.now() + seconds * 1000 -} - -/** - * Checks the session against the github API to see if the session is valid - * @param token Token of the session - * @returns boolean — Whether the session is valid - */ -export const verifySession = async (token: string | undefined) => { - if (!token) return false - - const octokit = personalOctokit(token) - try { - await octokit.rest.users.getAuthenticated() - return true - } catch { - return false - } -} - -/** - * Refresh access token - * @param clientId Client ID - * @param clientSecret Client Secret - * @param refreshToken Refresh token - * @returns object — New access token and refresh token - */ -export const refreshAccessToken = async ( - token: JWT, - clientId: string, - clientSecret: string, - refreshToken: string, -) => { - try { - authLogger.debug('Refreshing access token', { clientId }) - - const params = new URLSearchParams({ - client_id: clientId, - client_secret: clientSecret, - refresh_token: refreshToken, - grant_type: 'refresh_token', - }) - - const url = - 'https://github.com/login/oauth/access_token?' + params.toString() - - const response = await fetch(url, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - method: 'POST', - }).then(async (res) => { - const responseText = await res.text() - const entries = new URLSearchParams(responseText) - const params = Object.fromEntries(entries) - - if (params.error) { - throw new Error(params.error_description) - } - - return params - }) - - return { - accessToken: response.access_token, - // Access token expiration is provided as number in seconds until expiration (value is always 8 hours) - accessTokenExpires: normalizeExpirationDate(Number(response.expires_in)), - refreshToken: response.refresh_token, - // Refresh token expiration is provided as number of seconds until expiration (value is always 6 months) - refreshTokenExpires: normalizeExpirationDate( - Number(response.refresh_token_expires_in), - ), - } - } catch (error) { - authLogger.error('Error refreshing access token', { error }) - // Return the original token with an error if we failed to refresh the token so the user gets signed out - return { - ...token, - error: 'RefreshAccessTokenError' as const, - } - } -} - -export const nextAuthOptions: AuthOptions = { - pages: { - signIn: '/auth/login', - error: '/auth/error', - }, - debug: process.env.NODE_ENV === 'development', - providers: [ - GitHub({ - clientId: process.env.GITHUB_CLIENT_ID!, - clientSecret: process.env.GITHUB_CLIENT_SECRET!, - authorization: { - params: { scope: 'repo, user, read:org' }, - }, - }), - ], - secret: process.env.NEXTAUTH_SECRET!, - logger: { - error(code, metadata) { - if (!(metadata instanceof Error) && metadata.provider) { - // redact the provider secret here - delete metadata.provider - authLogger.error('Auth error', { code, metadata }) - } else { - authLogger.error('Auth error', { code, metadata }) - } - }, - warn(code) { - authLogger.warn('Auth warn', { code }) - }, - debug(code, metadata) { - authLogger.debug('Auth debug', { code, metadata }) - }, - }, - callbacks: { - signIn: async (params) => { - authLogger.debug('Sign in callback') - - const profile = params.profile as Profile & { login?: string } - - // If there is no login, prevent sign in - if (!profile?.login) { - return false - } - - // Get the allowed handles list - const allowedHandles = ( - process.env.ALLOWED_HANDLES?.split(',') ?? [] - ).filter((handle) => handle !== '') - - // Get the allowed orgs list - const allowedOrgs = (process.env.ALLOWED_ORGS?.split(',') ?? []).filter( - (org) => org !== '', - ) - - // If there are no allowed handles and no allowed orgs specified, allow all users - if (allowedHandles.length === 0 && allowedOrgs.length === 0) { - authLogger.info( - 'No allowed handles or orgs specified, allowing all users.', - ) - - authLogger.info('User is allowed to sign in:', profile.login) - - return true - } - - authLogger.debug('Trying to sign in with handle:', profile.login) - - // If the user is in the allowed handles list, allow sign in - if (allowedHandles.includes(profile.login)) { - authLogger.info('User is allowed to sign in:', profile.login) - - return true - } - - authLogger.debug( - 'User is not in the allowed handles list:', - profile.login, - ) - - authLogger.debug( - "Checking if any of user's orgs are in allowed orgs list", - ) - - const octokit = personalOctokit(params.account?.access_token as string) - - // Get the user's organizations - const orgs = await octokit - .paginate(octokit.rest.orgs.listForAuthenticatedUser) - .catch((error: Error) => { - authLogger.error('Failed to fetch organizations', { error }) - return [] - }) - - // Check if any of the user's organizations are in the allowed orgs list - if (orgs.some((org) => allowedOrgs.includes(org.login))) { - authLogger.info( - 'User has an org in the allowed orgs list:', - profile.login, - ) - - authLogger.info('User is allowed to sign in:', profile.login) - - return true - } - - authLogger.warn('User is not allowed to sign in:', profile.login) - - return false - }, - session: ({ session, token }) => { - if (token) { - session.user.name = token.name as string - session.user.image = token.image as string - session.user.email = token.email as string - session.user.accessToken = token.accessToken - session.expires = new Date(token.accessTokenExpires).toISOString() - session.error = token.error - } - - return session - }, - jwt: async ({ token, user, account }) => { - // Initial sign in - if (user && account) { - authLogger.debug('Initial sign in') - token = { - accessToken: account.access_token as string, - // Access token expiration is provided as number in seconds since epoch (value is always 8 hours) - // Convert to milliseconds - accessTokenExpires: (account.expires_at as number) * 1000, - refreshToken: account.refresh_token as string, - // Refresh token expiration is provided as number of seconds until expiration (value is always 6 months) - refreshTokenExpires: normalizeExpirationDate( - account.refresh_token_expires_in as number, - ), - ...user, - } - - return token - } - - // Return previous token if the access token has not expired yet - if (Date.now() < token.accessTokenExpires) { - authLogger.debug('Access token valid') - return token - } - - authLogger.debug('Access token has expired') - - // Return previous token if the refresh token has expired - if (Date.now() >= token.refreshTokenExpires) { - authLogger.warn('Refresh token has expired') - return token - } - - // Refresh the access token - const refreshedToken = await refreshAccessToken( - token, - process.env.GITHUB_CLIENT_ID!, - process.env.GITHUB_CLIENT_SECRET!, - token.refreshToken, - ) - - // Return the previous token if we failed to refresh the token - if (!refreshedToken) { - return token - } - - // Return the new token - token = { - ...refreshedToken, - ...user, - } - - return token - }, - }, -} diff --git a/src/app/api/trpc/[trpc]/route.tsx b/src/app/api/trpc/[trpc]/route.tsx deleted file mode 100644 index bfc81312..00000000 --- a/src/app/api/trpc/[trpc]/route.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - FetchCreateContextFnOptions, - fetchRequestHandler, -} from '@trpc/server/adapters/fetch' -import { getServerSession } from 'next-auth' -import { nextAuthOptions } from '../../auth/lib/nextauth-options' -import { appRouter } from '../trpc-router' -import { logger } from '../../../../utils/logger' - -const trpcLogger = logger.getSubLogger({ name: 'trpc' }) - -const createContext = async ({ - req, - resHeaders, -}: FetchCreateContextFnOptions) => { - const session = await getServerSession(nextAuthOptions) - - return { req, resHeaders, session } -} - -const handler = (request: Request) => { - trpcLogger.info(`incoming request ${request.url}`) - return fetchRequestHandler({ - endpoint: '/api/trpc', - req: request, - router: appRouter, - createContext, - }) -} - -export { handler as GET, handler as POST } diff --git a/src/app/api/trpc/trpc-router.ts b/src/app/api/trpc/trpc-router.ts deleted file mode 100644 index f5c0a5a3..00000000 --- a/src/app/api/trpc/trpc-router.ts +++ /dev/null @@ -1,21 +0,0 @@ -import configRouter from 'server/config/router' -import gitRouter from '../../../server/git/router' -import octokitRouter from '../../../server/octokit/router' -import reposRouter from '../../../server/repos/router' -import { procedure, t } from '../../../utils/trpc-server' - -export const healthCheckerRouter = t.router({ - healthChecker: procedure.query(() => { - return 'ok' - }), -}) - -export const appRouter = t.mergeRouters( - reposRouter, - octokitRouter, - gitRouter, - configRouter, - healthCheckerRouter, -) - -export type AppRouter = typeof appRouter diff --git a/src/app/auth/error/page.tsx b/src/app/auth/error/page.tsx deleted file mode 100644 index cd618299..00000000 --- a/src/app/auth/error/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client' - -import { MarkGithubIcon } from '@primer/octicons-react' -import { Box, Button, Octicon, Text } from '@primer/react' -import { useRouter } from 'next/navigation' - -const ErrorPage = () => { - const router = useRouter() - - return ( - - - - - - - - Access denied - - - - Reach out to your organization admin to get access - - - - - - - - - ) -} - -export default ErrorPage diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx deleted file mode 100644 index 65ba46de..00000000 --- a/src/app/auth/login/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client' - -import { Box } from '@primer/react' -import { Login } from 'app/components/login/Login' -import { useOrgsData } from 'hooks/useOrganizations' -import { useSession } from 'next-auth/react' -import { useRouter } from 'next/navigation' -import { useEffect } from 'react' - -const LoginPage = () => { - const session = useSession() - const orgsData = useOrgsData() - - const router = useRouter() - - useEffect(() => { - if (session.data?.user) { - // if orgs data is still loading, do nothing - if (orgsData.isLoading) { - return - } - - // if user only has one org, go to that org's page - if (orgsData.data?.length === 1) { - router.push(`/${orgsData.data[0].login}`) - return - } - - // otherwise go to home page - router.push('/') - } - }, [session.data?.user, orgsData.isLoading, orgsData.data, router]) - - return {!session.data?.user && } -} - -export default LoginPage diff --git a/src/app/components/breadcrumbs/ForkBreadcrumbs.tsx b/src/app/components/breadcrumbs/ForkBreadcrumbs.tsx deleted file mode 100644 index 2dceddc8..00000000 --- a/src/app/components/breadcrumbs/ForkBreadcrumbs.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Box, Breadcrumbs } from '@primer/react' -import { ForkData } from 'hooks/useFork' -import { OrgData } from 'hooks/useOrganization' - -interface ForkBreadcrumbsProps { - orgData: OrgData - forkData: ForkData -} - -export const ForkBreadcrumbs = ({ - orgData, - forkData, -}: ForkBreadcrumbsProps) => { - if (!orgData || !forkData) { - return null - } - - return ( - - - - All organizations - - - {orgData?.login} - - - {forkData?.name} - - - - ) -} diff --git a/src/app/components/breadcrumbs/OrgBreadcrumbs.tsx b/src/app/components/breadcrumbs/OrgBreadcrumbs.tsx deleted file mode 100644 index 40d538cd..00000000 --- a/src/app/components/breadcrumbs/OrgBreadcrumbs.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Box, Breadcrumbs } from '@primer/react' -import { OrgData } from 'hooks/useOrganization' - -interface ForkBreadcrumbsProps { - orgData: OrgData -} - -export const OrgBreadcrumbs = ({ orgData }: ForkBreadcrumbsProps) => { - if (!orgData) { - return null - } - - return ( - - - - All organizations - - - {orgData?.login} - - - - ) -} diff --git a/src/app/components/dialog/CreateMirrorDialog.tsx b/src/app/components/dialog/CreateMirrorDialog.tsx deleted file mode 100644 index 9238ba8e..00000000 --- a/src/app/components/dialog/CreateMirrorDialog.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Box, FormControl, Label, Link, Text, TextInput } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { Dialog } from '@primer/react/lib-esm/drafts' - -import { useState } from 'react' - -interface CreateMirrorDialogProps { - orgLogin: string - forkParentOwnerLogin: string - forkParentName: string - isOpen: boolean - closeDialog: () => void - createMirror: (data: { repoName: string; branchName: string }) => void -} - -export const CreateMirrorDialog = ({ - orgLogin, - forkParentOwnerLogin, - forkParentName, - isOpen, - closeDialog, - createMirror, -}: CreateMirrorDialogProps) => { - // set to default value of 'repository-name' for display purposes - const [repoName, setRepoName] = useState('repository-name') - - if (!isOpen) { - return null - } - - return ( - { - closeDialog() - setRepoName('repository-name') - }, - }, - { - content: 'Confirm', - variant: 'primary', - onClick: () => { - createMirror({ repoName, branchName: repoName }) - setRepoName('repository-name') - }, - disabled: repoName === 'repository-name' || repoName === '', - }, - ]} - onClose={() => { - closeDialog() - setRepoName('repository-name') - }} - width="large" - > - - - Mirror name - setRepoName(e.target.value)} - block - placeholder="e.g. repository-name" - maxLength={100} - /> - - This is a private mirror of{' '} - - {forkParentOwnerLogin}/{forkParentName} - - - - - Mirror location - - - - - - {orgLogin}/{repoName} - - - - - - Forked from{' '} - - {forkParentOwnerLogin}/{forkParentName} - - - - - - - - - - ) -} diff --git a/src/app/components/dialog/DeleteMirrorDialog.tsx b/src/app/components/dialog/DeleteMirrorDialog.tsx deleted file mode 100644 index 480126e3..00000000 --- a/src/app/components/dialog/DeleteMirrorDialog.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Box, Text } from '@primer/react' -import { Dialog } from '@primer/react/lib-esm/drafts' - -interface DeleteMirrorDialogProps { - orgLogin: string - orgId: string - mirrorName: string - isOpen: boolean - closeDialog: () => void - deleteMirror: (data: { - orgId: string - orgLogin: string - mirrorName: string - }) => void -} - -export const DeleteMirrorDialog = ({ - orgLogin, - orgId, - mirrorName, - isOpen, - closeDialog, - deleteMirror, -}: DeleteMirrorDialogProps) => { - if (!isOpen) { - return null - } - - return ( - deleteMirror({ orgId, orgLogin, mirrorName }), - }, - ]} - onClose={closeDialog} - > - - Are you sure you'd like to delete - - {' '} - {orgLogin}/{mirrorName}? - - - - ) -} diff --git a/src/app/components/dialog/EditMirrorDialog.tsx b/src/app/components/dialog/EditMirrorDialog.tsx deleted file mode 100644 index c6349543..00000000 --- a/src/app/components/dialog/EditMirrorDialog.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Box, FormControl, Label, Link, Text, TextInput } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { Dialog } from '@primer/react/lib-esm/drafts' - -import { useEffect, useState } from 'react' - -interface EditMirrorDialogProps { - orgLogin: string - forkParentOwnerLogin: string - forkParentName: string - orgId: string - mirrorName: string - isOpen: boolean - closeDialog: () => void - editMirror: (data: { - orgId: string - mirrorName: string - newMirrorName: string - }) => void -} - -export const EditMirrorDialog = ({ - orgLogin, - forkParentOwnerLogin, - forkParentName, - orgId, - mirrorName, - isOpen, - closeDialog, - editMirror, -}: EditMirrorDialogProps) => { - // set to the current mirror name for display purposes - const [newMirrorName, setNewMirrorName] = useState(mirrorName) - - useEffect(() => { - setNewMirrorName(mirrorName) - }, [mirrorName, setNewMirrorName]) - - if (!isOpen) { - return null - } - - return ( - { - closeDialog() - setNewMirrorName(mirrorName) - }, - }, - { - content: 'Confirm', - variant: 'primary', - onClick: () => { - editMirror({ - orgId, - mirrorName, - newMirrorName, - }) - setNewMirrorName(mirrorName) - }, - disabled: newMirrorName === mirrorName || newMirrorName === '', - }, - ]} - onClose={() => { - closeDialog() - setNewMirrorName(mirrorName) - }} - width="large" - > - - - Mirror name - setNewMirrorName(e.target.value)} - block - placeholder={mirrorName} - maxLength={100} - /> - - This is a private mirror of{' '} - - {forkParentOwnerLogin}/{forkParentName} - - - - - Mirror location - - - - - - {orgLogin}/{newMirrorName} - - - - - - Forked from{' '} - - {forkParentOwnerLogin}/{forkParentName} - - - - - - - - - - ) -} diff --git a/src/app/components/flash/AppNotInstalledFlash.tsx b/src/app/components/flash/AppNotInstalledFlash.tsx deleted file mode 100644 index 8d44de03..00000000 --- a/src/app/components/flash/AppNotInstalledFlash.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { AlertIcon } from '@primer/octicons-react' -import { Box, Flash, Link, Octicon } from '@primer/react' - -interface AppNotInstalledFlashProps { - orgLogin: string -} - -export const AppNotInstalledFlash = ({ - orgLogin, -}: AppNotInstalledFlashProps) => { - return ( - - - - - - - - This organization does not have the required App installed. Visit{' '} - - this page - {' '} - to install the App to the organization. - - - - - ) -} diff --git a/src/app/components/flash/ErrorFlash.tsx b/src/app/components/flash/ErrorFlash.tsx deleted file mode 100644 index 16d2625e..00000000 --- a/src/app/components/flash/ErrorFlash.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { AlertIcon, XIcon } from '@primer/octicons-react' -import { Box, Flash, IconButton, Octicon } from '@primer/react' - -interface ErrorFlashProps { - message: string - closeFlash?: () => void -} - -export const ErrorFlash = ({ message, closeFlash }: ErrorFlashProps) => { - return ( - - - - - - {message} - {closeFlash && ( - - - - )} - - - ) -} diff --git a/src/app/components/flash/SuccessFlash.tsx b/src/app/components/flash/SuccessFlash.tsx deleted file mode 100644 index f27baec5..00000000 --- a/src/app/components/flash/SuccessFlash.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { CheckIcon, XIcon } from '@primer/octicons-react' -import { Box, Flash, IconButton, Link, Octicon } from '@primer/react' - -interface SuccessFlashProps { - message: string - mirrorUrl: string - orgLogin: string - mirrorName: string - closeFlash: () => void -} - -export const SuccessFlash = ({ - message, - mirrorUrl, - orgLogin, - mirrorName, - closeFlash, -}: SuccessFlashProps) => { - return ( - - - - - - - {message}{' '} - - {orgLogin}/{mirrorName} - - . - - - - - - - ) -} diff --git a/src/app/components/header/ForkHeader.tsx b/src/app/components/header/ForkHeader.tsx deleted file mode 100644 index 1eaea680..00000000 --- a/src/app/components/header/ForkHeader.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Avatar, Label, Link, Pagehead, Spinner, Text } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { ForkData } from 'hooks/useFork' - -interface ForkHeaderProps { - forkData: ForkData -} - -export const ForkHeader = ({ forkData }: ForkHeaderProps) => { - return ( - - {forkData ? ( - - - - - - - - {forkData.organization?.login}/{forkData.name} - - - - - - Forked from{' '} - - {forkData.parent?.owner.login}/{forkData.parent?.name} - - - - - - ) : ( - - - - - - - Loading fork data... - - - - )} - - ) -} diff --git a/src/app/components/header/MainHeader.tsx b/src/app/components/header/MainHeader.tsx deleted file mode 100644 index 027622a5..00000000 --- a/src/app/components/header/MainHeader.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -'use client' - -import { MarkGithubIcon } from '@primer/octicons-react' -import { Avatar, Button, Header, Octicon, Text } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { signOut, useSession } from 'next-auth/react' - -export const MainHeader = () => { - const session = useSession() - - return ( -
- - - - - - Private Mirrors - - - {session && session.data?.user && ( - - - - - - - {session.data?.user.image && ( - - )} - - - - )} -
- ) -} diff --git a/src/app/components/header/OrgHeader.tsx b/src/app/components/header/OrgHeader.tsx deleted file mode 100644 index a7d6f853..00000000 --- a/src/app/components/header/OrgHeader.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Avatar, Link, Pagehead, Spinner, Text } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { OrgData } from 'hooks/useOrganization' - -interface OrgHeaderProps { - orgData: OrgData -} - -export const OrgHeader = ({ orgData }: OrgHeaderProps) => { - return ( - - {orgData ? ( - - - - - - - {orgData.login} - - - - ) : ( - - - - - - - Loading organization data... - - - - )} - - ) -} diff --git a/src/app/components/header/WelcomeHeader.tsx b/src/app/components/header/WelcomeHeader.tsx deleted file mode 100644 index 45eeedaa..00000000 --- a/src/app/components/header/WelcomeHeader.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { RepoForkedIcon } from '@primer/octicons-react' -import { Octicon, Pagehead, Text } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' - -export const WelcomeHeader = () => { - return ( - - - - - - - - Welcome to Private Mirrors App! - - - - - ) -} diff --git a/src/app/components/loading/Loading.tsx b/src/app/components/loading/Loading.tsx deleted file mode 100644 index ffb9ca34..00000000 --- a/src/app/components/loading/Loading.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Box, Spinner } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' - -interface LoadingProps { - message: string -} - -export const Loading = ({ message }: LoadingProps) => { - return ( - - - - - - {message} - - - ) -} diff --git a/src/app/components/login/Login.tsx b/src/app/components/login/Login.tsx deleted file mode 100644 index f514131e..00000000 --- a/src/app/components/login/Login.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { MarkGithubIcon } from '@primer/octicons-react' -import { Box, Button, Octicon, Text } from '@primer/react' -import { signIn } from 'next-auth/react' - -const signInWithGitHub = async () => { - await signIn('github') -} - -export const Login = () => { - return ( - - - - - - - - Sign in to get started. - - - - Private Mirrors - - - - - - - - - ) -} diff --git a/src/app/components/search/Search.tsx b/src/app/components/search/Search.tsx deleted file mode 100644 index 1074babd..00000000 --- a/src/app/components/search/Search.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { SearchIcon, XCircleFillIcon } from '@primer/octicons-react' -import { Box, FormControl, TextInput } from '@primer/react' -import { ChangeEvent } from 'react' - -interface SearchProps { - placeholder: string - searchValue: string - setSearchValue: (value: string) => void -} - -export const Search = ({ - placeholder, - searchValue, - setSearchValue, -}: SearchProps) => { - const handleChange = (event: ChangeEvent) => { - setSearchValue(event.target.value) - } - - return ( - - - Search - { - setSearchValue('') - }} - icon={XCircleFillIcon} - aria-label="Clear input" - sx={{ - color: 'fg.subtle', - }} - /> - } - /> - - - ) -} diff --git a/src/app/components/search/SearchWithCreate.tsx b/src/app/components/search/SearchWithCreate.tsx deleted file mode 100644 index 69388683..00000000 --- a/src/app/components/search/SearchWithCreate.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { PlusIcon, SearchIcon, XCircleFillIcon } from '@primer/octicons-react' -import { Box, Button, FormControl, TextInput } from '@primer/react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { ChangeEvent } from 'react' - -interface SearchWithCreateProps { - placeholder: string - createButtonLabel: string - searchValue: string - setSearchValue: (value: string) => void - openCreateDialog: () => void -} - -export const SearchWithCreate = ({ - placeholder, - createButtonLabel, - searchValue, - setSearchValue, - openCreateDialog, -}: SearchWithCreateProps) => { - const handleChange = (event: ChangeEvent) => { - setSearchValue(event.target.value) - } - - return ( - - - - - Search - { - setSearchValue('') - }} - icon={XCircleFillIcon} - aria-label="Clear input" - sx={{ - color: 'fg.subtle', - }} - /> - } - /> - - - - - - - - ) -} diff --git a/src/app/context/AuthProvider.tsx b/src/app/context/AuthProvider.tsx deleted file mode 100644 index a4d1957e..00000000 --- a/src/app/context/AuthProvider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -'use client' - -import { Session } from 'next-auth' -import { SessionProvider, signOut, useSession } from 'next-auth/react' - -import { ReactNode, useEffect } from 'react' -import { logger } from 'utils/logger' - -const authProviderLogger = logger.getSubLogger({ name: 'auth-provider' }) - -const VerifiedAuthProvider = ({ children }: { children: ReactNode }) => { - const session = useSession() - - // sign user out if session is expired - useEffect(() => { - if (!session || session.status === 'loading') { - return - } - - if (session.data?.error === 'RefreshAccessTokenError') { - authProviderLogger.error('Could not refresh access token - signing out') - signOut() - } - - if (session.data && new Date(session.data.expires) < new Date()) { - authProviderLogger.info('session expired - signing out') - signOut() - } - }, [ - session, - session.status, - session.data, - session.data?.error, - session.data?.expires, - ]) - - return children -} - -export const AuthProvider = ({ - children, - session, -}: { - children: ReactNode - session: Session | null -}) => { - return ( - - {children} - - ) -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index 1aa0b289..00000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { BaseStyles, Box, ThemeProvider } from '@primer/react' -import { StyledComponentsRegistry } from '../providers/registry-provider' -import { TrpcProvider } from '../providers/trpc-provider' -import { MainHeader } from './components/header/MainHeader' -import { AuthProvider } from './context/AuthProvider' -import { getServerSession } from 'next-auth' -import { nextAuthOptions } from './api/auth/lib/nextauth-options' - -const RootLayout = async ({ children }: { children: React.ReactNode }) => { - const session = await getServerSession(nextAuthOptions) - - return ( - - - - - - - - - - - - - {children} - - - - - - - - - - ) -} - -export default RootLayout diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx deleted file mode 100644 index b40ba235..00000000 --- a/src/app/not-found.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client' - -import { AlertIcon } from '@primer/octicons-react' -import { Box, Octicon } from '@primer/react' -import Blankslate from '@primer/react/lib-esm/Blankslate/Blankslate' - -const NotFoundPage = () => { - return ( - - - - - - - - Page not found - - This is not the page you're looking for. - - - - Back to home - - - - - ) -} - -export default NotFoundPage diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index fcd548d1..00000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,173 +0,0 @@ -'use client' - -import { Avatar, Box, Link, Octicon } from '@primer/react' -import { useState } from 'react' -import { OrgsData, useOrgsData } from 'hooks/useOrganizations' -import { Search } from './components/search/Search' -import { DataTable, Table } from '@primer/react/lib-esm/DataTable' -import Fuse from 'fuse.js' -import Blankslate from '@primer/react/lib-esm/Blankslate/Blankslate' -import { OrganizationIcon } from '@primer/octicons-react' -import { Stack } from '@primer/react/lib-esm/Stack' -import { WelcomeHeader } from './components/header/WelcomeHeader' -import { ErrorFlash } from './components/flash/ErrorFlash' - -const Home = () => { - const orgsData = useOrgsData() - - // set search value to be empty string by default - const [searchValue, setSearchValue] = useState('') - - // values for pagination - const pageSize = 10 - const [pageIndex, setPageIndex] = useState(0) - const start = pageIndex * pageSize - const end = start + pageSize - - // show loading table - if (orgsData.isLoading) { - return ( - - - - - - - - - ) - } - - // show blankslate if no organizations are found - if (!orgsData.data || orgsData.data.length === 0) { - return ( - - - - {orgsData.error && ( - - )} - - - - - - - - - - No organizations found - - Please install the app in an organization to see it here. - - - - - ) - } - - // set up search - const fuse = new Fuse(orgsData.data, { - keys: ['login'], - threshold: 0.2, - }) - - // perform search if there is a search value - let orgsSet: OrgsData = [] - if (searchValue) { - orgsSet = fuse.search(searchValue).map((result) => result.item) - } else { - orgsSet = orgsData.data - } - - // slice the data based on the pagination - const orgsPaginationSet = orgsSet.slice(start, end) - - return ( - - - - - { - return ( - - - - - - - {row.login} - - - - ) - }, - }, - ]} - cellPadding="spacious" - /> - { - setPageIndex(pageIndex) - }} - /> - - - ) -} - -export default Home diff --git a/src/aws/lambda.ts b/src/aws/lambda.ts new file mode 100644 index 00000000..1749c73f --- /dev/null +++ b/src/aws/lambda.ts @@ -0,0 +1,40 @@ +import { SecretsManager } from '@aws-sdk/client-secrets-manager'; +import { APIGatewayEvent, Context } from 'aws-lambda'; +import lowercaseKeys from 'lowercase-keys'; +import { Probot } from 'probot'; +import app from '../app'; + +const secretsManager = new SecretsManager(); +const credentialsValue = await secretsManager.getSecretValue({ SecretId: process.env.CREDENTIALS_ARN }); +if (!credentialsValue.SecretString) throw new Error('No SecretString returned for credentials'); +const credentials = JSON.parse(credentialsValue.SecretString) as Record; + +export async function handler(event: APIGatewayEvent, context: Context) { + console.log({ event, context }); + + const probot = new Probot({ + appId: credentials.appId, + privateKey: credentials.privateKey, + secret: credentials.webhookSecret + }); + + await probot.load(app, { context }); + + // lowercase all headers to respect headers insensitivity (RFC 7230 $3.2 'Header Fields', see issue #62) + const headersLowerCase = lowercaseKeys(event.headers); + + // this will be simpler once we ship `verifyAndParse()` + // see https://github.com/octokit/webhooks.js/issues/379 + await probot.webhooks.verifyAndReceive({ + id: headersLowerCase['x-github-delivery'], + name: headersLowerCase['x-github-event'], + signature: headersLowerCase['x-hub-signature-256'] ?? headersLowerCase['x-hub-signature'], + payload: event.body + } as Parameters[0]); + + // In case we are being called synchronously + return { + statusCode: 200, + body: JSON.stringify({ ok: true }) + }; +} diff --git a/src/bot/config.ts b/src/bot/config.ts deleted file mode 100644 index 5389824a..00000000 --- a/src/bot/config.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Configuration } from '@probot/octokit-plugin-config/dist-types/types' -import z from 'zod' -import { logger } from '../utils/logger' -import { appOctokit, installationOctokit } from './octokit' - -const configLogger = logger.getSubLogger({ name: 'config' }) - -const pmaConfig = z.object({ - publicOrg: z.string(), - privateOrg: z.string(), -}) - -type pmaConfig = z.infer & Configuration - -export const getGitHubConfig = async (orgId: string) => { - const installationId = await appOctokit().rest.apps.getOrgInstallation({ - org: orgId, - }) - const octokit = installationOctokit(String(installationId.data.id)) - - const orgData = await octokit.rest.orgs.get({ org: orgId }) - - configLogger.info( - `No config found for org, using default org: '${orgData.data.login}' for BOTH public and private!`, - ) - return { - publicOrg: orgData.data.login, - privateOrg: orgData.data.login, - } -} - -export const getEnvConfig = () => { - if (!process.env.PUBLIC_ORG) { - return null - } - - configLogger.info( - `PUBLIC_ORG is set. Using config from environment variables!`, - ) - - const config = { - publicOrg: process.env.PUBLIC_ORG, - privateOrg: process.env.PUBLIC_ORG, - } as pmaConfig - - if (process.env.PRIVATE_ORG) { - config.privateOrg = process.env.PRIVATE_ORG - } - return config -} - -export const validateConfig = (config: pmaConfig) => { - try { - pmaConfig.parse(config) - } catch (error) { - configLogger.error('Invalid config found!', { error }) - throw new Error( - 'Invalid config found! Please check the config and error log for more details.', - ) - } - - return config -} - -/** - * Fetches a configuration file from the organization's .github repository - * @param orgId Organization ID - * @returns Configuration file - */ -export const getConfig = async (orgId?: string) => { - let config: pmaConfig | null = null - - // First check for environment variables - config = getEnvConfig() - if (config) { - return validateConfig(config) - } - - // Lastly check github for a config - if (!orgId) { - configLogger.error( - 'No orgId present, Organization ID is required to set a config when not using environment variables', - ) - throw new Error('Organization ID is required to set a config!') - } - - config = await getGitHubConfig(orgId) - - configLogger.info(`Using following config values`, { - config, - }) - - return validateConfig(config) -} diff --git a/src/bot/graphql.ts b/src/bot/graphql.ts deleted file mode 100644 index ab575505..00000000 --- a/src/bot/graphql.ts +++ /dev/null @@ -1,198 +0,0 @@ -export const getBranchProtectionRulesetGQL = ` -query( - $owner: String! - $name: String! -) { - repository(owner: $owner, name: $name) { - rulesets(first: 50) { - nodes { - name - } - } - } -} -` - -export const forkBranchProtectionRulesetGQL = ` -mutation CreateRepositoryRuleset( - $repositoryId: ID! - $ruleName: String! - $bypassActorId: ID! - $includeRefs: [String!]! -) { - createRepositoryRuleset( - input: { - sourceId: $repositoryId - name: $ruleName - target: BRANCH - conditions: { - refName: { - include: $includeRefs - exclude: [] - } - } - rules: [ - { - type: CREATION - }, - { - type: UPDATE - parameters:{ - update:{ - updateAllowsFetchAndMerge: true - } - } - }, - { - type: DELETION - } - ] - enforcement: ACTIVE - bypassActors: { - actorId: $bypassActorId - bypassMode: ALWAYS - } - } - ) { - ruleset { - id - } - } -} -` - -export const mirrorBranchProtectionRulesetGQL = ` -mutation CreateRepositoryRuleset( - $repositoryId: ID! - $ruleName: String! - $bypassActorId: ID! - $includeRefs: [String!]! -) { - createRepositoryRuleset( - input: { - sourceId: $repositoryId - name: $ruleName - target: BRANCH - conditions: { - refName: { - include: $includeRefs - exclude: [] - } - } - rules: [ - { - type: PULL_REQUEST - parameters: { - pullRequest: { - dismissStaleReviewsOnPush: true - requireCodeOwnerReview: false - requireLastPushApproval: false - requiredApprovingReviewCount: 1 - requiredReviewThreadResolution: true - } - } - } - ] - enforcement: ACTIVE - bypassActors: { - actorId: $bypassActorId - bypassMode: ALWAYS - } - } - ) { - ruleset { - id - } - } -} -` - -export const forkBranchProtectionGQL = ` -mutation AddBranchProtection( - $repositoryId: ID! - $actorId: ID! - $pattern: String! -) { - createBranchProtectionRule( - input: { - repositoryId: $repositoryId - isAdminEnforced: true - pushActorIds: [$actorId] - pattern: $pattern - restrictsPushes: true - blocksCreations: true - } - ) { - branchProtectionRule { - id - } - } -} -` - -export const mirrorBranchProtectionGQL = ` -mutation AddBranchProtection( - $repositoryId: ID! - $actorId: ID! - $pattern: String! -) { - createBranchProtectionRule( - input: { - repositoryId: $repositoryId - requiresApprovingReviews:true - requiredApprovingReviewCount: 1 - pattern: $pattern - dismissesStaleReviews:true - pushActorIds: [$actorId] - } - ) { - branchProtectionRule { - id - } - } -} -` - -export const getReposInOrgGQL = ` -query( - $login: String! - $isFork: Boolean - $cursor: String -) { - organization(login: $login) { - repositories(isFork: $isFork, after: $cursor, first: 25, orderBy: {field: UPDATED_AT, direction: DESC}) { - totalCount - nodes { - databaseId - name - isPrivate - updatedAt - owner { - login - avatarUrl - } - parent { - name - owner { - login - avatarUrl - } - } - languages(first: 4) { - nodes { - color - name - } - } - refs(refPrefix: "refs/") { - totalCount - } - } - pageInfo { - hasNextPage - endCursor - } - } - } -} -` diff --git a/src/bot/index.ts b/src/bot/index.ts deleted file mode 100644 index eeedbd2f..00000000 --- a/src/bot/index.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { Probot } from 'probot' -import { logger } from '../utils/logger' -import { serverTrpc } from '../utils/trpc' -import { appOctokit, generateAppAccessToken } from './octokit' -import { createAllPushProtection, createDefaultBranchProtection } from './rules' - -import '../utils/proxy' - -type CustomProperties = Record - -const botLogger = logger.getSubLogger({ name: 'bot' }) - -// Helper function to get the fork name from the repository custom properties -export const getForkName = (props: CustomProperties) => { - return props.fork ?? null -} - -// Helper function to get the metadata from the repository description -export const getMetadata = ( - description: string | null, -): Record => { - botLogger.debug('Getting metadata from repository description', { - description, - }) - - if (!description) { - return {} - } - - try { - return JSON.parse(description) as Record - } catch { - botLogger.warn('Failed to parse repository description', { description }) - return {} - } -} - -function bot(app: Probot) { - // Catch-all to log all webhook events - app.onAny((context) => { - botLogger.debug('Received webhook event `onAny`', { event: context.name }) - }) - - // Good for debugging :) - app.on('ping', () => { - botLogger.debug('pong') - }) - - app.on('repository.created', async (context) => { - botLogger.info('Repository created', { - isFork: context.payload.repository.fork, - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) - - const authenticatedApp = await context.octokit.apps.getAuthenticated() - - // Create branch protection rules on forks - // if the repository is a fork, change branch protection rules to only allow the bot to push to it - if (context.payload.repository.fork) { - await createAllPushProtection( - context, - context.payload.repository.node_id, - authenticatedApp.data.node_id, - ) - } - - // Check repo properties to see if this is a mirror - const forkNameWithOwner = getForkName( - ( - context.payload.repository as typeof context.payload.repository & { - custom_properties: CustomProperties - } - ).custom_properties, - ) - - // Check repo description to see if this is a mirror - const metadata = getMetadata(context.payload.repository.description) - - // Skip if not a mirror - if (!forkNameWithOwner && !metadata.mirror) { - botLogger.info('Not a mirror repo, skipping', { - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) - return - } - - try { - // Get the default branch - const defaultBranch = context.payload.repository.default_branch - - botLogger.debug('Adding branch protections to default branch', { - defaultBranch, - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) - - await createDefaultBranchProtection( - context, - context.payload.repository.node_id, - authenticatedApp.data.node_id, - defaultBranch, - ) - } catch (error) { - botLogger.error('Failed to add branch protections', { - error, - }) - } - }) - - // We listen for repository edited events in case someone plays with branch protections - app.on('repository.edited', async (context) => { - const authenticatedApp = await context.octokit.apps.getAuthenticated() - - if (context.payload.repository.fork) { - await createAllPushProtection( - context, - context.payload.repository.node_id, - authenticatedApp.data.node_id, - ) - } - - // Check repo properties to see if this is a mirror - const forkNameWithOwner = getForkName( - ( - context.payload.repository as typeof context.payload.repository & { - custom_properties: CustomProperties - } - ).custom_properties, - ) - - // Check repo description to see if this is a mirror - const metadata = getMetadata(context.payload.repository.description) - - // Skip if not a mirror - if (!forkNameWithOwner && !metadata.mirror) { - botLogger.info('Not a mirror repo, skipping') - return - } - - try { - // Get the default branch - const defaultBranch = context.payload.repository.default_branch - - botLogger.debug('Adding branch protections to default branch', { - defaultBranch, - }) - - await createDefaultBranchProtection( - context, - context.payload.repository.node_id, - authenticatedApp.data.node_id, - defaultBranch, - ) - } catch (error) { - botLogger.error('Failed to add branch protections', { - error, - }) - } - }) - - app.on('push', async (context) => { - botLogger.info('Push event') - - // Check repo properties to see if this is a mirror - const forkNameWithOwner = getForkName( - ( - context.payload.repository as typeof context.payload.repository & { - custom_properties: CustomProperties - } - ).custom_properties, - ) - - const authenticatedApp = await context.octokit.apps.getAuthenticated() - - // Check repo description to see if this is a mirror - const metadata = getMetadata(context.payload.repository.description) - - // Skip if not a mirror - if (!forkNameWithOwner && !metadata.mirror) { - botLogger.info('Not a mirror repo, skipping') - return - } - - // Ignore if it was the bot - if (context.payload.sender.name === 'ospo-repo-sync-test[bot]') { - botLogger.info('Push was from bot, skipping') - return - } - - // Get the branch this was pushed to - const branch = context.payload.ref.replace('refs/heads/', '') - - // Skip if not the default branch - if (branch !== context.payload.repository.default_branch) { - botLogger.info('Not the default branch, skipping', { - branch, - defaultBranch: context.payload.repository.default_branch, - }) - return - } - - const [forkOwner, forkName] = forkNameWithOwner.split('/') - const mirrorOwner = context.payload.repository.owner.login - const mirrorName = context.payload.repository.name - const orgId = String(context.payload.organization!.id) - - // Need to validate that the bot itself is the one making this request - const privateInstallationId = - await appOctokit().rest.apps.getOrgInstallation({ - org: orgId, - }) - - const privateAccessToken = await generateAppAccessToken( - String(privateInstallationId.data.id), - ) - - const res = await serverTrpc.syncRepos - .mutate({ - accessToken: privateAccessToken, - forkBranchName: mirrorName, - mirrorBranchName: branch, - destinationTo: 'fork', - forkName, - forkOwner, - mirrorName, - mirrorOwner, - orgId, - }) - .catch((error: Error) => { - botLogger.error('Failed to sync repository', { error }) - }) - - botLogger.info('Synced repository', { res }) - - try { - // Get the default branch - const defaultBranch = context.payload.repository.default_branch - - botLogger.debug( - 'Adding branch protections to default branch in case repo.edited did not fire', - { - defaultBranch, - }, - ) - - await createDefaultBranchProtection( - context, - context.payload.repository.node_id, - authenticatedApp.data.node_id, - defaultBranch, - ) - } catch (error) { - botLogger.error('Failed to add branch protections', { error }) - } - }) -} - -export default bot diff --git a/src/bot/octokit.ts b/src/bot/octokit.ts deleted file mode 100644 index ec2598f9..00000000 --- a/src/bot/octokit.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { createAppAuth } from '@octokit/auth-app' -import { generatePKCS8Key } from 'utils/pem' -import { logger } from '../utils/logger' -import { Octokit } from './rest' - -const personalOctokitLogger = logger.getSubLogger({ name: 'personal-octokit' }) -const appOctokitLogger = logger.getSubLogger({ name: 'app-octokit' }) - -// This is a bug with the way the private key is stored in the docker env -// See https://github.com/moby/moby/issues/46773 -const privateKey = process.env.PRIVATE_KEY?.includes('\\n') - ? process.env.PRIVATE_KEY.replace(/\\n/g, '\n') - : process.env.PRIVATE_KEY! - -/** - * Generates an app access token for the app or an installation (if installationId is provided) - * @param installationId An optional installation ID to generate an app access token for - * @returns An access token for the app or installation - */ -export const generateAppAccessToken = async (installationId?: string) => { - const convertedKey = generatePKCS8Key(privateKey) - - if (installationId) { - const auth = createAppAuth({ - appId: process.env.APP_ID!, - privateKey: convertedKey, - installationId: installationId, - }) - - const appAuthentication = await auth({ - type: 'installation', - }) - - return appAuthentication.token - } - - const auth = createAppAuth({ - appId: process.env.APP_ID!, - privateKey, - clientId: process.env.CLIENT_ID!, - clientSecret: process.env.CLIENT_SECRET!, - }) - - const appAuthentication = await auth({ - type: 'app', - }) - - return appAuthentication.token -} - -/** - * Creates a new octokit instance that is authenticated as the app - * @returns Octokit authorized as the app - */ -export const appOctokit = () => { - const convertedKey = generatePKCS8Key(privateKey) - - return new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: process.env.APP_ID!, - privateKey: convertedKey, - clientId: process.env.CLIENT_ID!, - clientSecret: process.env.CLIENT_SECRET!, - }, - log: appOctokitLogger, - }) -} - -/** - * Creates a new octokit instance that is authenticated as the installation - * @param installationId installation ID to authenticate as - * @returns Octokit authorized as the installation - */ -export const installationOctokit = (installationId: string) => { - const convertedKey = generatePKCS8Key(privateKey) - - return new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: process.env.APP_ID!, - privateKey: convertedKey, - installationId: installationId, - }, - log: appOctokitLogger, - }) -} - -/** - * Creates a new octokit instance that is authenticated as the user - * @param token personal access token - * @returns Octokit authorized with the personal access token - */ -export const personalOctokit = (token: string) => { - return new Octokit({ - auth: token, - log: personalOctokitLogger, - }) -} - -/** - * Fetches octokit installations for both the contribution org and the private org - * @param contributionOrgId Id of the contribution org - * @param privateOrgId Id of the private org - * @returns octokit instances for both the contribution and private orgs - */ -export const getAuthenticatedOctokit = async ( - contributionOrgId: string, - privateOrgId: string, -) => { - const contributionInstallationId = - await appOctokit().rest.apps.getOrgInstallation({ - org: contributionOrgId, - }) - - const contributionAccessToken = await generateAppAccessToken( - String(contributionInstallationId.data.id), - ) - const contributionOctokit = installationOctokit( - String(contributionInstallationId.data.id), - ) - - const privateInstallationId = await appOctokit().rest.apps.getOrgInstallation( - { - org: privateOrgId, - }, - ) - - const privateAccessToken = await generateAppAccessToken( - String(privateInstallationId.data.id), - ) - const privateOctokit = installationOctokit( - String(privateInstallationId.data.id), - ) - - return { - contribution: { - accessToken: contributionAccessToken, - octokit: contributionOctokit, - installationId: String(contributionInstallationId.data.id), - }, - private: { - accessToken: privateAccessToken, - octokit: privateOctokit, - installationId: String(privateInstallationId.data.id), - }, - } -} diff --git a/src/bot/rest.ts b/src/bot/rest.ts deleted file mode 100644 index a8224dba..00000000 --- a/src/bot/rest.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { config } from '@probot/octokit-plugin-config' -import { Octokit as Core } from 'octokit' - -export const Octokit = Core.plugin(config).defaults({ - userAgent: `octokit-rest.js/repo-sync-bot`, -}) - -export type Octokit = InstanceType diff --git a/src/bot/rules.ts b/src/bot/rules.ts deleted file mode 100644 index bc6bc124..00000000 --- a/src/bot/rules.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { Repository } from '@octokit/graphql-schema' -import { Context } from 'probot' -import { logger } from '../utils/logger' -import { - forkBranchProtectionGQL, - forkBranchProtectionRulesetGQL, - getBranchProtectionRulesetGQL, - mirrorBranchProtectionGQL, - mirrorBranchProtectionRulesetGQL, -} from './graphql' - -const rulesLogger = logger.getSubLogger({ name: 'bot' }) - -type ContextEvent = Context<'repository.created' | 'repository.edited' | 'push'> - -/** - * Creates branch protection for the all branches - * First tries to create branch protection via rulesets, if that fails, falls back to branch protection - * @param context The context object - * @param repositoryNodeId The repository global node ID - * @param actorNodeId The actor node ID to bypass branch protections - */ -export const createAllPushProtection = async ( - context: ContextEvent, - repositoryNodeId: string, - actorNodeId: string, -) => { - rulesLogger.info('Creating branch protection for all branches', { - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) - - try { - // Add branch protection via rulesets to all branches - await createBranchProtectionRuleset( - context, - actorNodeId, - 'all-branch-protections-pma', - ['~ALL'], - ) - } catch (error) { - rulesLogger.error( - 'Failed to create branch protection via rulesets, falling back to branch protections', - { - error, - }, - ) - - try { - // Add branch protection via GQL to all branches - await createBranchProtection(context, repositoryNodeId, '*', actorNodeId) - } catch (error) { - rulesLogger.error( - 'Failed to create branch protection via GQL, falling back to REST', - { - error, - }, - ) - - try { - // Add branch protection via REST to all branches - await createBranchProtectionREST(context, '*') - } catch (error) { - rulesLogger.error( - 'Failed to create branch protection for all branches', - { - error, - }, - ) - } - } - } -} - -/** - * Creates branch protection for the default branch - * First tries to create branch protection via rulesets, if that fails, falls back to branch protection - * @param context The context object - * @param repositoryNodeId The repository global node ID - * @param actorNodeId The actor node ID to bypass branch protections - * @param defaultBranch The default branch name - */ -export const createDefaultBranchProtection = async ( - context: ContextEvent, - repositoryNodeId: string, - actorNodeId: string, - defaultBranch: string, -) => { - rulesLogger.info('Creating branch protection for default branch', { - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) - - try { - // Add branch protection via ruleset to the default branch - await createBranchProtectionRuleset( - context, - actorNodeId, - 'default-branch-protection-pma', - ['~DEFAULT_BRANCH'], - true, - ) - } catch (error) { - rulesLogger.error( - 'Failed to add branch protections to default branch, trying BP GQL instead', - { - error, - }, - ) - - try { - // Add branch protection via GQL to the default branch - await createBranchProtection( - context, - repositoryNodeId, - defaultBranch, - actorNodeId, - true, - ) - } catch (error) { - rulesLogger.error( - 'Failed to create branch protection from BP GQL, trying REST instead', - { - error, - }, - ) - - try { - // Add branch protection via REST to the default branch - await createBranchProtectionREST(context, defaultBranch) - } catch (error) { - rulesLogger.error( - 'Failed to create branch protection for default branch', - { - error, - }, - ) - } - } - } -} - -/** - * Creates branch protections rulesets - * @param context The context object - * @param bypassActorId The actor node ID to bypass branch protections - * @param ruleName The name of the branch protection ruleset - * @param includeRefs The refs to include in the branch protection ruleset - */ -const createBranchProtectionRuleset = async ( - context: ContextEvent, - bypassActorId: string, - ruleName: string, - includeRefs: string[], - isMirror = false, -) => { - rulesLogger.info('Creating branch protection via rulesets', { - isMirror, - }) - - // Get the current branch protection rulesets - const getBranchProtectionRuleset = await context.octokit.graphql<{ - repository: Repository - }>(getBranchProtectionRulesetGQL, { - owner: context.payload.repository.owner.login, - name: context.payload.repository.name, - }) - - if ( - getBranchProtectionRuleset.repository.rulesets?.nodes?.find( - (ruleset) => ruleset?.name === ruleName, - ) - ) { - rulesLogger.info('Branch protection ruleset already exists', { - getBranchProtectionRuleset, - }) - - return - } - - const query = isMirror - ? mirrorBranchProtectionRulesetGQL - : forkBranchProtectionRulesetGQL - - // Create the branch protection ruleset - const branchProtectionRuleset = await context.octokit.graphql(query, { - repositoryId: context.payload.repository.node_id, - ruleName, - bypassActorId, - includeRefs, - }) - - rulesLogger.info('Created branch protection via rulesets', { - branchProtectionRuleset, - }) -} - -/** - * Fallback function to create branch protection if ruleset creation fails - * @param context The context object - * @param repositoryNodeId The repository global node ID - * @param pattern The branch pattern - * @param actorId The bypass actor ID - */ -const createBranchProtection = async ( - context: ContextEvent, - repositoryNodeId: string, - pattern: string, - actorId: string, - isMirror = false, -) => { - rulesLogger.info('Creating branch protection via GQL', { - isMirror, - }) - - const query = isMirror ? mirrorBranchProtectionGQL : forkBranchProtectionGQL - - const forkBranchProtection = await context.octokit.graphql(query, { - repositoryId: repositoryNodeId, - pattern, - actorId, - }) - - rulesLogger.info('Created branch protection via GQL', { - forkBranchProtection, - }) -} - -/** - * The REST API fallback function to create branch protections in case GQL fails - * @param context The context object - * @param pattern The default branch pattern - */ -const createBranchProtectionREST = async ( - context: ContextEvent, - pattern: string, -) => { - rulesLogger.info('Creating branch protection via REST') - - const res = await context.octokit.repos.updateBranchProtection({ - branch: pattern, - enforce_admins: true, - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - required_pull_request_reviews: { - dismiss_stale_reviews: true, - require_code_owner_reviews: false, - required_approving_review_count: 1, - dismissal_restrictions: { - users: [], - teams: [], - }, - }, - required_status_checks: null, - restrictions: null, - }) - - rulesLogger.info('Created branch protection via REST', { - res, - repositoryOwner: context.payload.repository.owner.login, - repositoryName: context.payload.repository.name, - }) -} diff --git a/src/checks.ts b/src/checks.ts new file mode 100644 index 00000000..99b082f4 --- /dev/null +++ b/src/checks.ts @@ -0,0 +1,178 @@ +import { Octokit } from '@octokit/rest'; +import { Context as ProbotContext } from 'probot'; +import { Context as LambdaContext } from 'aws-lambda'; +import { GitHubCheck } from './GitHubCheck'; + +/** + * A check that should be sent to the default branch of the repository. + */ +export class DefaultCheck extends GitHubCheck { + public static async fromContexts(probotContext: ProbotContext, lambdaContext?: LambdaContext) { + const { data: repo } = await probotContext.octokit.rest.repos.get(probotContext.repo()); + const { data: head } = await probotContext.octokit.rest.git.getRef({ + ...probotContext.repo(), + ref: `heads/${repo.default_branch}` + }); + + const context = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + octokit: probotContext.octokit as unknown as Octokit, + getRemainingTimeInMillis: lambdaContext?.getRemainingTimeInMillis + ? () => lambdaContext.getRemainingTimeInMillis() + : undefined + }; + + return new DefaultCheck( + { + ...probotContext.repo(), + name: 'Default Branch', + head_sha: head.object.sha, + output: { + title: 'Default Branch', + summary: '', + text: [ + 'This check exists in case creation of other branches has failed', + 'To retry attaching this repository to an upstream repository, click the `resync` button' + ].join('\n') + }, + actions: [ + { + label: 'Resync', + description: 'Resync the root branch', + identifier: 'resync' + } + ] + }, + context + ); + } +} + +/** + * A check that should be sent to the root branch of the repository. + * + * It should allow for resyncing the root branch. + */ +export class RootCheck extends GitHubCheck { + // eslint-disable-next-line @typescript-eslint/require-await + public static async fromContexts(probotContext: ProbotContext<'push'>, lambdaContext?: LambdaContext) { + const context = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + octokit: probotContext.octokit as unknown as Octokit, + getRemainingTimeInMillis: lambdaContext?.getRemainingTimeInMillis + ? () => lambdaContext.getRemainingTimeInMillis() + : undefined + }; + + return new RootCheck( + { + ...probotContext.repo(), + name: 'Root Branch', + head_sha: probotContext.payload.after, + output: { + title: 'Root Branch', + summary: [ + '## Root Repository', + 'The `upstream` repository could be a fork of another repository (which may itself be a fork).', + 'In this case, contributions to the `upstream` repository are likely intended to be contributions to some repository in its network of forks (the `root` repository).', + '', + 'If the `upstream` repository is NOT a fork, then the `root` repository is the same as the `upstream` repository.', + + '## Root Branch', + 'The `root` branch follows the default branch of the `root` repository. This provides:', + '1. A starting point for upstream branches.', + '2. A convenient point to branch from when making new feature branches.', + '3. A good choice of default branch for the `downstream` repository.', + '', + '## Resyncing', + 'To resync the `root` branch with the default branch of the `root` repository, click the `resync` button' + ].join('\n') + }, + actions: [ + { + label: 'Resync', + description: 'Resync the root branch', + identifier: 'resync' + } + ] + }, + context + ); + } +} + +/** + * A check that should be sent to feature branches of the repository. + * + * It should give status for creating upstream branches. + */ +export class FeatureCheck extends GitHubCheck { + // eslint-disable-next-line @typescript-eslint/require-await + public static async fromContexts( + probotContext: ProbotContext<'push'>, + lambdaContext?: LambdaContext + ): Promise { + const context = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + octokit: probotContext.octokit as unknown as Octokit, + getRemainingTimeInMillis: lambdaContext?.getRemainingTimeInMillis + ? () => lambdaContext.getRemainingTimeInMillis() + : undefined + }; + + return new FeatureCheck( + { + ...probotContext.repo(), + name: 'Feature Branch', + head_sha: probotContext.payload.after, + output: { + title: 'Feature Branch', + summary: [ + 'The feature branches represent internal development on the `downstream` repository.', + 'Contents of these branches are not directly reflected on the `upstream` repository.', + 'This check ensures that an `upstream/` branch is created for the feature branch.' + ].join('\n') + } + }, + context + ); + } +} + +/** + * A check that should be sent to upstream branches of the repository. + * + * It should give status for mirroring changes to the upstream repository. + */ +export class UpstreamCheck extends GitHubCheck { + // eslint-disable-next-line @typescript-eslint/require-await + public static async fromContexts( + probotContext: ProbotContext<'push'>, + lambdaContext?: LambdaContext + ): Promise { + const context = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + octokit: probotContext.octokit as unknown as Octokit, + getRemainingTimeInMillis: lambdaContext?.getRemainingTimeInMillis + ? () => lambdaContext.getRemainingTimeInMillis() + : undefined + }; + + return new UpstreamCheck( + { + ...probotContext.repo(), + head_sha: probotContext.payload.after, + name: 'Upstream Branch', + output: { + title: 'Upstream Branch', + summary: [ + 'An `upstream/` branch is created whenever a `feature/` branch is created.', + 'It is initialised to the current HEAD of the `root` branch.', + 'This check ensures that changes to `upstream/` branches get mirrored in the upstream repository.' + ].join('\n') + } + }, + context + ); + } +} diff --git a/src/hooks/useFork.tsx b/src/hooks/useFork.tsx deleted file mode 100644 index 12a8404a..00000000 --- a/src/hooks/useFork.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { personalOctokit } from 'bot/octokit' -import { useSession } from 'next-auth/react' -import { useParams } from 'next/navigation' -import { Octokit } from 'octokit' -import { useEffect, useState } from 'react' - -const getForkById = async (accessToken: string, repoId: string) => { - try { - return ( - await personalOctokit(accessToken).request('GET /repositories/:id', { - id: repoId, - }) - ).data as Awaited>['data'] - } catch (error) { - console.error('Error fetching fork', { error }) - return null - } -} - -export const useForkData = () => { - const session = useSession() - const accessToken = session.data?.user.accessToken - - const { organizationId, forkId } = useParams() - - const [fork, setFork] = useState - > | null>(null) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - if (!organizationId || !forkId || !accessToken) { - return - } - - setIsLoading(true) - setError(null) - - getForkById(accessToken, forkId as string) - .then((fork) => { - setFork(fork) - }) - .catch((error: Error) => { - setError(error) - }) - .finally(() => { - setIsLoading(false) - }) - }, [accessToken, organizationId, forkId]) - - return { - data: fork, - isLoading, - error, - } -} - -export type ForkData = Awaited> diff --git a/src/hooks/useForks.tsx b/src/hooks/useForks.tsx deleted file mode 100644 index 8c93e139..00000000 --- a/src/hooks/useForks.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { getReposInOrgGQL } from 'bot/graphql' -import { personalOctokit } from 'bot/octokit' -import { useSession } from 'next-auth/react' -import { useEffect, useState } from 'react' -import { ForksObject } from 'types/forks' -import { logger } from '../utils/logger' - -const forksLogger = logger.getSubLogger({ name: 'useForks' }) - -const getForksInOrg = async (accessToken: string, login: string) => { - const res = await personalOctokit(accessToken) - .graphql.paginate(getReposInOrgGQL, { - login, - isFork: true, - }) - .catch((error: Error & { data: ForksObject }) => { - forksLogger.error('Error fetching forks', { error }) - return error.data - }) - - // the primer datatable component requires the data to not contain null - // values and the type returned from the graphql query contains null values - return { - organization: { - repositories: { - totalCount: res.organization.repositories.totalCount, - nodes: res.organization.repositories.nodes.map((node) => ({ - id: node.databaseId, - name: node.name, - isPrivate: node.isPrivate, - updatedAt: node.updatedAt, - owner: { - avatarUrl: node.owner.avatarUrl, - login: node.owner.login, - }, - parent: { - name: node?.parent?.name, - owner: { - login: node?.parent?.owner.login, - avatarUrl: node?.parent?.owner.avatarUrl, - }, - }, - languages: { - nodes: node.languages.nodes.map((node) => ({ - name: node.name, - color: node.color, - })), - }, - refs: { - totalCount: node.refs.totalCount, - }, - })), - }, - }, - } -} - -export const useForksData = (login: string | undefined) => { - const session = useSession() - const accessToken = session.data?.user.accessToken - - const [forks, setForks] = useState - > | null>(null) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - if (!login || !accessToken) { - return - } - - setIsLoading(true) - setError(null) - - getForksInOrg(accessToken, login) - .then((forks) => { - setForks(forks) - }) - .catch((error: Error) => { - setError(error) - }) - .finally(() => { - setIsLoading(false) - }) - }, [login, accessToken]) - - return { - data: forks, - isLoading, - error, - } -} diff --git a/src/hooks/useOrganization.tsx b/src/hooks/useOrganization.tsx deleted file mode 100644 index 2d06069f..00000000 --- a/src/hooks/useOrganization.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { personalOctokit } from 'bot/octokit' -import { useSession } from 'next-auth/react' -import { useParams, useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' - -export const getOrganizationData = async ( - accessToken: string, - orgId: string, -) => { - try { - return (await personalOctokit(accessToken).rest.orgs.get({ org: orgId })) - .data - } catch (error) { - console.error('Error fetching organization', { error }) - return null - } -} - -export const useOrgData = () => { - const router = useRouter() - - const { organizationId } = useParams() - - const session = useSession() - const accessToken = session.data?.user.accessToken - - const [orgData, setOrgData] = useState - > | null>(null) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - if (!accessToken || !organizationId) { - return - } - - setIsLoading(true) - setError(null) - - getOrganizationData(accessToken, organizationId as string) - .then((orgData) => { - if (!orgData) { - router.push('/_error') - } - - setOrgData(orgData) - }) - .catch((error: Error) => { - setError(error) - }) - .finally(() => { - setIsLoading(false) - }) - }, [organizationId, accessToken, router]) - - return { - data: orgData, - isLoading, - error, - } -} - -export type OrgData = Awaited> diff --git a/src/hooks/useOrganizations.tsx b/src/hooks/useOrganizations.tsx deleted file mode 100644 index 0667880e..00000000 --- a/src/hooks/useOrganizations.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { personalOctokit } from 'bot/octokit' -import { useSession } from 'next-auth/react' -import { useEffect, useState } from 'react' - -const getOrganizationsData = async (accessToken: string) => { - const octokit = personalOctokit(accessToken) - return await octokit.rest.orgs.listForAuthenticatedUser() -} - -export const useOrgsData = () => { - const session = useSession() - const accessToken = session.data?.user.accessToken - - const [organizationData, setOrganizationData] = useState( - null, - ) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - if (!accessToken) { - return - } - - setIsLoading(true) - setError(null) - - getOrganizationsData(accessToken) - .then((orgs) => { - setOrganizationData(orgs.data) - }) - .catch((error: Error) => { - setError(error) - }) - .finally(() => { - setIsLoading(false) - }) - }, [accessToken]) - - return { - data: organizationData, - isLoading, - error, - } -} - -export type OrgsData = Awaited>['data'] diff --git a/src/middleware.ts b/src/middleware.ts deleted file mode 100644 index 9b133f28..00000000 --- a/src/middleware.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { withAuth } from 'next-auth/middleware' - -export default withAuth({ - pages: { - signIn: '/auth/login', - error: '/auth/error', - }, -}) - -export const config = { - matcher: [ - /* - * Match all request paths except for the ones starting with: - * - api (API routes) - * - static (static files) - * - favicon.ico (favicon file) - */ - '/((?!api|static|favicon.ico).*)', - ], -} diff --git a/src/pages/api/webhooks.ts b/src/pages/api/webhooks.ts deleted file mode 100644 index f72b1348..00000000 --- a/src/pages/api/webhooks.ts +++ /dev/null @@ -1,26 +0,0 @@ -import app from 'bot' -import { createNodeMiddleware, createProbot } from 'probot' -import { logger } from 'utils/logger' - -export const probot = createProbot() - -const probotLogger = logger.getSubLogger({ name: 'probot' }) - -export const config = { - api: { - bodyParser: false, - }, -} - -export default createNodeMiddleware(app, { - probot: createProbot({ - defaults: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - log: { - child: () => probotLogger, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }), - webhooksPath: '/api/webhooks', -}) diff --git a/src/providers/registry-provider.tsx b/src/providers/registry-provider.tsx deleted file mode 100644 index 7ee80004..00000000 --- a/src/providers/registry-provider.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client' - -import { useServerInsertedHTML } from 'next/navigation' -import React, { useState } from 'react' -import { ServerStyleSheet, StyleSheetManager } from 'styled-components' - -export const StyledComponentsRegistry = ({ - children, -}: { - children: React.ReactNode -}) => { - // Only create stylesheet once with lazy initial state - // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state - const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()) - - useServerInsertedHTML(() => { - const styles = styledComponentsStyleSheet.getStyleElement() - styledComponentsStyleSheet.instance.clearTag() - return <>{styles} - }) - - if (typeof window !== 'undefined') return <>{children} - - return ( - - {children} - - ) -} diff --git a/src/providers/trpc-provider.tsx b/src/providers/trpc-provider.tsx deleted file mode 100644 index b9fa445c..00000000 --- a/src/providers/trpc-provider.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client' - -import { QueryClientProvider } from '@tanstack/react-query' -import { getFetch, httpBatchLink, loggerLink } from '@trpc/client' -import { useState } from 'react' -import superjson from 'superjson' -import queryClient from '../utils/query-client' -import { getBaseUrl, trpc } from '../utils/trpc' - -export const TrpcProvider = ({ children }: { children: React.ReactNode }) => { - const [trpcClient] = useState(() => - trpc.createClient({ - links: [ - loggerLink({ - enabled: () => true, - }), - httpBatchLink({ - url: `${getBaseUrl()}/api/trpc`, - fetch: async (input, init?) => { - const fetch = getFetch() - return fetch(input, { - ...init, - credentials: 'include', - }) - }, - }), - ], - transformer: superjson, - }), - ) - - return ( - - {children} - - ) -} diff --git a/src/server/config/controller.ts b/src/server/config/controller.ts deleted file mode 100644 index 03e01d40..00000000 --- a/src/server/config/controller.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { TRPCError } from '@trpc/server' -import { getConfig } from '../../bot/config' -import { logger } from '../../utils/logger' -import { GetConfigSchema } from './schema' - -const configApiLogger = logger.getSubLogger({ name: 'org-api' }) - -// Get the config values for the given org -export const getConfigHandler = async ({ - input, -}: { - input: GetConfigSchema -}) => { - try { - configApiLogger.info('Fetching config', { ...input }) - - const config = await getConfig(input.orgId) - - configApiLogger.debug('Fetched config', config) - - return config - } catch (error) { - configApiLogger.error('Error fetching config', { error }) - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: (error as Error).message, - }) - } -} diff --git a/src/server/config/router.ts b/src/server/config/router.ts deleted file mode 100644 index 82b8a8ba..00000000 --- a/src/server/config/router.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { procedure, router } from '../../utils/trpc-server' -import { getConfigHandler } from './controller' -import { GetConfigSchema } from './schema' - -const configRouter = router({ - getConfig: procedure - .input(GetConfigSchema) - .query(({ input }) => getConfigHandler({ input })), -}) - -export default configRouter diff --git a/src/server/config/schema.ts b/src/server/config/schema.ts deleted file mode 100644 index 3dee6cd3..00000000 --- a/src/server/config/schema.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod' - -export const GetConfigSchema = z.object({ - orgId: z.string(), -}) - -export type GetConfigSchema = z.TypeOf diff --git a/src/server/git/controller.ts b/src/server/git/controller.ts deleted file mode 100644 index a0e9131c..00000000 --- a/src/server/git/controller.ts +++ /dev/null @@ -1,123 +0,0 @@ -import simpleGit, { SimpleGitOptions } from 'simple-git' -import { getConfig } from '../../bot/config' -import { getAuthenticatedOctokit } from '../../bot/octokit' -import { generateAuthUrl } from '../../utils/auth' -import { temporaryDirectory } from '../../utils/dir' -import { logger } from '../../utils/logger' -import { SyncReposSchema } from './schema' - -const gitApiLogger = logger.getSubLogger({ name: 'git-api' }) - -// Syncs the fork and mirror repos -export const syncReposHandler = async ({ - input, -}: { - input: SyncReposSchema -}) => { - try { - gitApiLogger.info('Syncing repos', { ...input, accessToken: 'none' }) - - const config = await getConfig(input.orgId) - - gitApiLogger.debug('Fetched config', config) - - const { publicOrg, privateOrg } = config - - const octokitData = await getAuthenticatedOctokit(publicOrg, privateOrg) - const contributionOctokit = octokitData.contribution.octokit - const contributionAccessToken = octokitData.contribution.accessToken - - const privateOctokit = octokitData.private.octokit - const privateInstallationId = octokitData.private.installationId - const privateAccessToken = octokitData.private.accessToken - - const forkRepo = await contributionOctokit.rest.repos.get({ - owner: input.forkOwner, - repo: input.forkName, - }) - - const mirrorRepo = await privateOctokit.rest.repos.get({ - owner: input.mirrorOwner, - repo: input.mirrorName, - }) - - gitApiLogger.debug('Fetched both fork and mirror repos') - - const forkRemote = generateAuthUrl( - contributionAccessToken, - forkRepo.data.owner.login, - forkRepo.data.name, - ) - - const mirrorRemote = generateAuthUrl( - privateAccessToken, - mirrorRepo.data.owner.login, - mirrorRepo.data.name, - ) - - // First clone the fork and mirror repos into the same folder - const tempDir = temporaryDirectory() - - const options: Partial = { - config: [ - `user.name=pma[bot]`, - `user.email=${privateInstallationId}+pma[bot]@users.noreply.github.com`, - // Disable any global git hooks to prevent potential interference when running the app locally - 'core.hooksPath=/dev/null', - ], - } - - const git = simpleGit(tempDir, options) - await git.init() - await git.addRemote('fork', forkRemote) - await git.addRemote('mirror', mirrorRemote) - await git.fetch(['fork']) - await git.fetch(['mirror']) - - // Check if the branch exists on both repos - const forkBranches = await git.branch(['--list', 'fork/*']) - const mirrorBranches = await git.branch(['--list', 'mirror/*']) - - gitApiLogger.debug('branches', { - forkBranches: forkBranches.all, - mirrorBranches: mirrorBranches.all, - }) - - if (input.destinationTo === 'fork') { - await git.checkoutBranch( - input.forkBranchName, - `fork/${input.forkBranchName}`, - ) - gitApiLogger.debug('Checked out branch', input.forkBranchName) - await git.mergeFromTo( - `mirror/${input.mirrorBranchName}`, - input.forkBranchName, - ) - gitApiLogger.debug('Merged branches') - gitApiLogger.debug('git status', await git.status()) - await git.push('fork', input.forkBranchName) - } else { - await git.checkoutBranch( - input.mirrorBranchName, - `mirror/${input.mirrorBranchName}`, - ) - gitApiLogger.debug('Checked out branch', input.mirrorBranchName) - await git.mergeFromTo( - `fork/${input.forkBranchName}`, - input.mirrorBranchName, - ) - gitApiLogger.debug('Merged branches') - gitApiLogger.debug('git status', await git.status()) - await git.push('mirror', input.mirrorBranchName) - } - - return { - success: true, - } - } catch (error) { - gitApiLogger.error('Error syncing repos', { error }) - return { - success: false, - } - } -} diff --git a/src/server/git/router.ts b/src/server/git/router.ts deleted file mode 100644 index 609cd1ff..00000000 --- a/src/server/git/router.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { gitProcedure, router } from '../../utils/trpc-server' -import { syncReposHandler } from './controller' -import { SyncReposSchema } from './schema' - -const gitRouter = router({ - syncRepos: gitProcedure - .input(SyncReposSchema) - .mutation(({ input }) => syncReposHandler({ input })), -}) - -export default gitRouter diff --git a/src/server/git/schema.ts b/src/server/git/schema.ts deleted file mode 100644 index fed04de8..00000000 --- a/src/server/git/schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from 'zod' - -export const SyncReposSchema = z.object({ - accessToken: z.string(), - orgId: z.string(), - destinationTo: z.enum(['mirror', 'fork']), - forkOwner: z.string(), - forkName: z.string(), - mirrorName: z.string(), - mirrorOwner: z.string(), - mirrorBranchName: z.string(), - forkBranchName: z.string(), -}) - -export type SyncReposSchema = z.TypeOf diff --git a/src/server/octokit/controller.ts b/src/server/octokit/controller.ts deleted file mode 100644 index e8448fb0..00000000 --- a/src/server/octokit/controller.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { appOctokit } from '../../bot/octokit' -import { logger } from '../../utils/logger' -import { CheckInstallationSchema } from './schema' - -const octokitApiLogger = logger.getSubLogger({ name: 'octokit-api' }) - -// Checks if the app is installed in the org -export const checkInstallationHandler = async ({ - input, -}: { - input: CheckInstallationSchema -}) => { - try { - octokitApiLogger.info('Checking installation', { input }) - - const installationId = await appOctokit().rest.apps.getOrgInstallation({ - org: input.orgId, - }) - - if (installationId.data.id) { - return { installed: true } - } - - return { installed: false } - } catch (error) { - octokitApiLogger.error('Failed to check app installation', { input, error }) - - return { installed: false } - } -} diff --git a/src/server/octokit/router.ts b/src/server/octokit/router.ts deleted file mode 100644 index a4871e2f..00000000 --- a/src/server/octokit/router.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { procedure, router } from '../../utils/trpc-server' -import { checkInstallationHandler } from './controller' -import { CheckInstallationSchema } from './schema' - -const octokitRouter = router({ - checkInstallation: procedure - .input(CheckInstallationSchema) - .query(({ input }) => checkInstallationHandler({ input })), -}) - -export default octokitRouter diff --git a/src/server/octokit/schema.ts b/src/server/octokit/schema.ts deleted file mode 100644 index 3b10495e..00000000 --- a/src/server/octokit/schema.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod' - -export const CheckInstallationSchema = z.object({ - orgId: z.string(), -}) - -export type CheckInstallationSchema = z.TypeOf diff --git a/src/server/repos/controller.ts b/src/server/repos/controller.ts deleted file mode 100644 index 981fd895..00000000 --- a/src/server/repos/controller.ts +++ /dev/null @@ -1,319 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -import simpleGit, { SimpleGitOptions } from 'simple-git' -import { generateAuthUrl } from 'utils/auth' -import { temporaryDirectory } from 'utils/dir' -import { getConfig } from '../../bot/config' -import { - appOctokit, - getAuthenticatedOctokit, - installationOctokit, -} from '../../bot/octokit' -import { logger } from '../../utils/logger' -import { - CreateMirrorSchema, - DeleteMirrorSchema, - EditMirrorSchema, - ListMirrorsSchema, -} from './schema' -import { TRPCError } from '@trpc/server' - -const reposApiLogger = logger.getSubLogger({ name: 'repos-api' }) - -// Creates a mirror of a forked repo -export const createMirrorHandler = async ({ - input, -}: { - input: CreateMirrorSchema -}) => { - try { - reposApiLogger.info('createMirror', { input: input }) - - const config = await getConfig(input.orgId) - - reposApiLogger.debug('Fetched config', { config }) - - const { publicOrg, privateOrg } = config - - const octokitData = await getAuthenticatedOctokit(publicOrg, privateOrg) - const contributionOctokit = octokitData.contribution.octokit - const contributionAccessToken = octokitData.contribution.accessToken - - const privateOctokit = octokitData.private.octokit - const privateInstallationId = octokitData.private.installationId - const privateAccessToken = octokitData.private.accessToken - - const orgData = await contributionOctokit.rest.orgs.get({ - org: publicOrg, - }) - - await contributionOctokit.rest.repos - .get({ - owner: orgData.data.login, - repo: input.newRepoName, - }) - .then((res) => { - // if we get a response, then we know the repo exists so we throw an error - if (res.status === 200) { - reposApiLogger.info( - `Repo ${orgData.data.login}/${input.newRepoName} already exists`, - ) - - throw new Error( - `Repo ${orgData.data.login}/${input.newRepoName} already exists`, - ) - } - }) - .catch((error) => { - // catch and rethrow the error if the repo already exists - if ((error as Error).message.includes('already exists')) { - throw error - } - - // if there is a real error, then we log it and throw it - if (!(error as Error).message.includes('Not Found')) { - reposApiLogger.error('Not found', { error }) - throw error - } - }) - - try { - const forkData = await contributionOctokit.rest.repos.get({ - owner: input.forkRepoOwner, - repo: input.forkRepoName, - }) - - // Now create a temporary directory to clone the repo into - const tempDir = temporaryDirectory() - - const options: Partial = { - config: [ - `user.name=pma[bot]`, - // We want to use the private installation ID as the email so that we can push to the private repo - `user.email=${privateInstallationId}+pma[bot]@users.noreply.github.com`, - // Disable any global git hooks to prevent potential interference when running the app locally - 'core.hooksPath=/dev/null', - ], - } - const git = simpleGit(tempDir, options) - const remote = generateAuthUrl( - contributionAccessToken, - input.forkRepoOwner, - input.forkRepoName, - ) - - await git.clone(remote, tempDir) - - // Get the organization custom properties - const orgCustomProps = - await privateOctokit.rest.orgs.getAllCustomProperties({ - org: privateOrg, - }) - - // Creates custom property fork in the org if it doesn't exist - if ( - !orgCustomProps.data.some( - (prop: { property_name: string }) => prop.property_name === 'fork', - ) - ) { - await privateOctokit.rest.orgs.createOrUpdateCustomProperty({ - org: privateOrg, - custom_property_name: 'fork', - value_type: 'string', - }) - } - - // This repo needs to be created in the private org - const newRepo = await privateOctokit.rest.repos.createInOrg({ - name: input.newRepoName, - org: privateOrg, - private: true, - description: `Mirror of ${input.forkRepoOwner}/${input.forkRepoName}`, - custom_properties: { - fork: `${input.forkRepoOwner}/${input.forkRepoName}`, - }, - }) - - const defaultBranch = forkData.data.default_branch - - // Add the mirror remote - const upstreamRemote = generateAuthUrl( - privateAccessToken, - newRepo.data.owner.login, - newRepo.data.name, - ) - await git.addRemote('upstream', upstreamRemote) - await git.push('upstream', defaultBranch) - - // Create a new branch on both - await git.checkoutBranch(input.newBranchName, defaultBranch) - await git.push('origin', input.newBranchName) - - reposApiLogger.info('Mirror created', { - org: newRepo.data.owner.login, - name: newRepo.data.name, - }) - - return { - success: true, - data: newRepo.data, - } - } catch (error) { - // Clean up the private mirror repo made - await privateOctokit.rest.repos.delete({ - owner: privateOrg, - repo: input.newRepoName, - }) - - throw error - } - } catch (error) { - reposApiLogger.error('Error creating mirror', { error }) - - const message = - (error as any)?.response?.data?.errors?.[0]?.message ?? - (error as any)?.response?.data?.message ?? - (error as Error)?.message ?? - 'An error occurred' - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message, - }) - } -} - -// Lists all the mirrors of a forked repo -export const listMirrorsHandler = async ({ - input, -}: { - input: ListMirrorsSchema -}) => { - try { - reposApiLogger.info('Fetching mirrors', { input }) - - const config = await getConfig(input.orgId) - - const installationId = await appOctokit().rest.apps.getOrgInstallation({ - org: config.privateOrg, - }) - - const octokit = installationOctokit(String(installationId.data.id)) - - const privateOrgData = await octokit.rest.orgs.get({ - org: config.privateOrg, - }) - const publicOrgData = await octokit.rest.orgs.get({ org: input.orgId }) - - const repos = await octokit.paginate( - octokit.rest.search.repos, - { - q: `org:"${privateOrgData.data.login}"+props.fork:"${publicOrgData.data.login}/${input.forkName}" org:"${privateOrgData.data.login}"&mirror:"${publicOrgData.data.login}/${input.forkName}"+in:description`, - order: 'desc', - sort: 'updated', - }, - (response) => response.data, - ) - - return repos - } catch (error) { - reposApiLogger.info('Failed to fetch mirrors', { input, error }) - - const message = - (error as any)?.response?.data?.errors?.[0]?.message ?? - (error as any)?.response?.data?.message ?? - (error as Error)?.message ?? - 'An error occurred' - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message, - }) - } -} - -// Edits the name of a mirror -export const editMirrorHandler = async ({ - input, -}: { - input: EditMirrorSchema -}) => { - try { - reposApiLogger.info('Editing mirror', { input }) - - const config = await getConfig(input.orgId) - - const installationId = await appOctokit().rest.apps.getOrgInstallation({ - org: config.privateOrg, - }) - - const octokit = installationOctokit(String(installationId.data.id)) - - const repo = await octokit.rest.repos.update({ - owner: config.privateOrg, - repo: input.mirrorName, - name: input.newMirrorName, - }) - - return { - success: true, - data: repo.data, - } - } catch (error) { - reposApiLogger.error('Failed to edit mirror', { input, error }) - - const message = - (error as any)?.response?.data?.errors?.[0]?.message ?? - (error as any)?.response?.data?.message ?? - (error as Error)?.message ?? - 'An error occurred' - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message, - }) - } -} - -// Deletes a mirror -export const deleteMirrorHandler = async ({ - input, -}: { - input: DeleteMirrorSchema -}) => { - try { - reposApiLogger.info('Deleting mirror', { input }) - - const config = await getConfig(input.orgId) - - const installationId = await appOctokit().rest.apps.getOrgInstallation({ - org: config.privateOrg, - }) - - const octokit = installationOctokit(String(installationId.data.id)) - - await octokit.rest.repos.delete({ - owner: config.privateOrg, - repo: input.mirrorName, - }) - - return { - success: true, - } - } catch (error) { - reposApiLogger.error('Failed to delete mirror', { input, error }) - - const message = - (error as any)?.response?.data?.errors?.[0]?.message ?? - (error as any)?.response?.data?.message ?? - (error as Error)?.message ?? - 'An error occurred' - - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message, - }) - } -} diff --git a/src/server/repos/router.ts b/src/server/repos/router.ts deleted file mode 100644 index 53f16da0..00000000 --- a/src/server/repos/router.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { procedure, router } from '../../utils/trpc-server' -import { - createMirrorHandler, - deleteMirrorHandler, - editMirrorHandler, - listMirrorsHandler, -} from './controller' -import { - CreateMirrorSchema, - DeleteMirrorSchema, - EditMirrorSchema, - ListMirrorsSchema, -} from './schema' - -const reposRouter = router({ - createMirror: procedure - .input(CreateMirrorSchema) - .mutation(({ input }) => createMirrorHandler({ input })), - listMirrors: procedure - .input(ListMirrorsSchema) - .query(({ input }) => listMirrorsHandler({ input })), - editMirror: procedure - .input(EditMirrorSchema) - .mutation(({ input }) => editMirrorHandler({ input })), - deleteMirror: procedure - .input(DeleteMirrorSchema) - .mutation(({ input }) => deleteMirrorHandler({ input })), -}) - -export default reposRouter diff --git a/src/server/repos/schema.ts b/src/server/repos/schema.ts deleted file mode 100644 index 676790a7..00000000 --- a/src/server/repos/schema.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from 'zod' - -export const CreateMirrorSchema = z.object({ - orgId: z.string(), - forkRepoOwner: z.string(), - forkRepoName: z.string(), - forkId: z.string(), - newRepoName: z.string().max(100), - newBranchName: z.string(), -}) - -export const ListMirrorsSchema = z.object({ - orgId: z.string(), - forkName: z.string(), -}) - -export const EditMirrorSchema = z.object({ - orgId: z.string(), - mirrorName: z.string(), - newMirrorName: z.string().max(100), -}) - -export const DeleteMirrorSchema = z.object({ - orgId: z.string(), - mirrorName: z.string(), -}) - -export type CreateMirrorSchema = z.TypeOf -export type ListMirrorsSchema = z.TypeOf -export type EditMirrorSchema = z.TypeOf -export type DeleteMirrorSchema = z.TypeOf diff --git a/src/types/forks.ts b/src/types/forks.ts deleted file mode 100644 index f322812e..00000000 --- a/src/types/forks.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { z } from 'zod' - -// this type is generated from the graphql query to support -// the requirements of the primer datatable component -const ForksObject = z.object({ - organization: z.object({ - repositories: z.object({ - totalCount: z.number(), - nodes: z.array( - z.object({ - databaseId: z.number(), - name: z.string(), - isPrivate: z.boolean(), - updatedAt: z.date(), - owner: z.object({ - avatarUrl: z.string(), - login: z.string(), - }), - parent: z.object({ - name: z.string(), - owner: z.object({ - login: z.string(), - avatarUrl: z.string(), - }), - }), - languages: z.object({ - nodes: z.array( - z.object({ - name: z.string(), - color: z.string(), - }), - ), - }), - refs: z.object({ - totalCount: z.number(), - }), - }), - ), - }), - }), -}) - -export type ForksObject = z.infer diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts deleted file mode 100644 index 6863dbf4..00000000 --- a/src/types/next-auth.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import 'next-auth' -import { DefaultSession } from 'next-auth' - -declare module 'next-auth' { - interface Session { - error?: 'RefreshAccessTokenError' - user: { - accessToken: string | undefined - } & DefaultSession['user'] - } -} - -declare module 'next-auth/jwt' { - interface JWT { - accessToken: string - accessTokenExpires: number - refreshToken: string - refreshTokenExpires: number - error?: 'RefreshAccessTokenError' - } -} - -export { Session } diff --git a/src/utils/auth.ts b/src/utils/auth.ts deleted file mode 100644 index 5f3d08bb..00000000 --- a/src/utils/auth.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { TRPCError } from '@trpc/server' -import { getConfig } from '../bot/config' -import { personalOctokit } from '../bot/octokit' -import { logger } from '../utils/logger' - -/** - * Generates a git url with the access token in it - * @param accessToken Access token for the app - * @param owner Repo Owner - * @param repo Repo Name - * @returns formatted authenticated git url - */ -export const generateAuthUrl = ( - accessToken: string, - owner: string, - repo: string, -) => { - const USER = 'x-access-token' - const PASS = accessToken - const REPO = `github.com/${owner}/${repo}` - return `https://${USER}:${PASS}@${REPO}` -} - -const middlewareLogger = logger.getSubLogger({ name: 'middleware' }) - -/** - * Checks if the access token has access to the mirror org and repo - * - * Used for checking if the git.syncRepos mutation has the correct permissions - * @param accessToken Access token for the private org's installation - * @param mirrorOrgOwner Mirror org owner - * @param mirrorRepo Mirror repo name - */ -export const checkGitHubAppInstallationAuth = async ( - accessToken: string | undefined, - mirrorOrgOwner: string | undefined, - mirrorRepo: string | undefined, -) => { - if (!accessToken || !mirrorOrgOwner || !mirrorRepo) { - middlewareLogger.error('No access token or mirror org/repo provided') - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } - - const octokit = personalOctokit(accessToken) - - const data = await octokit.rest.repos - .get({ - owner: mirrorOrgOwner, - repo: mirrorRepo, - }) - .catch((error: Error) => { - middlewareLogger.error('Error checking github app installation auth', { - error, - }) - return null - }) - - if (!data?.data) { - middlewareLogger.error('App does not have access to mirror repo') - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } -} - -/** - * Checks to see if the user has access to the organization - * @param accessToken Access token for a user - */ -export const checkGitHubAuth = async ( - accessToken: string | undefined, - orgId: string | undefined, -) => { - if (!accessToken) { - middlewareLogger.error('No access token provided') - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } - - const octokit = personalOctokit(accessToken) - - try { - // Check validity of token - const user = await octokit.rest.users.getAuthenticated() - if (!user) { - middlewareLogger.error('No user found') - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } - - // Check if user has access to the org - if (orgId) { - const config = await getConfig(orgId) - - const org = await octokit.rest.orgs.getMembershipForAuthenticatedUser({ - org: config.publicOrg, - }) - - if (!org.data) { - middlewareLogger.error('User does not have access to org') - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } - } - } catch (error) { - middlewareLogger.error('Error checking github auth', error) - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } -} diff --git a/src/utils/dir.ts b/src/utils/dir.ts deleted file mode 100644 index 37c4188e..00000000 --- a/src/utils/dir.ts +++ /dev/null @@ -1,4 +0,0 @@ -import tempy from 'tempy' - -// FIXME: Had to downgrade tempy to not use esm -export const temporaryDirectory = () => tempy.directory() diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index b24a61f3..00000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import safeStringify from 'fast-safe-stringify' -import { Logger } from 'tslog' - -// This is a workaround for the issue with JSON.stringify and circular references -const stringify = (obj: any) => { - try { - return JSON.stringify(obj) - } catch { - return safeStringify(obj) - } -} - -// If you need logs during tests you can set the env var TEST_LOGGING=true -const getLoggerType = () => { - if (process.env.NODE_ENV === 'development') { - return 'pretty' - } - - if (process.env.NODE_ENV === 'test' || process.env.TEST_LOGGING === '1') { - return 'pretty' - } - - return 'json' -} - -// Map logger level name to number for tsLog -const mapLevelToMethod: Record = { - silly: 0, - trace: 1, - debug: 2, - info: 3, - warn: 4, - error: 5, - fatal: 6, -} - -export const logger = new Logger({ - type: getLoggerType(), - minLevel: - mapLevelToMethod[process.env.LOGGING_LEVEL?.toLowerCase() ?? 'info'], - maskValuesRegEx: [ - /"access[-._]?token":"[^"]+"/g, - /"api[-._]?key":"[^"]+"/g, - /"client[-._]?secret":"[^"]+"/g, - /"cookie":"[^"]+"/g, - /"password":"[^"]+"/g, - /"refresh[-._]?token":"[^"]+"/g, - /"secret":"[^"]+"/g, - /"token":"[^"]+"/g, - /(?<=:\/\/)([^:]+):([^@]+)(?=@)/g, - ], - overwrite: { - transportJSON: (log) => { - const logObjWithMeta = log as { - [key: string]: any - _meta?: Record - } - - const output: { - meta?: Record - message?: string - info?: Record - data?: Record - } = {} - - // set meta - output.meta = logObjWithMeta._meta - - // set message if it's a string or set it as info - if ( - Object.hasOwn(logObjWithMeta, '0') && - typeof logObjWithMeta['0'] === 'string' - ) { - output.message = logObjWithMeta['0'] - } else { - output.info = logObjWithMeta['0'] - } - - // set data - if (Object.hasOwn(logObjWithMeta, '1')) { - output.data = logObjWithMeta['1'] - } - - console.log(stringify(output)) - }, - }, -}) - -logger.getSubLogger({ name: 'default' }).info('Initialized logger') - -// Redirect next logs to our logger >:( -console.warn = logger - .getSubLogger({ name: 'console' }) - .warn.bind(logger.getSubLogger({ name: 'console' })) -// Currently set to warn because of warning issued by undici showing as error -console.error = logger - .getSubLogger({ name: 'console' }) - .warn.bind(logger.getSubLogger({ name: 'console' })) diff --git a/src/utils/pem.ts b/src/utils/pem.ts deleted file mode 100644 index 24f2304c..00000000 --- a/src/utils/pem.ts +++ /dev/null @@ -1,17 +0,0 @@ -import crypto from 'crypto' - -/** - * Converts a private key in PKCS1 format to PKCS8 format - * @param privateKey Private key in PKCS1 format - */ -export const generatePKCS8Key = (privateKey: string) => { - const privateKeyPkcs8 = crypto - .createPrivateKey(privateKey.replace(/\\n/g, '\n')) - .export({ - type: 'pkcs8', - format: 'pem', - }) - .toString() - - return privateKeyPkcs8 -} diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts deleted file mode 100644 index 39a9f08a..00000000 --- a/src/utils/proxy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici' -import { ProxyAgent } from 'proxy-agent' -import http from 'http' -import https from 'https' - -// set unidci global dispatcher to a proxy agent based on env variables for fetch calls -const envHttpProxyAgent = new EnvHttpProxyAgent() -setGlobalDispatcher(envHttpProxyAgent) - -// set global agent for older libraries that use http and https calls -const proxyAgent = new ProxyAgent() -http.globalAgent = proxyAgent -https.globalAgent = proxyAgent diff --git a/src/utils/query-client.ts b/src/utils/query-client.ts deleted file mode 100644 index 146de715..00000000 --- a/src/utils/query-client.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { QueryClient } from '@tanstack/react-query' - -const queryClient = new QueryClient({ - defaultOptions: { queries: { staleTime: 5 * 1000 } }, -}) - -export default queryClient diff --git a/src/utils/trpc-middleware.ts b/src/utils/trpc-middleware.ts deleted file mode 100644 index f27659b5..00000000 --- a/src/utils/trpc-middleware.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { checkGitHubAppInstallationAuth, checkGitHubAuth } from './auth' -import { Middleware } from './trpc-server' - -export const verifyGitHubAppAuth: Middleware = async (opts) => { - const { ctx, rawInput } = opts - - // Check app authentication - await checkGitHubAppInstallationAuth( - (rawInput as Record)?.accessToken, - (rawInput as Record)?.mirrorOwner, - (rawInput as Record)?.mirrorName, - ) - - return opts.next({ - ctx, - }) -} - -export const verifyAuth: Middleware = async (opts) => { - const { ctx, rawInput } = opts - - // Verify valid github session - await checkGitHubAuth( - ctx.session?.user?.accessToken, - (rawInput as Record)?.orgId, // Fetch orgId if there is one - ) - - return opts.next({ - ctx, - }) -} diff --git a/src/utils/trpc-server.ts b/src/utils/trpc-server.ts deleted file mode 100644 index 4d25e021..00000000 --- a/src/utils/trpc-server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { initTRPC } from '@trpc/server' -import { CreateNextContextOptions } from '@trpc/server/adapters/next' -import { getServerSession } from 'next-auth' -import SuperJSON from 'superjson' -import { nextAuthOptions } from '../app/api/auth/lib/nextauth-options' -import { verifyAuth, verifyGitHubAppAuth } from '../utils/trpc-middleware' - -export const createContext = async (opts: CreateNextContextOptions) => { - const session = await getServerSession(opts.req, opts.res, nextAuthOptions) - - return { - session, - } -} - -// Avoid exporting the entire t-object -// since it's not very descriptive. -// For instance, the use of a t variable -// is common in i18n libraries. -export const t = initTRPC.context().create({ - transformer: SuperJSON, -}) - -// Base router and procedure helpers -export const router = t.router -const publicProcedure = t.procedure -export type Middleware = Parameters<(typeof t.procedure)['use']>[0] -// Used for general user access token verification -export const procedure = publicProcedure.use(verifyAuth) -// Used for GitHub App authentication verification (non-user events like 'push') -export const gitProcedure = publicProcedure.use(verifyGitHubAppAuth) diff --git a/src/utils/trpc.ts b/src/utils/trpc.ts deleted file mode 100644 index fbaf1f28..00000000 --- a/src/utils/trpc.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createTRPCProxyClient, httpBatchLink } from '@trpc/client' -import { createTRPCReact } from '@trpc/react-query' -import SuperJSON from 'superjson' -import type { AppRouter } from '../app/api/trpc/trpc-router' - -export const getBaseUrl = () => { - if (typeof window !== 'undefined') - // browser should use relative path - return '' - if (process.env.VERCEL_URL) - // reference for vercel.com deployments - return `https://${process.env.VERCEL_URL}` - if (process.env.NEXTAUTH_URL) - // reference for non-vercel providers - return process.env.NEXTAUTH_URL - // assume localhost - return `http://localhost:${process.env.PORT ?? 3000}` -} - -export const trpc = createTRPCReact() -export const serverTrpc = createTRPCProxyClient({ - transformer: SuperJSON, - links: [ - httpBatchLink({ - url: `${getBaseUrl()}/api/trpc`, - }), - ], -})