├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ └── publish.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bun.lock ├── docker-compose.yml ├── package.json ├── src ├── env.ts ├── index.ts ├── md-truncate.ts └── util.ts ├── test └── test.sh ├── tsconfig.json └── wrangler.toml /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Dockerfile* 3 | docker-compose* 4 | .dockerignore 5 | .git 6 | .gitignore 7 | .github 8 | .wrangler 9 | README.md 10 | LICENSE 11 | .vscode 12 | Makefile 13 | helm-charts 14 | .env 15 | .editorconfig 16 | .idea 17 | coverage* 18 | test/ 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: JRoy 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Worker 2 | on: 3 | push: 4 | repository_dispatch: 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 60 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: oven-sh/setup-bun@v2 12 | - name: Build & Deploy Worker 13 | uses: cloudflare/wrangler-action@v3 14 | with: 15 | apiToken: ${{ secrets.CF_API_TOKEN }} 16 | accountId: ${{ secrets.CF_ACCOUNT_ID }} 17 | packageManager: bun 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Publish 11 | steps: 12 | - name: Checkout Repository 13 | uses: actions/checkout@v4 14 | - name: Login to GitHub Container Registry 15 | uses: docker/login-action@v3 16 | with: 17 | registry: ghcr.io 18 | username: ${{ github.actor }} 19 | password: ${{ secrets.GITHUB_TOKEN }} 20 | - name: Generate tag 21 | id: tag 22 | run: | 23 | ts=$(date +%s) 24 | branch=${GITHUB_REF##*/} 25 | echo "IMAGE_ID=${branch}-${GITHUB_SHA::8}-${ts}" >> "$GITHUB_OUTPUT" 26 | - name: Publish Docker image 27 | uses: docker/build-push-action@v6 28 | with: 29 | context: . 30 | push: true 31 | tags: | 32 | ghcr.io/jroy/disgit:latest 33 | ghcr.io/jroy/disgit:${{ steps.tag.outputs.IMAGE_ID }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | .storage 4 | .wrangler 5 | .capnp 6 | 7 | test/headers.http 8 | test/body.json 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun:canary-debian AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY . . 6 | 7 | RUN bun install --frozen-lockfile 8 | 9 | RUN bun run docker:build 10 | 11 | FROM jacoblincool/workerd:latest 12 | 13 | COPY --from=builder /app/worker.capnp ./worker.capnp 14 | 15 | EXPOSE 8080/tcp 16 | CMD ["serve", "--experimental", "--binary", "worker.capnp"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # disgit 2 | A Cloudflare Worker (or Docker Container) which provides better GitHub->Discord webhook integration than the built-in Discord webhook executor. 3 | 4 | You can use this Cloudflare worker by following the steps after clicking the button below 5 | 6 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/JRoy/disgit) 7 | 8 | 9 | You can also deploy disgit to docker container: 10 | * Docker Compose: Clone this repository and run `docker compose up --build -d`. 11 | * The worker will be started on port 8080 12 | * Docker Image: The disgit container image is published to the GitHub Container Registry [here](https://github.com/JRoy/disgit/pkgs/container/disgit). For more information on how to authenticate with GitHub's container registry, check the help article [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry). 13 | 14 | ## Environment Variables 15 | disgit has the following optional environment variables that you can use to customize your instance; 16 | - `IGNORED_BRANCHES_REGEX` - A regex pattern for branches that should be ignored 17 | - `IGNORED_BRANCHES` - A comma seperated list of branches that should be ignored 18 | - `IGNORED_USERS` - A comma seperated list of users that should be ignored 19 | - `IGNORED_PAYLOADS` - A comma seperated list of webhook events that should be ignored 20 | - `DEBUG_PASTE` - Set to `true` to enable debug embeds. 21 | - `EXECUTE_MERGE_QUEUE_BRANCHES` - Set to `true` to unignore merge queue related branches. 22 | 23 | ## Supported Events 24 | The following webhook events are supported as of now; 25 | * [check_run](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#check_run) 26 | * [commit_comment](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#commit_comment) 27 | * [create](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#create) 28 | * [delete](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#delete) 29 | * [deployment](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#deployment) 30 | * [deployment_status](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#deployment_status) 31 | * [discussion](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#discussion) 32 | * [discussion_comment](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#discussion_comment) 33 | * [fork](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#fork) 34 | * [gollum](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#gollum) (wiki) 35 | * [issue_comment](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment) 36 | * This event also sends pull request comments...*sigh* 37 | * [issues](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issues) 38 | * [package](https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#package) 39 | * [ping](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#ping) 40 | * [pull_request](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request) 41 | * [pull_request_review](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review) 42 | * [pull_request_review_comment](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review_comment) 43 | * [push](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#push) 44 | * [release](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#release) 45 | * [star](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#star) 46 | * ...feel free to contribute more that suit your needs! 47 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "disgit", 6 | "dependencies": { 7 | "@cloudflare/workers-types": "^4.20250607.0", 8 | "typescript": "^5.8.3", 9 | "wrangler": "^4.19.1", 10 | }, 11 | "devDependencies": { 12 | "selflare": "^1.1.2", 13 | }, 14 | }, 15 | }, 16 | "packages": { 17 | "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], 18 | 19 | "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg=="], 20 | 21 | "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250607.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZPEYltIzDbGYlSZFcAyVnbFxZeG9xlmGmz4742C7tvZ8fOrqXExdqnqPwE1anIRLKx/Oxt/nemPng+V2iSrPUw=="], 22 | 23 | "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250607.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AC4bH9u8RF97szBM5KNXO6jRHbeat8CX4Cm+T3IANSkHAMkVRi5wjjnCy1teF3TBrXSRB5epvH8y7jTBQjJpLQ=="], 24 | 25 | "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250607.0", "", { "os": "linux", "cpu": "x64" }, "sha512-floRxUocnj4+ULmIsIRFwzxzRTarvidD0tLpSS3HjyuTUy5ma+pgAyfXQcE3oP8orBJlG5QIOtgVDpH4jD1qTQ=="], 26 | 27 | "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250607.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-w4ghOr5eof/rnpuW1XJwwuLOReVumAt8SrRv66aZB7ly7eBpfnOCZIaa4obZ9uuEbonxblHXUyVfBMgjL6u52w=="], 28 | 29 | "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250607.0", "", { "os": "win32", "cpu": "x64" }, "sha512-fKp/Cgh4LG2XiqiVShGENVqZ+nZ6B19y3cqf2tRuHVPgRQgnYnEDneS8jJTkltC0gKJNp+cLawEe8DSDbQJ7Vg=="], 30 | 31 | "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250607.0", "", {}, "sha512-OYmKNzC2eQy6CNj+j0go8Ut3SezjsprCgJyEaBzJql+473WAN9ndVnNZy9lj/tTyLV6wzpQkZWmRAKGDmacvkg=="], 32 | 33 | "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 34 | 35 | "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], 36 | 37 | "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], 38 | 39 | "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], 40 | 41 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], 42 | 43 | "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], 44 | 45 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], 46 | 47 | "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], 48 | 49 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], 50 | 51 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], 52 | 53 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], 54 | 55 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], 56 | 57 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], 58 | 59 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], 60 | 61 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], 62 | 63 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], 64 | 65 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], 66 | 67 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], 68 | 69 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], 70 | 71 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], 72 | 73 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], 74 | 75 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], 76 | 77 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], 78 | 79 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], 80 | 81 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], 82 | 83 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], 84 | 85 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], 86 | 87 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], 88 | 89 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], 90 | 91 | "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], 92 | 93 | "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], 94 | 95 | "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], 96 | 97 | "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], 98 | 99 | "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], 100 | 101 | "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], 102 | 103 | "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], 104 | 105 | "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], 106 | 107 | "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], 108 | 109 | "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], 110 | 111 | "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], 112 | 113 | "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], 114 | 115 | "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], 116 | 117 | "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], 118 | 119 | "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], 120 | 121 | "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], 122 | 123 | "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], 124 | 125 | "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], 126 | 127 | "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], 128 | 129 | "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], 130 | 131 | "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], 132 | 133 | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 134 | 135 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 136 | 137 | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 138 | 139 | "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], 140 | 141 | "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], 142 | 143 | "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 144 | 145 | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 146 | 147 | "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], 148 | 149 | "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 150 | 151 | "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], 152 | 153 | "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], 154 | 155 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 156 | 157 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 158 | 159 | "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], 160 | 161 | "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 162 | 163 | "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], 164 | 165 | "dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="], 166 | 167 | "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 168 | 169 | "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], 170 | 171 | "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 172 | 173 | "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], 174 | 175 | "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 176 | 177 | "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 178 | 179 | "estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], 180 | 181 | "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], 182 | 183 | "exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="], 184 | 185 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 186 | 187 | "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 188 | 189 | "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], 190 | 191 | "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], 192 | 193 | "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], 194 | 195 | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 196 | 197 | "magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], 198 | 199 | "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], 200 | 201 | "miniflare": ["miniflare@4.20250525.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250525.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-4PJlT5WA+hfclFU5Q7xnpG1G1VGYTXaf/3iu6iKQ8IsbSi9QvPTA2bSZ5goCFxmJXDjV4cxttVxB0Wl1CLuQ0w=="], 202 | 203 | "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], 204 | 205 | "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], 206 | 207 | "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 208 | 209 | "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 210 | 211 | "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], 212 | 213 | "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 214 | 215 | "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], 216 | 217 | "rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="], 218 | 219 | "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], 220 | 221 | "selflare": ["selflare@1.1.2", "", { "dependencies": { "@iarna/toml": "^2.2.5", "dedent": "^1.5.1", "workerd": "^1.20240223.1", "wrangler": "^3.30.1", "yargs": "^17.7.2" }, "bin": { "selflare": "dist/index.js" } }, "sha512-i3nasf4rzAt5g3qmcmnjoxQHFsZ/u9+KJ8LZqFhV0PEkFngMwicpoMDXtwNngID8TuZss/dex+RZ9ZcFGODoiA=="], 222 | 223 | "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], 224 | 225 | "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], 226 | 227 | "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], 228 | 229 | "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 230 | 231 | "sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="], 232 | 233 | "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], 234 | 235 | "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], 236 | 237 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 238 | 239 | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 240 | 241 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 242 | 243 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 244 | 245 | "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], 246 | 247 | "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], 248 | 249 | "unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], 250 | 251 | "workerd": ["workerd@1.20250607.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250607.0", "@cloudflare/workerd-darwin-arm64": "1.20250607.0", "@cloudflare/workerd-linux-64": "1.20250607.0", "@cloudflare/workerd-linux-arm64": "1.20250607.0", "@cloudflare/workerd-windows-64": "1.20250607.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-STF1R9iySmM8XgSWQP1v0Jp9wxzZzH8p369CGHsFdlOUgtMbRmD5ssQxv9Z5PnZQ+L/c+kiioUxPR3ROC3AKwA=="], 252 | 253 | "wrangler": ["wrangler@4.19.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250525.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250525.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250525.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-b+ed2SJKauHgndl4Im1wHE+FeSSlrdlEZNuvpc8q/94k4EmRxRkXnwBAsVWuicBxG3HStFLQPGGlvL8wGKTtHw=="], 254 | 255 | "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 256 | 257 | "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 258 | 259 | "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 260 | 261 | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], 262 | 263 | "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 264 | 265 | "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], 266 | 267 | "zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], 268 | 269 | "miniflare/workerd": ["workerd@1.20250525.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250525.0", "@cloudflare/workerd-darwin-arm64": "1.20250525.0", "@cloudflare/workerd-linux-64": "1.20250525.0", "@cloudflare/workerd-linux-arm64": "1.20250525.0", "@cloudflare/workerd-windows-64": "1.20250525.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ=="], 270 | 271 | "selflare/wrangler": ["wrangler@3.114.9", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250408.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250408.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-1e0gL+rxLF04kM9bW4sxoDGLXpJ1x53Rx1t18JuUm6F67qadKKPISyUAXuBeIQudWrCWEBXaTVnSdLHz0yBXbA=="], 272 | 273 | "wrangler/workerd": ["workerd@1.20250525.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250525.0", "@cloudflare/workerd-darwin-arm64": "1.20250525.0", "@cloudflare/workerd-linux-64": "1.20250525.0", "@cloudflare/workerd-linux-arm64": "1.20250525.0", "@cloudflare/workerd-windows-64": "1.20250525.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ=="], 274 | 275 | "miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250525.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-L5l+7sSJJT2+riR5rS3Q3PKNNySPjWfRIeaNGMVRi1dPO6QPi4lwuxfRUFNoeUdilZJUVPfSZvTtj9RedsKznQ=="], 276 | 277 | "miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250525.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3IbIdrF/vJWh/WBvshwcSyUh175VAiLRW7963S1dXChrZ1N5wuKGQm9xY69cIGVtitpMJWWW3jLq7J/Xxwm0Q=="], 278 | 279 | "miniflare/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250525.0", "", { "os": "linux", "cpu": "x64" }, "sha512-KSyQPAby+c6cpENoO0ayCQlY6QIh28l/+QID7VC1SLXfiNHy+hPNsH1vVBTST6CilHVAQSsy9tCZ9O9XECB8yg=="], 280 | 281 | "miniflare/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250525.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nt0FUxS2kQhJUea4hMCNPaetkrAFDhPnNX/ntwcqVlGgnGt75iaAhupWJbU0GB+gIWlKeuClUUnDZqKbicoKyg=="], 282 | 283 | "miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250525.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mwTj+9f3uIa4NEXR1cOa82PjLa6dbrb3J+KCVJFYIaq7e63VxEzOchCXS4tublT2pmOhmFqkgBMXrxozxNkR2Q=="], 284 | 285 | "selflare/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], 286 | 287 | "selflare/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.14", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg=="], 288 | 289 | "selflare/wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], 290 | 291 | "selflare/wrangler/miniflare": ["miniflare@3.20250408.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250408.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uTs7cGWFErgJTKtBdmtctwhuoxniuCQqDT8+xaEiJdEC8d+HsaZVYfZwIX2NuSmdAiHMe7NtbdZYjFMbIXtJsQ=="], 292 | 293 | "selflare/wrangler/unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], 294 | 295 | "selflare/wrangler/workerd": ["workerd@1.20250408.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250408.0", "@cloudflare/workerd-darwin-arm64": "1.20250408.0", "@cloudflare/workerd-linux-64": "1.20250408.0", "@cloudflare/workerd-linux-arm64": "1.20250408.0", "@cloudflare/workerd-windows-64": "1.20250408.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-bBUX+UsvpzAqiWFNeZrlZmDGddiGZdBBbftZJz2wE6iUg/cIAJeVQYTtS/3ahaicguoLBz4nJiDo8luqM9fx1A=="], 296 | 297 | "wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250525.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-L5l+7sSJJT2+riR5rS3Q3PKNNySPjWfRIeaNGMVRi1dPO6QPi4lwuxfRUFNoeUdilZJUVPfSZvTtj9RedsKznQ=="], 298 | 299 | "wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250525.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3IbIdrF/vJWh/WBvshwcSyUh175VAiLRW7963S1dXChrZ1N5wuKGQm9xY69cIGVtitpMJWWW3jLq7J/Xxwm0Q=="], 300 | 301 | "wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250525.0", "", { "os": "linux", "cpu": "x64" }, "sha512-KSyQPAby+c6cpENoO0ayCQlY6QIh28l/+QID7VC1SLXfiNHy+hPNsH1vVBTST6CilHVAQSsy9tCZ9O9XECB8yg=="], 302 | 303 | "wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250525.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nt0FUxS2kQhJUea4hMCNPaetkrAFDhPnNX/ntwcqVlGgnGt75iaAhupWJbU0GB+gIWlKeuClUUnDZqKbicoKyg=="], 304 | 305 | "wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250525.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mwTj+9f3uIa4NEXR1cOa82PjLa6dbrb3J+KCVJFYIaq7e63VxEzOchCXS4tublT2pmOhmFqkgBMXrxozxNkR2Q=="], 306 | 307 | "selflare/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], 308 | 309 | "selflare/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], 310 | 311 | "selflare/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], 312 | 313 | "selflare/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], 314 | 315 | "selflare/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], 316 | 317 | "selflare/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], 318 | 319 | "selflare/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], 320 | 321 | "selflare/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], 322 | 323 | "selflare/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], 324 | 325 | "selflare/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], 326 | 327 | "selflare/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], 328 | 329 | "selflare/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], 330 | 331 | "selflare/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], 332 | 333 | "selflare/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], 334 | 335 | "selflare/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], 336 | 337 | "selflare/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], 338 | 339 | "selflare/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], 340 | 341 | "selflare/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], 342 | 343 | "selflare/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], 344 | 345 | "selflare/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], 346 | 347 | "selflare/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], 348 | 349 | "selflare/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], 350 | 351 | "selflare/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250408.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-bxhIwBWxaNItZLXDNOKY2dCv0FHjDiDkfJFpwv4HvtvU5MKcrivZHVmmfDzLW85rqzfcDOmKbZeMPVfiKxdBZw=="], 352 | 353 | "selflare/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250408.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5XZ2Oykr8bSo7zBmERtHh18h5BZYC/6H1YFWVxEj3PtalF3+6SHsO4KZsbGvDml9Pu7sHV277jiZE5eny8Hlyw=="], 354 | 355 | "selflare/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250408.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WbgItXWln6G5d7GvYLWcuOzAVwafysZaWunH3UEfsm95wPuRofpYnlDD861gdWJX10IHSVgMStGESUcs7FLerQ=="], 356 | 357 | "selflare/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250408.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pAhEywPPvr92SLylnQfZEPgXz+9pOG9G9haAPLpEatncZwYiYd9yiR6HYWhKp2erzCoNrOqKg9IlQwU3z1IDiw=="], 358 | 359 | "selflare/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250408.0", "", { "os": "win32", "cpu": "x64" }, "sha512-nJ3RjMKGae2aF2rZ/CNeBvQPM+W5V1SUK0FYWG/uomyr7uQ2l4IayHna1ODg/OHHTEgIjwom0Mbn58iXb0WOcQ=="], 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | worker: 3 | build: . 4 | image: worker 5 | environment: 6 | - IGNORED_BRANCH_REGEX=${IGNORED_BRANCH_REGEX} 7 | - IGNORED_BRANCHES=${IGNORED_BRANCHES} 8 | - IGNORED_USERS=${IGNORED_USERS} 9 | - IGNORED_PAYLOADS=${IGNORED_PAYLOADS} 10 | - DEBUG_PASTE=${DEBUG_PASTE} 11 | volumes: 12 | - ./.storage/cache:/worker/cache 13 | ports: 14 | - "8080:8080" 15 | healthcheck: 16 | test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"] 17 | interval: 15s 18 | timeout: 10s 19 | retries: 3 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disgit", 3 | "version": "1.0.0", 4 | "description": "A Cloudflare Worker which provides better GitHub->Discord webhook integration than the built-in Discord webhook executor.", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/JRoy/disgit.git" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "@cloudflare/workers-types": "^4.20250607.0", 13 | "typescript": "^5.8.3", 14 | "wrangler": "^4.19.1" 15 | }, 16 | "devDependencies": { 17 | "selflare": "^1.1.2" 18 | }, 19 | "scripts": { 20 | "docker:build": "WRANGLER_SEND_METRICS=false wrangler deploy --dry-run --outdir .wrangler/dist && selflare compile --script .wrangler/dist/index.js", 21 | "build": "selflare compile", 22 | "start": "wrangler dev src/index.ts --local=true", 23 | "docker": "wrangler dev index.ts --local=true", 24 | "deploy": "wranger deploy", 25 | "check": "tsc" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Raw environment from Workers 3 | */ 4 | export interface Env { 5 | IGNORED_BRANCHES_REGEX: string; 6 | IGNORED_BRANCHES: string; 7 | IGNORED_USERS: string; 8 | IGNORED_PAYLOADS: string; 9 | 10 | GITHUB_WEBHOOK_SECRET: string; 11 | 12 | DEBUG_PASTE: string; 13 | AWAIT_ERRORS: string; 14 | EXECUTE_MERGE_QUEUE_BRANCHES: string; 15 | } 16 | 17 | /** 18 | * Parsed environment 19 | */ 20 | export class BoundEnv { 21 | private ignoredBranchPattern?: RegExp; 22 | private ignoredBranches: string[]; 23 | private ignoredUsers: string[]; 24 | private ignoredPayloads: string[]; 25 | readonly githubWebhookSecret: string; 26 | readonly debugPaste: boolean; 27 | readonly awaitErrors: boolean; 28 | readonly executeMergeQueueBranches: boolean; 29 | 30 | constructor(env: Env) { 31 | if (typeof env.IGNORED_BRANCHES_REGEX !== 'undefined') { 32 | this.ignoredBranchPattern = new RegExp(env.IGNORED_BRANCHES_REGEX); 33 | } 34 | this.ignoredBranches = env.IGNORED_BRANCHES?.split(",") || []; 35 | this.ignoredUsers = env.IGNORED_USERS?.split(",") || []; 36 | this.ignoredPayloads = env.IGNORED_PAYLOADS?.split(",") || []; 37 | this.githubWebhookSecret = env.GITHUB_WEBHOOK_SECRET; 38 | this.debugPaste = env.DEBUG_PASTE == "true" || env.DEBUG_PASTE == "1"; 39 | this.awaitErrors = env.AWAIT_ERRORS == "true" || env.AWAIT_ERRORS == "1"; 40 | this.executeMergeQueueBranches = env.EXECUTE_MERGE_QUEUE_BRANCHES == "true" || env.EXECUTE_MERGE_QUEUE_BRANCHES == "1"; 41 | } 42 | 43 | /** 44 | * @param {String} branch 45 | * @return {boolean} 46 | */ 47 | isIgnoredBranch(branch: string): boolean { 48 | if (!this.executeMergeQueueBranches && branch.startsWith('gh-readonly-queue/')) { 49 | return true; 50 | } 51 | 52 | return (this.ignoredBranchPattern && branch.match(this.ignoredBranchPattern) != null) || this.ignoredBranches.includes(branch); 53 | } 54 | 55 | /** 56 | * @param {String} user 57 | * @return {boolean} 58 | */ 59 | isIgnoredUser(user: string): boolean { 60 | return this.ignoredUsers.includes(user); 61 | } 62 | 63 | /** 64 | * @param {String} payload 65 | * @return {boolean} 66 | */ 67 | isIgnoredPayload(payload: string): boolean { 68 | return this.ignoredPayloads.includes(payload); 69 | } 70 | 71 | async buildDebugPaste(embed: any): Promise { 72 | embed = JSON.stringify({ 73 | "files": [ 74 | { 75 | "content": { 76 | "format": "text", 77 | "value": embed 78 | } 79 | } 80 | ] 81 | }); 82 | 83 | embed = await (await fetch("https://api.pastes.dev/post", { 84 | headers: { 85 | "user-agent": "disgit", 86 | "content-type": "application/json", 87 | }, 88 | method: "POST", 89 | body: embed 90 | })).text(); 91 | 92 | embed = JSON.stringify({ 93 | "content": embed 94 | }); 95 | return embed; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {BoundEnv, Env} from './env'; 2 | import {Sender, shortCommit, truncate} from './util'; 3 | 4 | // Handles event sent by cloudflare 5 | async function handleRequest(request: Request, env: BoundEnv): Promise { 6 | const event = request.headers.get("X-GitHub-Event"); 7 | const contentType = request.headers.get("content-type"); 8 | if (event != null && contentType != null) { 9 | /*if (!(await validateRequest(request, env.githubWebhookSecret))) { 10 | return new Response('Invalid secret', { status: 403 }); 11 | }*/ 12 | let json: any; 13 | if (contentType.includes("application/json")) { 14 | json = await request.json(); 15 | } else if (contentType.includes("application/x-www-form-urlencoded")) { 16 | json = JSON.parse((await request.formData()).get("payload") as string); 17 | } else { 18 | return new Response(`Unknown content type ${contentType}`, { status: 400 }); 19 | } 20 | 21 | let embed = buildEmbed(json, event, env); 22 | if (embed == null) { 23 | return new Response('Webhook NO-OP', {status: 200}) 24 | } 25 | 26 | if (env.debugPaste) { 27 | embed = await env.buildDebugPaste(embed); 28 | } 29 | 30 | const url = new URL(request.url) 31 | let [hookId, hookToken] = (url.pathname + url.search).substring(1).split("/"); 32 | 33 | if (typeof(hookToken) == 'undefined') { 34 | return new Response('Missing Webhook Authorization', { status: 400 }); 35 | } 36 | 37 | const result = await fetch(`https://discord.com/api/webhooks/${hookId}/${hookToken}`, { 38 | headers: { 39 | "content-type": "application/json;charset=UTF=8" 40 | }, 41 | method: "POST", 42 | body: embed 43 | }) 44 | 45 | if (result.ok) { 46 | return new Response(`disgit successfully executed webhook ${hookId}`, { status: 200 }); 47 | } 48 | 49 | return new Response(`Failed to send webhook ${hookId}: Got ${result.status} from discord: ${await result.text()}`, { status: 400 }); 50 | } else { 51 | return new Response('Bad Request', { status: 400 }) 52 | } 53 | } 54 | 55 | function buildEmbed(json: any, event: string, env: BoundEnv): string | null { 56 | const { action } = json; 57 | 58 | if (env.isIgnoredPayload(event)) { 59 | return null; 60 | } 61 | 62 | switch (event) { 63 | case "check_run": { 64 | if (action !== "completed") { 65 | break; 66 | } 67 | return buildCheck(json, env); 68 | } 69 | case "commit_comment": { 70 | if (action !== "created") { 71 | break; 72 | } 73 | return buildCommitComment(json, env); 74 | } 75 | case "create": { 76 | return buildCreateBranch(json, env); 77 | } 78 | case "delete": { 79 | return buildDeleteBranch(json, env); 80 | } 81 | case "discussion": { 82 | if (action !== "created") { 83 | break; 84 | } 85 | return buildDiscussion(json, env); 86 | } 87 | case "discussion_comment": { 88 | if (action !== "created") { 89 | break; 90 | } 91 | return buildDiscussionComment(json, env); 92 | } 93 | case "fork": { 94 | return buildFork(json); 95 | } 96 | case "issue_comment": { 97 | if (action !== "created") { 98 | break; 99 | } 100 | return buildIssueComment(json, env); 101 | } 102 | case "issues": { 103 | switch (action) { 104 | case "opened": { 105 | return buildIssue(json, env); 106 | } 107 | case "reopened": { 108 | return buildIssueReOpen(json); 109 | } 110 | case "closed": { 111 | return buildIssueClose(json); 112 | } 113 | default: { 114 | return null; 115 | } 116 | } 117 | } 118 | case "package": { 119 | switch (action) { 120 | case "published": { 121 | return buildPackagePublished(json); 122 | } 123 | case "updated": { 124 | return buildPackageUpdated(json); 125 | } 126 | default : { 127 | return null; 128 | } 129 | } 130 | } 131 | case "ping": { 132 | return buildPing(json); 133 | } 134 | case "pull_request": { 135 | switch (action) { 136 | case "opened": { 137 | return buildPull(json, env); 138 | } 139 | case "closed": { 140 | return buildPullClose(json); 141 | } 142 | case "reopened": { 143 | return buildPullReopen(json); 144 | } 145 | case "converted_to_draft": { 146 | return buildPullDraft(json); 147 | } 148 | case "ready_for_review": { 149 | return buildPullReadyReview(json); 150 | } 151 | case "enqueued": { 152 | return buildPullEnqueue(json); 153 | } 154 | case "dequeued": { 155 | return buildPullDequeue(json); 156 | } 157 | default: { 158 | return null; 159 | } 160 | } 161 | } 162 | case "pull_request_review": { 163 | switch (action) { 164 | case "submitted": 165 | case "dismissed": { 166 | return buildPullReview(json); 167 | } 168 | default: { 169 | return null; 170 | } 171 | } 172 | } 173 | case "pull_request_review_comment": { 174 | if (action !== "created") { 175 | break; 176 | } 177 | return buildPullReviewComment(json); 178 | } 179 | case "push": { 180 | return buildPush(json, env); 181 | } 182 | case "release": { 183 | if (action === "released" || action === "prereleased") { 184 | return buildRelease(json); 185 | } 186 | break; 187 | } 188 | case "star": { 189 | if (action !== "created") { 190 | break; 191 | } 192 | return buildStar(json); 193 | } 194 | case "deployment": { 195 | if (action !== "created") { 196 | break; 197 | } 198 | return buildDeployment(json); 199 | } 200 | case "deployment_status": { 201 | return buildDeploymentStatus(json); 202 | } 203 | // Wiki 204 | case "gollum": { 205 | return buildWiki(json); 206 | } 207 | } 208 | 209 | return null; 210 | } 211 | 212 | function buildEmbedBody(title: string, url: string | undefined, sender: Sender, color: number, description?: string, footer?: string, fields?: any[]): string { 213 | const date = new Date(); 214 | const avatarHash = `${date.getFullYear()}${date.getMonth()}${date.getDay()}`; 215 | return JSON.stringify({ 216 | embeds: [ 217 | { 218 | title: truncate(title, 255), 219 | url, 220 | description: description ? truncate(description, 1000) : undefined, 221 | author: { 222 | name: truncate(sender.login, 255), 223 | url: sender.html_url, 224 | icon_url: `${sender.avatar_url}?=${avatarHash}` 225 | }, 226 | color, 227 | footer: footer ? { 228 | text: truncate(footer, 255), 229 | } : undefined, 230 | fields: fields ?? [], 231 | } 232 | ] 233 | }); 234 | } 235 | 236 | function buildPing(json: any): string { 237 | const { zen, hook, repository, sender, organization } = json; 238 | 239 | const isOrg = hook['type'] == 'Organization'; 240 | 241 | const name = isOrg ? organization['login'] : repository['full_name']; 242 | 243 | return buildEmbedBody(`[${name}] ${hook.type} hook ping received`, undefined, sender, 12118406, zen); 244 | } 245 | 246 | function buildRelease(json: any): string | null { 247 | const { release, repository, sender } = json; 248 | const { draft, name, tag_name, body, html_url, prerelease } = release; 249 | 250 | if (draft) { 251 | return null; 252 | } 253 | 254 | let effectiveName = name == null ? tag_name : name; 255 | 256 | return buildEmbedBody( 257 | `[${repository['full_name']}] New ${prerelease ? 'pre' : ''}release published: ${effectiveName}`, 258 | html_url, 259 | sender, 260 | 14573028, 261 | body 262 | ); 263 | } 264 | 265 | function buildPush(json: any, env: BoundEnv): string | null { 266 | const { commits, forced, after, repository, ref, compare, sender } = json; 267 | 268 | let branch = ref.substring(11); 269 | 270 | if (env.isIgnoredBranch(branch)) { 271 | return null; 272 | } 273 | 274 | if (env.isIgnoredUser(sender.login)) { 275 | return null; 276 | } 277 | 278 | if (forced) { 279 | return buildEmbedBody( 280 | `[${repository["full_name"]}] Branch ${branch} was force-pushed to \`${shortCommit(after)}\``, 281 | compare.replace("...", ".."), 282 | sender, 283 | 16722234 284 | ); 285 | } 286 | 287 | let amount = commits.length; 288 | 289 | if (amount === 0) { 290 | return null; 291 | } 292 | 293 | let description = ""; 294 | let lastCommitUrl = ""; 295 | for (let i = 0; i < commits.length; i++) { 296 | let commit = commits[i]; 297 | let commitUrl = commit.url; 298 | let line = `[\`${shortCommit(commit.id)}\`](${commitUrl}) ${truncate(commit.message.split("\n")[0], 50)} - ${commit.author.username} 299 | `; 300 | if (description.length + line.length >= 1500) { 301 | break; 302 | } 303 | lastCommitUrl = commitUrl; 304 | description += line; 305 | } 306 | const commitWord = amount === 1 ? "commit" : "commits"; 307 | 308 | return buildEmbedBody( 309 | `[${repository.name}:${branch}] ${amount} new ${commitWord}`, 310 | amount === 1 ? lastCommitUrl : compare, 311 | sender, 312 | 6120164, 313 | description 314 | ); 315 | } 316 | 317 | function buildPullEnqueue(json: any): string { 318 | const { pull_request, repository, sender } = json; 319 | 320 | const queueUrl = `${repository["html_url"]}/queue/${pull_request.base.ref}`; 321 | 322 | return buildEmbedBody( 323 | `[${repository["full_name"]}] Pull request enqueued: #${pull_request.number} ${pull_request.title}`, 324 | pull_request["html_url"], 325 | sender, 326 | 16752896, 327 | `[View \`${pull_request.base.ref}\` merge queue](${queueUrl})` 328 | ); 329 | } 330 | 331 | function buildPullDequeue(json: any): string { 332 | const { pull_request, repository, sender } = json; 333 | 334 | const queueUrl = `${repository["html_url"]}/queue/${pull_request.base.ref}`; 335 | 336 | return buildEmbedBody( 337 | `[${repository["full_name"]}] Pull request dequeued: #${pull_request.number} ${pull_request.title}`, 338 | pull_request["html_url"], 339 | sender, 340 | 13584462, 341 | `[View \`${pull_request.base.ref}\` merge queue](${queueUrl})` 342 | ); 343 | } 344 | 345 | function buildPullReviewComment(json: any): string { 346 | const { pull_request, comment, repository, sender } = json; 347 | 348 | return buildEmbedBody( 349 | `[${repository["full_name"]}] Pull request review comment: #${pull_request.number} ${pull_request.title}`, 350 | comment["html_url"], 351 | sender, 352 | 7829367, 353 | comment.body 354 | ); 355 | } 356 | 357 | function buildPullReview(json: any): string { 358 | const { pull_request, review, repository, action, sender } = json; 359 | 360 | let state = "reviewed"; 361 | let color = 7829367; 362 | switch (review.state) { 363 | case "approved": { 364 | state = "approved"; 365 | color = 37378; 366 | break; 367 | } 368 | case "changes_requested": { 369 | state = "changes requested" 370 | color = 16722234; 371 | break; 372 | } 373 | default: { 374 | if (action === "dismissed") { 375 | state = "review dismissed"; 376 | } 377 | break; 378 | } 379 | } 380 | 381 | return buildEmbedBody( 382 | `[${repository["full_name"]}] Pull request ${state}: #${pull_request.number} ${pull_request.title}`, 383 | review["html_url"], 384 | sender, 385 | color, 386 | review.body 387 | ); 388 | } 389 | 390 | function buildPullReadyReview(json: any): string { 391 | const { pull_request, repository, sender } = json; 392 | 393 | return buildEmbedBody( 394 | `[${repository["full_name"]}] Pull request marked for review: #${pull_request.number} ${pull_request.title}`, 395 | pull_request["html_url"], 396 | sender, 397 | 37378 398 | ); 399 | } 400 | 401 | function buildPullDraft(json: any): string { 402 | const { pull_request, repository, sender } = json; 403 | 404 | return buildEmbedBody( 405 | `[${repository["full_name"]}] Pull request marked as draft: #${pull_request.number} ${pull_request.title}`, 406 | pull_request["html_url"], 407 | sender, 408 | 10987431 409 | ); 410 | } 411 | 412 | function buildPullReopen(json: any): string { 413 | const { pull_request, repository, sender } = json; 414 | 415 | let draft = pull_request.draft; 416 | let color = draft ? 10987431 : 37378; 417 | let type = draft ? "Draft pull request" : "Pull request" 418 | 419 | return buildEmbedBody( 420 | `[${repository["full_name"]}] ${type} reopened: #${pull_request.number} ${pull_request.title}`, 421 | pull_request["html_url"], 422 | sender, 423 | color 424 | ); 425 | } 426 | 427 | function buildPullClose(json: any): string { 428 | const { pull_request, repository, sender } = json; 429 | 430 | let merged = pull_request.merged; 431 | let color = merged ? 8866047 : 16722234; 432 | let status = merged ? "merged" : "closed"; 433 | 434 | return buildEmbedBody( 435 | `[${repository["full_name"]}] Pull request ${status}: #${pull_request.number} ${pull_request.title}`, 436 | pull_request["html_url"], 437 | sender, 438 | color 439 | ); 440 | } 441 | 442 | function buildPull(json: any, env: BoundEnv): string | null { 443 | const { pull_request, repository, sender } = json; 444 | 445 | if (env.isIgnoredUser(sender.login)) { 446 | return null; 447 | } 448 | 449 | let draft = pull_request.draft; 450 | let color = draft ? 10987431 : 37378; 451 | let type = draft ? "Draft pull request" : "Pull request" 452 | 453 | return buildEmbedBody( 454 | `[${repository["full_name"]}] ${type} opened: #${pull_request.number} ${pull_request.title}`, 455 | pull_request["html_url"], 456 | sender, 457 | color, 458 | pull_request.body 459 | ); 460 | } 461 | 462 | function buildIssueComment(json: any, env: BoundEnv): string | null { 463 | const { issue, comment, repository, sender } = json; 464 | 465 | if (env.isIgnoredUser(sender.login)) { 466 | return null; 467 | } 468 | 469 | let entity = "pull_request" in issue ? "pull request" : "issue"; 470 | 471 | return buildEmbedBody( 472 | `[${repository["full_name"]}] New comment on ${entity}: #${issue.number} ${issue.title}`, 473 | comment["html_url"], 474 | sender, 475 | 11373312, 476 | comment.body 477 | ); 478 | } 479 | 480 | function buildIssueClose(json: any): string { 481 | const { issue, repository, sender } = json; 482 | 483 | const reason = issue.state_reason; 484 | 485 | return buildEmbedBody( 486 | `[${repository["full_name"]}] Issue closed ${reason ? `as ${reason.replaceAll('_', ' ')}` : ''}: #${issue.number} ${issue.title}`, 487 | issue["html_url"], 488 | sender, 489 | 16730159 490 | ); 491 | } 492 | 493 | function buildIssueReOpen(json: any): string { 494 | const { issue, repository, sender } = json; 495 | 496 | return buildEmbedBody( 497 | `[${repository["full_name"]}] Issue reopened: #${issue.number} ${issue.title}`, 498 | issue["html_url"], 499 | sender, 500 | 16743680 501 | ); 502 | } 503 | 504 | function buildIssue(json: any, env: BoundEnv): string | null { 505 | const { issue, repository, sender } = json; 506 | 507 | if (env.isIgnoredUser(sender.login)) { 508 | return null; 509 | } 510 | 511 | return buildEmbedBody( 512 | `[${repository["full_name"]}] Issue opened: #${issue.number} ${issue.title}`, 513 | issue["html_url"], 514 | sender, 515 | 16743680, 516 | issue.body 517 | ); 518 | } 519 | 520 | function buildPackagePublished(json: any): string { 521 | const { sender, repository } = json; 522 | const pkg = "package" in json ? json.package : json["registry_package"]; 523 | 524 | return buildEmbedBody( 525 | `[${repository["full_name"]}] Package Published: ${pkg.namespace}/${pkg.name}`, 526 | pkg["package_version"]["html_url"], 527 | sender, 528 | 37378 529 | ); 530 | } 531 | 532 | function buildPackageUpdated(json: any): string { 533 | const { sender, repository } = json; 534 | const pkg = "package" in json ? json.package : json["registry_package"]; 535 | 536 | return buildEmbedBody( 537 | `[${repository["full_name"]}] Package Updated: ${pkg.namespace}/${pkg.name}`, 538 | pkg["package_version"]["html_url"], 539 | sender, 540 | 37378 541 | ); 542 | } 543 | 544 | function buildFork(json: any): string { 545 | const { sender, repository, forkee } = json; 546 | 547 | return buildEmbedBody( 548 | `[${repository["full_name"]}] Fork Created: ${forkee["full_name"]}`, 549 | forkee["html_url"], 550 | sender, 551 | 16562432 552 | ); 553 | } 554 | 555 | function buildDiscussionComment(json: any, env: BoundEnv): string | null { 556 | const { discussion, comment, repository, sender } = json; 557 | const { category } = discussion; 558 | 559 | if (env.isIgnoredUser(sender.login)) { 560 | return null; 561 | } 562 | 563 | return buildEmbedBody( 564 | `[${repository["full_name"]}] New comment on discussion: #${discussion.number} ${discussion.title}`, 565 | comment["html_url"], 566 | sender, 567 | 35446, 568 | comment.body, 569 | `Discussion Category: ${category.name}` 570 | ); 571 | } 572 | 573 | function buildDiscussion(json: any, env: BoundEnv): string | null { 574 | const { discussion, repository, sender } = json; 575 | const { category } = discussion; 576 | 577 | if (env.isIgnoredUser(sender.login)) { 578 | return null; 579 | } 580 | 581 | return buildEmbedBody( 582 | `[${repository["full_name"]}] New discussion: #${discussion.number} ${discussion.title}`, 583 | discussion["html_url"], 584 | sender, 585 | 9737471, 586 | discussion.body, 587 | `Discussion Category: ${category.name}` 588 | ); 589 | } 590 | 591 | function buildDeleteBranch(json: any, env: BoundEnv): string | null { 592 | const { ref, ref_type, repository, sender } = json; 593 | 594 | if (ref_type == "branch" && env.isIgnoredBranch(ref)) { 595 | return null; 596 | } 597 | 598 | return buildEmbedBody( 599 | `[${repository["full_name"]}] ${ref_type} deleted: ${ref}`, 600 | undefined, 601 | sender, 602 | 1 603 | ); 604 | } 605 | 606 | function buildCreateBranch(json: any, env: BoundEnv): string | null { 607 | const { ref, ref_type, repository, sender } = json; 608 | 609 | if (env.isIgnoredUser(sender.login)) { 610 | return null; 611 | } 612 | 613 | if (ref_type == "branch" && env.isIgnoredBranch(ref)) { 614 | return null; 615 | } 616 | 617 | return buildEmbedBody( 618 | `[${repository["full_name"]}] New ${ref_type} created: ${ref}`, 619 | undefined, 620 | sender, 621 | 3881787 622 | ); 623 | } 624 | 625 | function buildCommitComment(json: any, env: BoundEnv): string | null { 626 | const { sender, comment, repository } = json; 627 | 628 | if (env.isIgnoredUser(sender.login)) { 629 | return null; 630 | } 631 | 632 | return buildEmbedBody( 633 | `[${repository["full_name"]}] New comment on commit \`${shortCommit(comment["commit_id"])}\``, 634 | comment["html_url"], 635 | sender, 636 | 3881787, 637 | comment.body 638 | ); 639 | } 640 | 641 | function buildCheck(json: any, env: BoundEnv): string | null { 642 | const { check_run, repository, sender } = json; 643 | const { conclusion, output, html_url, check_suite } = check_run; 644 | 645 | if (repository == null || check_suite["head_branch"] == null) { 646 | return null; 647 | } 648 | 649 | let target = check_suite["head_branch"]; 650 | 651 | if (env.isIgnoredBranch(target)) { 652 | return null; 653 | } 654 | 655 | if (check_suite["pull_requests"].length > 0) { 656 | let pull = check_suite["pull_requests"][0]; 657 | if (pull.url.startsWith(`https://api.github.com/repos/${repository["full_name"]}`)) { 658 | target = `PR #${pull.number}` 659 | } 660 | } 661 | 662 | let color = 11184810; 663 | let status = "failed" 664 | if (conclusion === "success") { 665 | color = 45866; 666 | status = "succeeded"; 667 | } else if (conclusion === "failure" || conclusion === "cancelled") { 668 | color = 16726843; 669 | status = conclusion === "failure" ? "failed" : "cancelled" 670 | } else if (conclusion === "timed_out" || conclusion === "action_required" || conclusion === "stale") { 671 | color = 14984995; 672 | status = conclusion === "timed_out" ? "timed out" : (conclusion === "action_required" ? "requires action" : "became stale"); 673 | } else if (conclusion === "neutral") { 674 | status = "didn't run"; 675 | } else if (conclusion === "skipped") { 676 | status = "was skipped"; 677 | } 678 | 679 | let fields = [ 680 | { 681 | name: "Action Name", 682 | value: check_run.name, 683 | inline: true 684 | } 685 | ]; 686 | 687 | if (output.title != null) { 688 | fields.push({ 689 | name: "Output Title", 690 | value: truncate(output.title, 1000), 691 | inline: true 692 | }); 693 | } 694 | 695 | if (output.summary != null) { 696 | fields.push({ 697 | name: "Output Summary", 698 | value: truncate(output.summary, 1000), 699 | inline: false 700 | }); 701 | } 702 | 703 | return buildEmbedBody( 704 | `[${repository["full_name"]}] Actions check ${status} on ${target}`, 705 | html_url, 706 | sender, 707 | color, 708 | undefined, 709 | undefined, 710 | fields 711 | ); 712 | } 713 | 714 | function buildStar(json: any): string { 715 | const { sender, repository } = json; 716 | 717 | return buildEmbedBody( 718 | `[${repository["full_name"]}] New star added`, 719 | repository["html_url"], 720 | sender, 721 | 16562432 722 | ); 723 | } 724 | 725 | function buildDeployment(json: any) { 726 | const { deployment, repository, sender } = json; 727 | const { description, payload } = deployment; 728 | 729 | return buildEmbedBody( 730 | `[${repository["full_name"]}] Deployment started for ${description}`, 731 | payload["web_url"] === null ? "" : payload["web_url"], 732 | sender, 733 | 11158713 734 | ); 735 | } 736 | 737 | function buildDeploymentStatus(json: any) { 738 | const { deployment, deployment_status, repository, sender } = json; 739 | const { description, payload } = deployment; 740 | const { state } = deployment_status; 741 | 742 | let color = 16726843; 743 | let term = "succeeded"; 744 | switch (state) { 745 | case "success": { 746 | color = 45866; 747 | break; 748 | } 749 | case "failure": { 750 | term = "failed" 751 | break; 752 | } 753 | case "error": { 754 | term = "errored" 755 | break; 756 | } 757 | default: { 758 | return null; 759 | } 760 | } 761 | 762 | return buildEmbedBody( 763 | `[${repository["full_name"]}] Deployment for ${description} ${term}`, 764 | payload["web_url"] === null ? "" : payload["web_url"], 765 | sender, 766 | color 767 | ); 768 | } 769 | 770 | function buildWiki(json: any): string | null { 771 | const { pages, sender, repository } = json; 772 | 773 | // Pages is always an array with several "actions". 774 | // Count the amount of "created" and "edited" actions and store the amount in a variable. 775 | // Also store the titles of the pages in an array since we will need them later. 776 | let created = 0; 777 | let edited = 0; 778 | let titles: string[] = []; 779 | for (let i = 0; i < pages.length; i++) { 780 | const { action } = pages[i]; 781 | if (action === "created") { 782 | created++; 783 | } else if (action === "edited") { 784 | edited++; 785 | } 786 | 787 | // Wrap the title in a markdown with the link to the page. 788 | let title = `[${pages[i].title}](${pages[i]["html_url"]})`; 789 | 790 | // Capitalize the first letter of the action, then prepend it to the title. 791 | titles.push(`${action.charAt(0).toUpperCase() + action.slice(1)}: ${title}`); 792 | } 793 | 794 | // If there are no pages, return null. 795 | if (created === 0 && edited === 0) { 796 | return null; 797 | } 798 | 799 | // Set the message based on if there are any created or edited pages. 800 | // If there are only 1 of one type, set the message to singular. 801 | // If there are multiple of one type, set the message to plural. 802 | let message; 803 | let color; 804 | if (created === 1 && edited === 0) { 805 | message = "A page was created"; 806 | // Set the color to green. 807 | color = 45866; 808 | } else if (created === 0 && edited === 1) { 809 | message = "A page was edited"; 810 | // Set the color to orange. 811 | color = 16562432; 812 | } else { 813 | if (created > 0 && edited > 0) { 814 | message = `${created} page${created > 1 ? "s" : ""} were created and ${edited} ${edited > 1 ? "were" : "was"} edited`; 815 | } else { 816 | message = `${Math.max(created, edited)} pages were ${created > 0 ? "created" : "edited"}`; 817 | } 818 | // Set the color to blue. 819 | color = 6120164; 820 | } 821 | 822 | // Prepend the repository title to the message. 823 | message = `[${repository["full_name"]}] ${message}`; 824 | 825 | // Build the embed, with the sender as the author, the message as the title, and the edited pages as the description. 826 | return buildEmbedBody( 827 | message, 828 | repository["html_url"], 829 | sender, 830 | color, 831 | titles.join("\n"), 832 | ); 833 | } 834 | 835 | function handleError(error: any): Response { 836 | console.error('Uncaught error:', error) 837 | 838 | const { stack } = error 839 | return new Response(stack || error, { 840 | status: 500, 841 | headers: { 842 | 'Content-Type': 'text/plain;charset=UTF-8' 843 | } 844 | }) 845 | } 846 | 847 | 848 | export default { 849 | async fetch( 850 | request: Request, 851 | env: Env, 852 | ctx: ExecutionContext 853 | ): Promise { 854 | const url = new URL(request.url) 855 | if (url.pathname === "/health") { 856 | return new Response("OK", { status: 200 }); 857 | } 858 | 859 | const bound = new BoundEnv(env); 860 | return handleRequest(request, bound).catch(handleError); 861 | }, 862 | }; 863 | -------------------------------------------------------------------------------- /src/md-truncate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * from https://github.com/pchiwan/markdown-truncate 3 | **/ 4 | 5 | export const ASTERISK_ITALIC = '*'; 6 | export const UNDERSCORE_ITALIC = '_'; 7 | export const ASTERISK_BOLD = '**'; 8 | export const UNDERSCORE_BOLD = '__'; 9 | export const BACKTICK = '`'; 10 | export const TRIPLE_BACKTICKS = '```'; 11 | export const ESCAPED_UNDERSCORE = '\\_'; 12 | export const ESCAPED_ASTERISK = '\\*'; 13 | 14 | export const ESCAPED_ASTERISK_REGEXP = /\\\*/g; 15 | export const ASTERISK_PLACEHOLDER_REGEXP = /ASTERISKPLACEHOLDER/gm; 16 | 17 | export const ESCAPED_UNDERSCORE_REGEXP = /\\_/g; 18 | export const UNDERSCORE_PLACEHOLDER_REGEXP = /UNDERSCOREPLACEHOLDER/gm; 19 | 20 | export const UNDERSCORE_BOLD_PLACEHOLDER_REGEXP = /UNDERSCOREBOLDPLACEHOLDER/gm; 21 | export const UNDERSCORE_BOLD_REGEXP = /(__)(.*?)(__)/g; 22 | 23 | export const ASTERISK_BOLD_PLACEHOLDER_REGEXP = /ASTERISKBOLDPLACEHOLDER/gm; 24 | export const ASTERISK_BOLD_REGEXP = /(\*\*)(.*?)(\*\*)/g; 25 | 26 | export const UNDERSCORE_ITALIC_PLACEHOLDER_REGEXP = /UNDERSCOREITALICPLACEHOLDER/gm; 27 | export const UNDERSCORE_ITALIC_REGEXP = /(_)(.*?)(_)/g; 28 | 29 | export const ASTERISK_ITALIC_PLACEHOLDER_REGEXP = /ASTERISKITALICPLACEHOLDER/gm; 30 | export const ASTERISK_ITALIC_REGEXP = /(\*)(.*?)(\*)/g; 31 | 32 | export const TRIPLE_BACKTICKS_PLACEHOLDER_REGEXP = /TRIPLEBACKTICKSPLACEHOLDER/gm; 33 | export const TRIPLE_BACKTICKS_REGEXP = /(```)(.*?)(```)/gs; 34 | 35 | export const BACKTICK_PLACEHOLDER_REGEXP = /BACKTICKPLACEHOLDER/gm; 36 | export const BACKTICK_REGEXP = /(`)(.*?)(`)/gs; 37 | 38 | const HYPERLINK = /^\[([^[]+)\]\(([^)]+)\)/; 39 | 40 | const replaceFormatMarkersWithPlaceholders = (text: string) => 41 | text 42 | .replace(ESCAPED_UNDERSCORE_REGEXP, UNDERSCORE_PLACEHOLDER_REGEXP.source) 43 | .replace(ESCAPED_ASTERISK_REGEXP, ASTERISK_PLACEHOLDER_REGEXP.source) 44 | .replace( 45 | UNDERSCORE_BOLD_REGEXP, 46 | `${UNDERSCORE_BOLD_PLACEHOLDER_REGEXP.source}$2${UNDERSCORE_BOLD_PLACEHOLDER_REGEXP.source}` 47 | ) 48 | .replace( 49 | ASTERISK_BOLD_REGEXP, 50 | `${ASTERISK_BOLD_PLACEHOLDER_REGEXP.source}$2${ASTERISK_BOLD_PLACEHOLDER_REGEXP.source}` 51 | ) 52 | .replace( 53 | UNDERSCORE_ITALIC_REGEXP, 54 | `${UNDERSCORE_ITALIC_PLACEHOLDER_REGEXP.source}$2${UNDERSCORE_ITALIC_PLACEHOLDER_REGEXP.source}` 55 | ) 56 | .replace( 57 | ASTERISK_ITALIC_REGEXP, 58 | `${ASTERISK_ITALIC_PLACEHOLDER_REGEXP.source}$2${ASTERISK_ITALIC_PLACEHOLDER_REGEXP.source}` 59 | ) 60 | .replace( 61 | TRIPLE_BACKTICKS_REGEXP, 62 | `${TRIPLE_BACKTICKS_PLACEHOLDER_REGEXP.source}$2${TRIPLE_BACKTICKS_PLACEHOLDER_REGEXP.source}` 63 | ) 64 | .replace( 65 | BACKTICK_REGEXP, 66 | `${BACKTICK_PLACEHOLDER_REGEXP.source}$2${BACKTICK_PLACEHOLDER_REGEXP.source}` 67 | ) 68 | 69 | const replaceFormatPlaceholdersWithMarkers = (text: string) => 70 | text 71 | .replace(UNDERSCORE_PLACEHOLDER_REGEXP, ESCAPED_UNDERSCORE) 72 | .replace(ASTERISK_PLACEHOLDER_REGEXP, ESCAPED_ASTERISK) 73 | .replace(UNDERSCORE_BOLD_PLACEHOLDER_REGEXP, UNDERSCORE_BOLD) 74 | .replace(ASTERISK_BOLD_PLACEHOLDER_REGEXP, ASTERISK_BOLD) 75 | .replace(UNDERSCORE_ITALIC_PLACEHOLDER_REGEXP, UNDERSCORE_ITALIC) 76 | .replace(ASTERISK_ITALIC_PLACEHOLDER_REGEXP, ASTERISK_ITALIC) 77 | .replace(TRIPLE_BACKTICKS_PLACEHOLDER_REGEXP, TRIPLE_BACKTICKS) 78 | .replace(BACKTICK_PLACEHOLDER_REGEXP, BACKTICK); 79 | 80 | const formatMarkers = [ 81 | ASTERISK_BOLD_PLACEHOLDER_REGEXP.source, 82 | UNDERSCORE_BOLD_PLACEHOLDER_REGEXP.source, 83 | ASTERISK_ITALIC_PLACEHOLDER_REGEXP.source, 84 | UNDERSCORE_ITALIC_PLACEHOLDER_REGEXP.source, 85 | BACKTICK_PLACEHOLDER_REGEXP.source, 86 | TRIPLE_BACKTICKS_PLACEHOLDER_REGEXP.source, 87 | ]; 88 | 89 | const formatPlaceholdersMap = { 90 | [UNDERSCORE_PLACEHOLDER_REGEXP.source]: ESCAPED_UNDERSCORE.length, 91 | [ASTERISK_PLACEHOLDER_REGEXP.source]: ESCAPED_ASTERISK.length, 92 | } 93 | 94 | const findFormatPlaceholderAhead = (text: string) => { 95 | const formatPlaceholders = Object.keys(formatPlaceholdersMap); 96 | 97 | for (let i = 0, l = formatPlaceholders.length; i < l; i++) { 98 | if (text.startsWith(formatPlaceholders[i])) { 99 | return formatPlaceholders[i]; 100 | } 101 | } 102 | 103 | return null; 104 | } 105 | 106 | const findFormatMarkerAhead = (text: string, formatStack: any[]) => { 107 | for (let i = 0, l = formatMarkers.length; i < l; i++) { 108 | if (text.startsWith(formatMarkers[i])) { 109 | if (formatStack[formatStack.length - 1] === formatMarkers[i]) { 110 | formatStack.pop(); 111 | } else { 112 | formatStack.push(formatMarkers[i]); 113 | } 114 | return formatMarkers[i]; 115 | } 116 | } 117 | 118 | return null; 119 | }; 120 | 121 | const truncate = (text: string, limit: number, ellipsis: boolean | undefined) => { 122 | let count = 0; 123 | 124 | const truncateString = (text: string) => { 125 | let formatStack: string[] = []; 126 | let skipCountIncrement = false; 127 | let outputText = ''; 128 | let index = 0; 129 | 130 | while (count < limit && index < text.length) { 131 | const formatMarker = findFormatMarkerAhead(text.substring(index), formatStack); 132 | if (formatMarker) { 133 | outputText += formatMarker; 134 | index += formatMarker.length; 135 | skipCountIncrement = true; 136 | } 137 | 138 | const formatPlaceholder = findFormatPlaceholderAhead(text.substring(index)); 139 | if (formatPlaceholder) { 140 | outputText += formatPlaceholder; 141 | index += formatPlaceholder.length; 142 | skipCountIncrement = true; 143 | count += formatPlaceholdersMap[formatPlaceholder]; 144 | } 145 | 146 | const hyperlinkAheadRegexp = new RegExp(HYPERLINK); 147 | const hyperlinkMatch = hyperlinkAheadRegexp.exec(text.substring(index)); 148 | if (hyperlinkMatch) { 149 | const hyperlinkText = hyperlinkMatch[1]; 150 | const hyperlinkUrl = hyperlinkMatch[2]; 151 | 152 | outputText += `[${truncateString(hyperlinkText)}](${hyperlinkUrl})`; 153 | index += hyperlinkMatch[0].length; 154 | skipCountIncrement = true; 155 | } 156 | 157 | if (!formatMarker && !hyperlinkMatch) { 158 | outputText += text[index]; 159 | index++; 160 | } 161 | 162 | if (!skipCountIncrement) { 163 | count++; 164 | } 165 | 166 | skipCountIncrement = false; 167 | } 168 | 169 | outputText = outputText.trimEnd(); 170 | 171 | while (formatStack.length > 0) { 172 | outputText += formatStack.pop(); 173 | } 174 | 175 | return outputText; 176 | }; 177 | 178 | let outputText = truncateString(text); 179 | 180 | if (ellipsis && outputText.length < text.length) { 181 | outputText += '...'; 182 | } 183 | 184 | return outputText; 185 | }; 186 | 187 | export interface MdTruncateOptions { 188 | limit?: number; 189 | ellipsis?: boolean; 190 | } 191 | 192 | export function mdTruncate(text: string = '', options: MdTruncateOptions = {}): string { 193 | const { limit, ellipsis } = options; 194 | 195 | if (isNaN(Number(limit)) || text.length <= (limit ?? 0)) { 196 | return text; 197 | } 198 | 199 | let outputText = replaceFormatMarkersWithPlaceholders(text); 200 | outputText = truncate(outputText, limit ?? 0, ellipsis); 201 | outputText = replaceFormatPlaceholdersWithMarkers(outputText); 202 | return outputText; 203 | } 204 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { mdTruncate } from "./md-truncate"; 2 | 3 | /** 4 | * @param {String} str 5 | * @param {Number} num 6 | * @return {string|null} 7 | */ 8 | export function truncate(str: string, num: number): string | null { 9 | if (str === null) { 10 | return null; 11 | } 12 | 13 | str = str.replace(/[\n|\r]*/g, ""); 14 | if (str.length <= num) { 15 | return str; 16 | } 17 | 18 | let truncatedStr = mdTruncate(str, { limit: num, ellipsis: true }); 19 | 20 | // mdTruncate doesn't count formatting markers, so we need to ensure the length is correct 21 | let trimNum = num; 22 | while (truncatedStr.length < num) { 23 | trimNum -= 10; 24 | truncatedStr = mdTruncate(str, { limit: num, ellipsis: true }); 25 | } 26 | 27 | return truncatedStr; 28 | } 29 | 30 | /** 31 | * @param {String} hash 32 | * @return {string} 33 | */ 34 | export function shortCommit(hash: string): string { 35 | return hash.substring(0, 7); 36 | } 37 | 38 | export async function validateRequest(request: Request, secret: string): Promise { 39 | const signatureHeader = request.headers.get("X-Hub-Signature-256")?.substring("sha256=".length); 40 | if (signatureHeader == null) { 41 | return false; 42 | } 43 | 44 | const encoder = new TextEncoder(); 45 | const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {name: "HMAC", hash: "SHA-256" }, false, ["verify"]); 46 | 47 | 48 | return crypto.subtle.verify("HMAC", key, encoder.encode(signatureHeader), await request.arrayBuffer()) 49 | } 50 | 51 | export type Sender = { 52 | login: string; 53 | html_url: string; 54 | avatar_url: string; 55 | } 56 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [[ $# -ne 2 ]]; then 6 | echo "Usage: $0 " 7 | exit 1 8 | fi 9 | 10 | echo "Running test with ID: $1 and Secret: $2" 11 | 12 | # Script directory, so it works no matter where you call it from 13 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 14 | 15 | HEADERS_FILE="$SCRIPT_DIR/headers.http" 16 | BODY_FILE="$SCRIPT_DIR/body.json" 17 | BASE_URL="http://localhost:8787" 18 | 19 | # Basic sanity‑checks 20 | [[ -f "$HEADERS_FILE" ]] || { echo "Missing $HEADERS_FILE"; exit 1; } 21 | [[ -f "$BODY_FILE" ]] || { echo "Missing $BODY_FILE"; exit 1; } 22 | 23 | curl -vv --show-error --fail \ 24 | -X POST \ 25 | -H @"$HEADERS_FILE" \ 26 | --data @"$BODY_FILE" \ 27 | "${BASE_URL}/$1/$2" 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "lib": ["esnext"], 6 | "jsx": "react", 7 | "types": ["@cloudflare/workers-types"], 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "allowJs": true, 11 | "checkJs": false, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "disgit" 2 | main = "src/index.ts" 3 | compatibility_date = "2025-01-01" 4 | 5 | workers_dev = true 6 | 7 | [vars] 8 | IGNORED_BRANCH_REGEX="" 9 | IGNORED_BRANCHES="" 10 | IGNORED_USERS="" 11 | IGNORED_PAYLOADS="" 12 | DEBUG_PASTE=false 13 | EXECUTE_MERGE_QUEUE_BRANCHES=false 14 | 15 | # Uses secrets: 16 | # - GITHUB_WEBHOOK_SECRET 17 | # Run `echo | wrangler secret put ` for each of these 18 | --------------------------------------------------------------------------------