diff --git a/.changeset/nice-rocks-lay.md b/.changeset/nice-rocks-lay.md new file mode 100644 index 000000000..333398c33 --- /dev/null +++ b/.changeset/nice-rocks-lay.md @@ -0,0 +1,5 @@ +--- +'@hono/github-webhooks': major +--- + +GitHub Webhooks middleware that allows for listening to Webhooks in your Hono app. diff --git a/.github/workflows/ci-github-webhooks.yml b/.github/workflows/ci-github-webhooks.yml new file mode 100644 index 000000000..670c25d87 --- /dev/null +++ b/.github/workflows/ci-github-webhooks.yml @@ -0,0 +1,24 @@ +name: ci-github-webhooks +on: + push: + branches: [main] + paths: + - 'packages/github-webhooks/**' + pull_request: + branches: ['*'] + paths: + - 'packages/github-webhooks/**' + +jobs: + ci: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/github-webhooks + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.x + - run: yarn install --frozen-lockfile + - run: yarn prerelease diff --git a/package.json b/package.json index a3e75b17b..2c5905409 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "build:ajv-validator": "yarn workspace @hono/ajv-validator build", "build:tsyringe": "yarn workspace @hono/tsyringe build", "build:cloudflare-access": "yarn workspace @hono/cloudflare-access build", + "build:github-webhooks": "yarn workspace @hono/github-webhooks build", "build": "run-p 'build:*'", "lint": "eslint 'packages/**/*.{ts,tsx}'", "lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'", diff --git a/packages/github-webhooks/README.md b/packages/github-webhooks/README.md new file mode 100644 index 000000000..43e940c7e --- /dev/null +++ b/packages/github-webhooks/README.md @@ -0,0 +1,42 @@ +# GitHub Webhooks middleware for Hono + +Middleware that allows you to receive and process GitHub Webhooks events in your Hono application. +It conveniently validates the incoming requests and provides you with a simple API to handle the +events. + +## Installation + +```bash +npm i @hono/github-webhooks +``` + +## Usage + +> [!IMPORTANT] +> This middleware requires you to set the `GITHUB_WEBHOOK_SECRET` environment variable. This is the +> secret that GitHub uses to sign the payloads. + +```ts +import { Hono } from 'hono' +import { gitHubWebhooksMiddleware } from './dist' + +type Env = { + GITHUB_WEBHOOK_SECRET: string +} + +const app = new Hono<{ Bindings: Env }>() + +app.use('/webhook', gitHubWebhooksMiddleware()) + +app.post('/webhook', async (c) => { + const webhooks = c.get('webhooks') + + webhooks.on('star.created', async ({ id, name, payload }) => { + console.log(`Received ${name} event with id ${id} and payload: ${payload}`) + }) +}) +``` + +> [!TIP] +> This middleware builds upon the [GitHub Octokit Webhooks](https://github.com/octokit/webhooks.js) +> library. You can find the list of events and their payloads in the library's documentation. diff --git a/packages/github-webhooks/package.json b/packages/github-webhooks/package.json new file mode 100644 index 000000000..3c9b2e6b1 --- /dev/null +++ b/packages/github-webhooks/package.json @@ -0,0 +1,58 @@ +{ + "name": "@hono/github-webhooks", + "version": "1.0.0", + "packageManager": "yarn@4.0.2", + "description": "Hono middleware that listens for GitHub Webhooks", + "author": "oscarvz (https://github.com/oscarvz)", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "test": "vitest --run", + "build": "tsup ./src/index.ts --format esm,cjs --dts", + "prerelease": "yarn build && yarn test", + "release": "yarn publish" + }, + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "license": "MIT", + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/honojs/middleware.git" + }, + "homepage": "https://github.com/honojs/middleware", + "engines": { + "node": ">=18" + }, + "dependencies": { + "@octokit/webhooks": "^13.4.1" + }, + "devDependencies": { + "@octokit/types": "^13.6.2", + "@octokit/webhooks-methods": "^5.1.0", + "hono": "^4.6.13", + "tsup": "^8.3.5", + "vitest": "^2.1.8" + }, + "peerDependencies": { + "hono": "*" + } +} diff --git a/packages/github-webhooks/src/index.ts b/packages/github-webhooks/src/index.ts new file mode 100644 index 000000000..4c38b4152 --- /dev/null +++ b/packages/github-webhooks/src/index.ts @@ -0,0 +1,81 @@ +import { Webhooks } from '@octokit/webhooks'; +import type { Options, WebhookEventName } from '@octokit/webhooks/dist-types/types'; +import { } from 'hono'; +import { env } from 'hono/adapter'; +import { createMiddleware } from 'hono/factory'; + +export type GitHubWebhooksEnv = { + GITHUB_WEBHOOK_SECRET: string +} + +declare module 'hono' { + interface ContextVariableMap { + webhooks: InstanceType + } +} + +/** + * Middleware to receive & validate GitHub webhook requests by verifying their signatures. It + * exposes the `webhooks` instance in the context variable map, and allows you to listen to specific + * events using the `webhooks.on`, `webhooks.onAny`, or `webhooks.onError` methods. + * + * @see [Octokit Webhooks documentation](https://github.com/octokit/webhooks.js) + * + * The webhooks instance can be accessed via `c.get('webhooks')` in the route handler. + * + * @example + * type Env = { + * GITHUB_WEBHOOK_SECRET: string + * } + * + * const app = new Hono<{ Bindings: Env }>() + * + * app.use("/webhook", GitHubWebhooksMiddleware()) + * + * app.post("/webhook", async (c) => { + * const webhooks = c.get("webhooks") + * + * webhooks.on("star.created", async ({ id, name, payload }) => { + * console.log(`Received ${name} event with id ${id} and payload: ${payload}`) + * }) + * }) + */ +export const gitHubWebhooksMiddleware = (options?: Options) => + createMiddleware(async (c, next) => { + const { GITHUB_WEBHOOK_SECRET } = env(c) + const { secret, ...rest } = options || { + secret: GITHUB_WEBHOOK_SECRET, + } + + if (!secret) { + return c.text('Missing GitHub Webhook secret key', 403) + } + + const webhooks = new Webhooks({ secret, ...rest }) + + c.set('webhooks', webhooks) + + await next() + + const id = c.req.header('x-github-delivery') + const signature = c.req.header('x-hub-signature-256') + const name = c.req.header('x-github-event') as WebhookEventName | undefined + + if (!(id && name && signature)) { + return c.text('Invalid webhook request', 403) + } + + const payload = await c.req.text() + + try { + await webhooks.verifyAndReceive({ + id, + name, + signature, + payload, + }) + return c.text('Webhook received & verified', 201) + } catch (error) { + return c.text(`Failed to verify GitHub Webhook request: ${error}`, 400) + } + }) diff --git a/packages/github-webhooks/test/index.test.ts b/packages/github-webhooks/test/index.test.ts new file mode 100644 index 000000000..02227ccf1 --- /dev/null +++ b/packages/github-webhooks/test/index.test.ts @@ -0,0 +1,116 @@ +import { sign } from '@octokit/webhooks-methods' +import { Hono } from 'hono' +import { describe, expect, test, vi } from 'vitest' + +import { gitHubWebhooksMiddleware } from '../src' + +const ENV_VARS = { + GITHUB_WEBHOOK_SECRET: 'GITHUB_WEBHOOK_SECRET', +} + +describe('GitHub Webhooks Middleware', () => { + test('rejects incoming request when the GitHub Webhook secret key is missing', async () => { + const app = new Hono() + + app.use('/webhook', gitHubWebhooksMiddleware()) + + const res = await app.request('/webhook', { + method: 'POST', + }) + + expect(res.status).toBe(403) + }) + + test('rejects incoming request when the signed secret is incorrect', async () => { + const app = new Hono() + + app.use('/webhook', gitHubWebhooksMiddleware({ secret: ENV_VARS.GITHUB_WEBHOOK_SECRET })) + + const FAULTY_SIGNATURE = 'sha256=faulty-signature' + + const res = await app.request('/webhook', { + method: 'POST', + headers: { + 'x-github-delivery': 'random-id-assigned-by-github', + 'x-hub-signature-256': FAULTY_SIGNATURE, + 'x-github-event': 'star', + 'content-type': 'application/json', + }, + body: JSON.stringify({ action: 'created' }), + }) + + expect(res.status).toBe(400) + }) + + test("webhooks.on('star.created') is called when repo star payload is verified", async () => { + // Simple payload for testing purposes + const body = JSON.stringify({ + action: 'created', + }) + + // We sign the payload with the secret to simulate a real GitHub webhook request + const signature = await sign(ENV_VARS.GITHUB_WEBHOOK_SECRET, body) + const starCreationHandler = vi.fn() + const irrelevantHandler = vi.fn() + + const app = new Hono() + + app.use('/webhook', gitHubWebhooksMiddleware({ secret: ENV_VARS.GITHUB_WEBHOOK_SECRET })) + + app.post('/webhook', async (c) => { + const webhooks = c.var.webhooks + webhooks.on('star.created', starCreationHandler) + webhooks.on('discussion.created', irrelevantHandler) + }) + + const res = await app.request('/webhook', { + method: 'POST', + headers: { + 'x-github-delivery': 'random-id-assigned-by-github', + 'x-hub-signature-256': signature, + 'x-github-event': 'star', + 'content-type': 'application/json', + }, + body: JSON.stringify({ action: 'created' }), + }) + + expect(res.status).toBe(201) + expect(starCreationHandler).toHaveBeenCalledOnce() + expect(irrelevantHandler).not.toHaveBeenCalled() + }) + + test("webhooks.on('issues.opened') is called when repo issue payload is verified", async () => { + const body = JSON.stringify({ + action: 'opened', + }) + + const signature = await sign(ENV_VARS.GITHUB_WEBHOOK_SECRET, body) + const issuesOpenedHandler = vi.fn() + const irrelevantHandler = vi.fn() + + const app = new Hono() + + app.use('/webhook', gitHubWebhooksMiddleware({ secret: ENV_VARS.GITHUB_WEBHOOK_SECRET })) + + app.post('/webhook', async (c) => { + const webhooks = c.var.webhooks + webhooks.on('issues.opened', issuesOpenedHandler) + webhooks.on('project.closed', irrelevantHandler) + }) + + const res = await app.request('/webhook', { + method: 'POST', + headers: { + 'x-github-delivery': 'random-id-assigned-by-github', + 'x-hub-signature-256': signature, + 'x-github-event': 'issues', + 'content-type': 'application/json', + }, + body: JSON.stringify({ action: 'opened' }), + }) + + expect(res.status).toBe(201) + expect(issuesOpenedHandler).toHaveBeenCalledOnce() + expect(irrelevantHandler).not.toHaveBeenCalled() + }) +}) diff --git a/packages/github-webhooks/test/tsconfig.json b/packages/github-webhooks/test/tsconfig.json new file mode 100644 index 000000000..e7c45ea99 --- /dev/null +++ b/packages/github-webhooks/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "module": "Preserve", + "moduleResolution": "bundler" + }, + "include": ["./*.test.ts"], +} diff --git a/yarn.lock b/yarn.lock index a5d5d156e..e3dd6805b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2618,6 +2618,21 @@ __metadata: languageName: unknown linkType: soft +"@hono/github-webhooks@workspace:packages/github-webhooks": + version: 0.0.0-use.local + resolution: "@hono/github-webhooks@workspace:packages/github-webhooks" + dependencies: + "@octokit/types": "npm:^13.6.2" + "@octokit/webhooks": "npm:^13.4.1" + "@octokit/webhooks-methods": "npm:^5.1.0" + hono: "npm:^4.6.13" + tsup: "npm:^8.3.5" + vitest: "npm:^2.1.8" + peerDependencies: + hono: "*" + languageName: unknown + linkType: soft + "@hono/graphql-server@workspace:packages/graphql-server": version: 0.0.0-use.local resolution: "@hono/graphql-server@workspace:packages/graphql-server" @@ -3723,6 +3738,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -4092,6 +4114,56 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^22.2.0": + version: 22.2.0 + resolution: "@octokit/openapi-types@npm:22.2.0" + checksum: a45bfc735611e836df0729f5922bbd5811d401052b972d1e3bc1278a2d2403e00f4552ce9d1f2793f77f167d212da559c5cb9f1b02c935114ad6d898779546ee + languageName: node + linkType: hard + +"@octokit/openapi-webhooks-types@npm:8.5.1": + version: 8.5.1 + resolution: "@octokit/openapi-webhooks-types@npm:8.5.1" + checksum: 56c57fc6f9209ddb95e95034ee167e42d3bd68d5bb8286864d6f751ab0f2a6859614b4b0a98a85aa131dd9aa4522245b6dc5065e820f80f37fe791fac651244f + languageName: node + linkType: hard + +"@octokit/request-error@npm:^6.0.1": + version: 6.1.5 + resolution: "@octokit/request-error@npm:6.1.5" + dependencies: + "@octokit/types": "npm:^13.0.0" + checksum: 37afef6c072d987ddf50b3438bcc974741a22ee7f788172876f92b5228ed43f5c4c1556a1d73153508d6c8d3a3d2344c7fefb6cde8678c7f63c2115b8629c49b + languageName: node + linkType: hard + +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.6.2": + version: 13.6.2 + resolution: "@octokit/types@npm:13.6.2" + dependencies: + "@octokit/openapi-types": "npm:^22.2.0" + checksum: ea51afb21b667b25dad9e5daae1701da1b362a4d6ed9609f6d3f9f219e5389bf50f7e53ae029ca190750e278be3ab963cac648a95ad248f245a5fda16a4f1ed1 + languageName: node + linkType: hard + +"@octokit/webhooks-methods@npm:^5.0.0, @octokit/webhooks-methods@npm:^5.1.0": + version: 5.1.0 + resolution: "@octokit/webhooks-methods@npm:5.1.0" + checksum: 8f59f960c44ed9bdecd0f4a71ec6a50d490e9b46aa588887df8a69b15722262ce4cc02f0c1915cc145b452fd5b1e38e4b4ab0fb1d92e151308da550a7dfc1f6a + languageName: node + linkType: hard + +"@octokit/webhooks@npm:^13.4.1": + version: 13.4.1 + resolution: "@octokit/webhooks@npm:13.4.1" + dependencies: + "@octokit/openapi-webhooks-types": "npm:8.5.1" + "@octokit/request-error": "npm:^6.0.1" + "@octokit/webhooks-methods": "npm:^5.0.0" + checksum: 59a917d52874bc318546d17f3ee3e94abd1658437eb69bf5af55a605a39832bcff321e075d1dae1feee57ed6df5511403a4e389e774dec82f6a5a6e69fcf6d11 + languageName: node + linkType: hard + "@open-draft/deferred-promise@npm:^2.2.0": version: 2.2.0 resolution: "@open-draft/deferred-promise@npm:2.2.0" @@ -5674,6 +5746,37 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/expect@npm:2.1.8" + dependencies: + "@vitest/spy": "npm:2.1.8" + "@vitest/utils": "npm:2.1.8" + chai: "npm:^5.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: 6fbf4abc2360efe4d3671d3425f8bb6012fe2dd932a88720d8b793030b766ba260494822c721d3fc497afe52373515c7e150635a95c25f6e1b567f86155c5408 + languageName: node + linkType: hard + +"@vitest/mocker@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/mocker@npm:2.1.8" + dependencies: + "@vitest/spy": "npm:2.1.8" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.12" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: b4113ed8a57c0f60101d02e1b1769357a346ecd55ded499eab384d52106fd4b12d51e9aaa6db98f47de0d56662477be0ed8d46d6dfa84c235f9e1b234709814e + languageName: node + linkType: hard + "@vitest/pretty-format@npm:2.0.4, @vitest/pretty-format@npm:^2.0.4": version: 2.0.4 resolution: "@vitest/pretty-format@npm:2.0.4" @@ -5683,6 +5786,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:2.1.8, @vitest/pretty-format@npm:^2.1.8": + version: 2.1.8 + resolution: "@vitest/pretty-format@npm:2.1.8" + dependencies: + tinyrainbow: "npm:^1.2.0" + checksum: 1dc5c9b1c7c7e78e46a2a16033b6b20be05958bbebc5a5b78f29e32718c80252034804fccd23f34db6b3583239db47e68fc5a8e41942c54b8047cc3b4133a052 + languageName: node + linkType: hard + "@vitest/runner@npm:0.34.6": version: 0.34.6 resolution: "@vitest/runner@npm:0.34.6" @@ -5769,6 +5881,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/runner@npm:2.1.8" + dependencies: + "@vitest/utils": "npm:2.1.8" + pathe: "npm:^1.1.2" + checksum: d0826a71494adeafc8c6478257f584d11655145c83e2d8f94c17301d7059c7463ad768a69379e394c50838a7435abcc9255a6b7d8894f5ee06b153e314683a75 + languageName: node + linkType: hard + "@vitest/snapshot@npm:0.34.6": version: 0.34.6 resolution: "@vitest/snapshot@npm:0.34.6" @@ -5857,6 +5979,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/snapshot@npm:2.1.8" + dependencies: + "@vitest/pretty-format": "npm:2.1.8" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + checksum: 8d7a77a52e128630ea737ee0a0fe746d1d325cac5848326861dbf042844da4d5c1a5145539ae0ed1a3f0b0363506e98d86f2679fadf114ec4b987f1eb616867b + languageName: node + linkType: hard + "@vitest/spy@npm:0.34.6": version: 0.34.6 resolution: "@vitest/spy@npm:0.34.6" @@ -5929,6 +6062,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/spy@npm:2.1.8" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 9740f10772ede004ea7f9ffb8a6c3011341d75d9d7f2d4d181b123a701c4691e942f38cf1700684a3bb5eea3c78addf753fd8cdf78c51d8eadc3bada6fadf8f2 + languageName: node + linkType: hard + "@vitest/utils@npm:0.34.6": version: 0.34.6 resolution: "@vitest/utils@npm:0.34.6" @@ -6023,6 +6165,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:2.1.8": + version: 2.1.8 + resolution: "@vitest/utils@npm:2.1.8" + dependencies: + "@vitest/pretty-format": "npm:2.1.8" + loupe: "npm:^3.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: d4a29ecd8f6c24c790e4c009f313a044d89e664e331bc9c3cfb57fe1380fb1d2999706dbbfc291f067d6c489602e76d00435309fbc906197c0d01f831ca17d64 + languageName: node + linkType: hard + "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -7262,6 +7415,19 @@ __metadata: languageName: node linkType: hard +"chai@npm:^5.1.2": + version: 5.1.2 + resolution: "chai@npm:5.1.2" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 6c04ff8495b6e535df9c1b062b6b094828454e9a3c9493393e55b2f4dbff7aa2a29a4645133cad160fb00a16196c4dc03dc9bb37e1f4ba9df3b5f50d7533a736 + languageName: node + linkType: hard + "chalk@npm:^1.0.0, chalk@npm:^1.1.3": version: 1.1.3 resolution: "chalk@npm:1.1.3" @@ -8797,6 +8963,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.5.4": + version: 1.5.4 + resolution: "es-module-lexer@npm:1.5.4" + checksum: 300a469488c2f22081df1e4c8398c78db92358496e639b0df7f89ac6455462aaf5d8893939087c1a1cbcbf20eed4610c70e0bcb8f3e4b0d80a5d2611c539408c + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.2 resolution: "es-set-tostringtag@npm:2.0.2" @@ -10069,6 +10242,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.1.0": + version: 1.1.0 + resolution: "expect-type@npm:1.1.0" + checksum: 5af0febbe8fe18da05a6d51e3677adafd75213512285408156b368ca471252565d5ca6e59e4bddab25121f3cfcbbebc6a5489f8cc9db131cc29e69dcdcc7ae15 + languageName: node + linkType: hard + "expect@npm:^28.0.0, expect@npm:^28.1.3": version: 28.1.3 resolution: "expect@npm:28.1.3" @@ -11507,6 +11687,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:^4.6.13": + version: 4.6.13 + resolution: "hono@npm:4.6.13" + checksum: 8bf6ed856e3204d8dadc74cddc0f5a8e99e78a12df00e5b57f4d6c416ad7f808e6140688a30606c7d4b2a585b0ad085b7ed8be419f13a4b58e29dfdd54cfdac8 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -14308,6 +14495,13 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^3.1.2": + version: 3.1.2 + resolution: "loupe@npm:3.1.2" + checksum: b13c02e3ddd6a9d5f8bf84133b3242de556512d824dddeea71cce2dbd6579c8f4d672381c4e742d45cf4423d0701765b4a6e5fbc24701def16bc2b40f8daa96a + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -14400,6 +14594,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.12": + version: 0.30.15 + resolution: "magic-string@npm:0.30.15" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 7d10403cb0b403c0453d7af57d8d01a58c334b260e64653c5f5c2311800f4c6b1b7f4502153f9051dd8a87116acd50e5e3fce4bf79ec9d7127f087aa1c08b96b + languageName: node + linkType: hard + "make-dir@npm:^3.0.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -18614,6 +18817,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.8.0": + version: 3.8.0 + resolution: "std-env@npm:3.8.0" + checksum: f560a2902fd0fa3d648d7d0acecbd19d664006f7372c1fba197ed4c216b4c9e48db6e2769b5fe1616d42a9333c9f066c5011935035e85c59f45dc4f796272040 + languageName: node + linkType: hard + "stoppable@npm:^1.1.0": version: 1.1.0 resolution: "stoppable@npm:1.1.0" @@ -19213,6 +19423,13 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + "tinyexec@npm:^0.3.1": version: 0.3.1 resolution: "tinyexec@npm:0.3.1" @@ -19275,6 +19492,13 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^1.0.1": + version: 1.0.2 + resolution: "tinypool@npm:1.0.2" + checksum: 31ac184c0ff1cf9a074741254fe9ea6de95026749eb2b8ec6fd2b9d8ca94abdccda731f8e102e7f32e72ed3b36d32c6975fd5f5523df3f1b6de6c3d8dfd95e63 + languageName: node + linkType: hard + "tinyrainbow@npm:^1.2.0": version: 1.2.0 resolution: "tinyrainbow@npm:1.2.0" @@ -19296,6 +19520,13 @@ __metadata: languageName: node linkType: hard +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -20760,6 +20991,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:2.1.8": + version: 2.1.8 + resolution: "vite-node@npm:2.1.8" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.7" + es-module-lexer: "npm:^1.5.4" + pathe: "npm:^1.1.2" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: cb28027a7425ba29780e216164c07d36a4ff9eb60d83afcad3bc222fd5a5f3e36030071c819edd6d910940f502d49e52f7564743617bc1c5875485b0952c72d5 + languageName: node + linkType: hard + "vite@npm:^3.0.0 || ^4.0.0 || ^5.0.0-0, vite@npm:^3.1.0 || ^4.0.0 || ^5.0.0-0, vite@npm:^5.0.0": version: 5.0.10 resolution: "vite@npm:5.0.10" @@ -21248,6 +21494,56 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^2.1.8": + version: 2.1.8 + resolution: "vitest@npm:2.1.8" + dependencies: + "@vitest/expect": "npm:2.1.8" + "@vitest/mocker": "npm:2.1.8" + "@vitest/pretty-format": "npm:^2.1.8" + "@vitest/runner": "npm:2.1.8" + "@vitest/snapshot": "npm:2.1.8" + "@vitest/spy": "npm:2.1.8" + "@vitest/utils": "npm:2.1.8" + chai: "npm:^5.1.2" + debug: "npm:^4.3.7" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.1" + tinypool: "npm:^1.0.1" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + vite-node: "npm:2.1.8" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.1.8 + "@vitest/ui": 2.1.8 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: e70631bad5662d6c60c5cf836a4baf58b890db6654fef1f608fe6a86aa49a2b9f078aac74b719d4d3c87c5c781968cc73590a7935277b48f3d8b6fb9c5b4d276 + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8"