diff --git a/.gitignore b/.gitignore index 06c3eac..f43d240 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ coverage/ dist/ +.DS_Store +.env diff --git a/packages/gh-prs-ready-need-review/.env.example b/packages/gh-prs-ready-need-review/.env.example new file mode 100644 index 0000000..3fe627e --- /dev/null +++ b/packages/gh-prs-ready-need-review/.env.example @@ -0,0 +1,16 @@ +# GitHub — fine-grained or classic PAT with repo read access. +# Fine-grained tokens need Pull requests + Issues (read), and Contents (read) for private repo search. +GITHUB_TOKEN= +# Repository to watch (owner/repo) +GITHUB_REPO=your-org/your-repo + +# Gather Smart Object webhook — open PRs you authored or are assigned to +GATHER_WEBHOOK_URL=https://api.v2.gather.town/api/v2/hooks/spaces/.../objects/... +GATHER_WEBHOOK_SECRET=whsec_... + +# Gather Smart Object webhook — open PRs that directly named you as reviewer, not yet approved (last 7 days) +GATHER_REVIEW_WEBHOOK_URL=https://api.v2.gather.town/api/v2/hooks/spaces/.../objects/... +GATHER_REVIEW_WEBHOOK_SECRET=whsec_... + +# How often to poll all registered sources (default: 10 minutes) +POLL_INTERVAL_MS=600000 diff --git a/packages/gh-prs-ready-need-review/README.md b/packages/gh-prs-ready-need-review/README.md new file mode 100644 index 0000000..48e542f --- /dev/null +++ b/packages/gh-prs-ready-need-review/README.md @@ -0,0 +1,42 @@ +# @webhook-objects/gh-prs-ready-need-review + +Polls GitHub for two PR metrics and mirrors each count to a Gather Smart Object `counter`: + +1. Open, ready-for-review PRs you authored or are assigned to (last 14 days) +2. Open PRs that directly named you as reviewer, not yet approved (last 7 days) + +Uses the GitHub REST search API (not the `gh` CLI). Configure via `.env` — see +`.env.example`. + +## Setup + +```sh +cp packages/gh-prs-ready-need-review/.env.example packages/gh-prs-ready-need-review/.env +# fill in GITHUB_TOKEN, GITHUB_REPO, and both Gather webhook URL/secret pairs +pnpm install --merge-git-branch-lockfiles +``` + +## Usage + +```sh +# probe both Smart Objects (webhook.ping) +pnpm --filter @webhook-objects/gh-prs-ready-need-review ping + +# clear legacy activity slots and reset counters (one-time migration) +pnpm --filter @webhook-objects/gh-prs-ready-need-review reset + +# poll GitHub and push counter.set to both objects (runs until Ctrl+C) +pnpm --filter @webhook-objects/gh-prs-ready-need-review start +``` + +| env var | required | default | meaning | +| --- | --- | --- | --- | +| `GITHUB_TOKEN` | yes | — | PAT with repo read access | +| `GITHUB_REPO` | yes | — | `owner/repo` to watch | +| `GATHER_WEBHOOK_URL` | yes | — | counter object for open PRs | +| `GATHER_WEBHOOK_SECRET` | yes | — | `whsec_…` for that object | +| `GATHER_REVIEW_WEBHOOK_URL` | yes | — | counter object for review requests | +| `GATHER_REVIEW_WEBHOOK_SECRET` | yes | — | `whsec_…` for that object | +| `POLL_INTERVAL_MS` | no | `600000` | poll interval (min 10000) | + +Events are signed and sent via [`@gathertown/webhook-object-sdk`](https://www.npmjs.com/package/@gathertown/webhook-object-sdk). diff --git a/packages/gh-prs-ready-need-review/package.json b/packages/gh-prs-ready-need-review/package.json new file mode 100644 index 0000000..1e63599 --- /dev/null +++ b/packages/gh-prs-ready-need-review/package.json @@ -0,0 +1,28 @@ +{ + "name": "@webhook-objects/gh-prs-ready-need-review", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Poll GitHub PR metrics and mirror counts to Gather Smart Object counters", + "bin": { + "gh-prs-ready-need-review": "src/index.ts" + }, + "scripts": { + "start": "tsx --env-file-if-exists=.env src/index.ts", + "ping": "tsx --env-file-if-exists=.env src/ping.ts", + "reset": "tsx --env-file-if-exists=.env src/reset.ts", + "test": "vitest run --coverage" + }, + "licenses": [ + { + "type": "Apache-2.0" + }, + { + "type": "MIT" + } + ], + "dependencies": { + "@gathertown/webhook-object-sdk": "^0.1.1", + "octokit": "^5.0.5" + } +} diff --git a/packages/gh-prs-ready-need-review/src/config.spec.ts b/packages/gh-prs-ready-need-review/src/config.spec.ts new file mode 100644 index 0000000..02ce922 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/config.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from "vitest"; +import { parseRepo } from "./config"; + +test("parseRepo accepts owner/repo", () => { + expect(parseRepo("acme/widget")).toEqual({ owner: "acme", name: "widget" }); +}); + +test("parseRepo rejects invalid shapes", () => { + expect(() => parseRepo("not-a-repo")).toThrow(/owner\/repo/); + expect(() => parseRepo("a/b/c")).toThrow(/owner\/repo/); +}); + +test("parseRepo trims whitespace", () => { + expect(parseRepo(" acme/widget ")).toEqual({ + owner: "acme", + name: "widget", + }); +}); diff --git a/packages/gh-prs-ready-need-review/src/config.ts b/packages/gh-prs-ready-need-review/src/config.ts new file mode 100644 index 0000000..e88d21d --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/config.ts @@ -0,0 +1,68 @@ +const WHSEC_PATTERN = /^whsec_.{20,}/; +const WEBHOOK_URL_PATTERN = + /^https:\/\/api\.v2\.(?:staging\.)?gather\.town\/api\/v2\/hooks\/spaces\/[0-9a-f-]{36}\/objects\/[0-9a-f-]{36}$/i; + +function required(name: string): string { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`Missing required env var: ${name}`); + return value; +} + +export function parseRepo(value: string): { owner: string; name: string } { + const match = value.trim().match(/^([^/]+)\/([^/]+)$/); + if (!match) { + throw new Error( + `GITHUB_REPO must be owner/repo (got ${JSON.stringify(value)})`, + ); + } + return { owner: match[1], name: match[2] }; +} + +function loadGatherWebhook(urlEnv: string, secretEnv: string) { + const secret = required(secretEnv); + if (!WHSEC_PATTERN.test(secret)) { + throw new Error(`${secretEnv} is missing or malformed (expected whsec_…)`); + } + + const url = required(urlEnv); + if (!WEBHOOK_URL_PATTERN.test(url)) { + throw new Error( + `${urlEnv} is invalid — copy the full URL from the Smart Object setup (spaces/{uuid}/objects/{uuid})`, + ); + } + + return { url, secret }; +} + +export type Config = { + gather: { + openPrs: { url: string; secret: string }; + reviewRequested: { url: string; secret: string }; + }; + github: { token: string; owner: string; repo: string }; + pollIntervalMs: number; +}; + +export function loadConfig(): Config { + const githubRepo = parseRepo(required("GITHUB_REPO")); + const pollIntervalMs = Number(process.env.POLL_INTERVAL_MS ?? "600000"); + if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 10_000) { + throw new Error("POLL_INTERVAL_MS must be a number >= 10000"); + } + + return { + gather: { + openPrs: loadGatherWebhook("GATHER_WEBHOOK_URL", "GATHER_WEBHOOK_SECRET"), + reviewRequested: loadGatherWebhook( + "GATHER_REVIEW_WEBHOOK_URL", + "GATHER_REVIEW_WEBHOOK_SECRET", + ), + }, + github: { + token: required("GITHUB_TOKEN"), + owner: githubRepo.owner, + repo: githubRepo.name, + }, + pollIntervalMs, + }; +} diff --git a/packages/gh-prs-ready-need-review/src/github/client.spec.ts b/packages/gh-prs-ready-need-review/src/github/client.spec.ts new file mode 100644 index 0000000..01abc8a --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/github/client.spec.ts @@ -0,0 +1,30 @@ +import { expect, test, vi } from "vitest"; +import { + cutoffSinceDate, + PR_CUTOFF_DAYS, + prCreatedSinceDate, + REVIEW_REQUEST_CUTOFF_DAYS, + reviewRequestSinceDate, +} from "./client"; + +test("cutoffSinceDate returns YYYY-MM-DD", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-07-01T12:00:00Z")); + + expect(cutoffSinceDate(7)).toBe("2026-06-24"); + expect(cutoffSinceDate(14)).toBe("2026-06-17"); + + vi.useRealTimers(); +}); + +test("PR search cutoffs use expected day windows", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-07-01T12:00:00Z")); + + expect(prCreatedSinceDate()).toBe(cutoffSinceDate(PR_CUTOFF_DAYS)); + expect(reviewRequestSinceDate()).toBe( + cutoffSinceDate(REVIEW_REQUEST_CUTOFF_DAYS), + ); + + vi.useRealTimers(); +}); diff --git a/packages/gh-prs-ready-need-review/src/github/client.ts b/packages/gh-prs-ready-need-review/src/github/client.ts new file mode 100644 index 0000000..ac3637c --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/github/client.ts @@ -0,0 +1,90 @@ +import { Octokit } from "octokit"; + +export const PR_CUTOFF_DAYS = 14; +export const REVIEW_REQUEST_CUTOFF_DAYS = 7; + +export type GitHubPullRequest = { + id: number; + number: number; + title: string; + html_url: string; +}; + +function createOctokit(token: string): Octokit { + return new Octokit({ auth: token }); +} + +export function cutoffSinceDate(days: number): string { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - days); + return cutoff.toISOString().slice(0, 10); +} + +/** YYYY-MM-DD for GitHub `created:>=` search qualifier */ +export function prCreatedSinceDate(): string { + return cutoffSinceDate(PR_CUTOFF_DAYS); +} + +/** YYYY-MM-DD — last week for review-requested PR search */ +export function reviewRequestSinceDate(): string { + return cutoffSinceDate(REVIEW_REQUEST_CUTOFF_DAYS); +} + +export async function fetchGitHubLogin(token: string): Promise { + const octokit = createOctokit(token); + const { data: user } = await octokit.rest.users.getAuthenticated(); + if (!user.login) { + throw new Error("GitHub /user response missing login"); + } + return user.login; +} + +export async function searchIssues( + token: string, + query: string, + { perPage = 100 }: { perPage?: number } = {}, +): Promise { + const octokit = createOctokit(token); + const { data } = await octokit.rest.search.issuesAndPullRequests({ + q: query, + per_page: perPage, + }); + return (data.items ?? []) as GitHubPullRequest[]; +} + +/** + * Open, ready-for-review PRs you opened or are assigned to. Search API rejects + * OR on user qualifiers, so merge author + assignee results and dedupe. + */ +export async function listOpenPrsForUser( + token: string, + { owner, repo, login }: { owner: string; repo: string; login: string }, +): Promise { + const createdSince = prCreatedSinceDate(); + const base = `repo:${owner}/${repo} is:pr is:open draft:false created:>=${createdSince}`; + const [authored, assigned] = await Promise.all([ + searchIssues(token, `${base} author:${login}`), + searchIssues(token, `${base} assignee:${login}`), + ]); + + const byId = new Map(); + for (const item of [...authored, ...assigned]) { + byId.set(item.id, item); + } + + return [...byId.values()].sort((a, b) => a.number - b.number); +} + +/** + * Open PRs that directly requested you as a reviewer (not via a team), not yet + * approved by anyone, created in the last week. + */ +export async function listOpenReviewRequestedPrsForUser( + token: string, + { owner, repo, login }: { owner: string; repo: string; login: string }, +): Promise { + const since = reviewRequestSinceDate(); + const query = `repo:${owner}/${repo} is:pr is:open user-review-requested:${login} -review:approved created:>=${since}`; + const items = await searchIssues(token, query); + return items.sort((a, b) => a.number - b.number); +} diff --git a/packages/gh-prs-ready-need-review/src/index.ts b/packages/gh-prs-ready-need-review/src/index.ts new file mode 100644 index 0000000..bfb90a4 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/index.ts @@ -0,0 +1,42 @@ +#!/usr/bin/env -S npx tsx +/** + * Poll GitHub for PR metrics and mirror counts to two Gather Smart Object + * counters — open PRs you authored/are assigned to, and unapproved review + * requests naming you directly. + * + * @module + */ +import { createWebhookObjectClient } from "@gathertown/webhook-object-sdk"; +import { loadConfig } from "./config"; +import { fetchGitHubLogin } from "./github/client"; +import { createPollEntries } from "./polls/registry"; +import { runPollCycle } from "./runner"; + +const config = loadConfig(); + +const pollEntries = createPollEntries({ + openPrs: createWebhookObjectClient(config.gather.openPrs), + reviewRequested: createWebhookObjectClient(config.gather.reviewRequested), +}); + +const login = await fetchGitHubLogin(config.github.token); +const ctx = { github: { ...config.github, login } }; + +async function tick() { + try { + await runPollCycle(ctx, pollEntries); + } catch (err) { + console.error("Poll cycle failed:", err); + } +} + +console.log( + `Starting gh-prs-ready-need-review as @${login} (poll every ${config.pollIntervalMs / 1000}s)`, +); + +const loop = async () => { + await tick(); + setTimeout(loop, config.pollIntervalMs); +}; + +loop(); diff --git a/packages/gh-prs-ready-need-review/src/ping.ts b/packages/gh-prs-ready-need-review/src/ping.ts new file mode 100644 index 0000000..0c30e83 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/ping.ts @@ -0,0 +1,19 @@ +import { createWebhookObjectClient } from "@gathertown/webhook-object-sdk"; +import { loadConfig } from "./config"; + +const config = loadConfig(); + +const targets = [ + { name: "open PRs (authored/assigned)", ...config.gather.openPrs }, + { + name: "unapproved direct review-requested PRs", + ...config.gather.reviewRequested, + }, +]; + +for (const { name, url, secret } of targets) { + const client = createWebhookObjectClient({ url, secret }); + const result = await client.ping(); + console.log(`\n${name}:`); + console.log(JSON.stringify(result, null, 2)); +} diff --git a/packages/gh-prs-ready-need-review/src/polls/github-pr-review-count.ts b/packages/gh-prs-ready-need-review/src/polls/github-pr-review-count.ts new file mode 100644 index 0000000..5df4431 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/polls/github-pr-review-count.ts @@ -0,0 +1,29 @@ +import { listOpenPrsForUser, prCreatedSinceDate } from "../github/client"; +import type { Poll, PollContext, PollResult } from "./types"; + +/** Count open, ready-for-review PRs you opened or are assigned to. */ +export const githubPrReviewCountPoll: Poll = async ( + ctx: PollContext, +): Promise => { + const { owner, repo, token, login } = ctx.github; + + const prs = await listOpenPrsForUser(token, { owner, repo, login }); + + console.log( + `[github-pr-review-count] ${prs.length} ready-for-review PR(s) since ${prCreatedSinceDate()} authored by or assigned to @${login}:`, + ); + if (prs.length === 0) { + console.log(" (none)"); + } else { + for (const pr of prs) { + console.log(` #${pr.number} ${pr.title}`); + console.log(` ${pr.html_url}`); + } + } + + return { + pollId: "github-pr-review-count", + label: "GitHub PR reviews", + value: prs.length, + }; +}; diff --git a/packages/gh-prs-ready-need-review/src/polls/github-review-requested-pr-count.ts b/packages/gh-prs-ready-need-review/src/polls/github-review-requested-pr-count.ts new file mode 100644 index 0000000..1717d1c --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/polls/github-review-requested-pr-count.ts @@ -0,0 +1,39 @@ +import { + listOpenReviewRequestedPrsForUser, + reviewRequestSinceDate, +} from "../github/client"; +import type { Poll, PollContext, PollResult } from "./types"; + +/** + * Open PRs that directly named you as a reviewer, not yet approved by anyone, + * in the last week. + */ +export const githubReviewRequestedPrCountPoll: Poll = async ( + ctx: PollContext, +): Promise => { + const { owner, repo, token, login } = ctx.github; + + const prs = await listOpenReviewRequestedPrsForUser(token, { + owner, + repo, + login, + }); + + console.log( + `[github-review-requested-count] ${prs.length} unapproved direct review request(s) since ${reviewRequestSinceDate()} for @${login}:`, + ); + if (prs.length === 0) { + console.log(" (none)"); + } else { + for (const pr of prs) { + console.log(` #${pr.number} ${pr.title}`); + console.log(` ${pr.html_url}`); + } + } + + return { + pollId: "github-review-requested-count", + label: "GitHub unapproved review-requested PRs", + value: prs.length, + }; +}; diff --git a/packages/gh-prs-ready-need-review/src/polls/registry.ts b/packages/gh-prs-ready-need-review/src/polls/registry.ts new file mode 100644 index 0000000..8b15c00 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/polls/registry.ts @@ -0,0 +1,29 @@ +import type { WebhookObjectClient } from "@gathertown/webhook-object-sdk"; +import { publishCounter } from "../publishers/gather-counter"; +import { githubPrReviewCountPoll } from "./github-pr-review-count"; +import { githubReviewRequestedPrCountPoll } from "./github-review-requested-pr-count"; +import type { Poll, PollResult } from "./types"; + +export type PollEntry = { + poll: Poll; + client: WebhookObjectClient; + publish: (client: WebhookObjectClient, result: PollResult) => Promise; +}; + +export function createPollEntries(senders: { + openPrs: WebhookObjectClient; + reviewRequested: WebhookObjectClient; +}): PollEntry[] { + return [ + { + poll: githubPrReviewCountPoll, + client: senders.openPrs, + publish: publishCounter, + }, + { + poll: githubReviewRequestedPrCountPoll, + client: senders.reviewRequested, + publish: publishCounter, + }, + ]; +} diff --git a/packages/gh-prs-ready-need-review/src/polls/types.ts b/packages/gh-prs-ready-need-review/src/polls/types.ts new file mode 100644 index 0000000..54c7714 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/polls/types.ts @@ -0,0 +1,11 @@ +export type PollContext = { + github: { owner: string; repo: string; token: string; login: string }; +}; + +export type PollResult = { + pollId: string; + label: string; + value: number; +}; + +export type Poll = (ctx: PollContext) => Promise; diff --git a/packages/gh-prs-ready-need-review/src/publishers/gather-counter.ts b/packages/gh-prs-ready-need-review/src/publishers/gather-counter.ts new file mode 100644 index 0000000..978253f --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/publishers/gather-counter.ts @@ -0,0 +1,11 @@ +import type { WebhookObjectClient } from "@gathertown/webhook-object-sdk"; +import type { PollResult } from "../polls/types"; + +/** Publish a poll count to a Smart Object via counter.set. */ +export async function publishCounter( + client: WebhookObjectClient, + result: PollResult, +): Promise { + const count = Math.max(0, Math.floor(result.value)); + await client.send("counter.set", { count }); +} diff --git a/packages/gh-prs-ready-need-review/src/reset.ts b/packages/gh-prs-ready-need-review/src/reset.ts new file mode 100644 index 0000000..e1ff2fc --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/reset.ts @@ -0,0 +1,40 @@ +import { createWebhookObjectClient } from "@gathertown/webhook-object-sdk"; +import { loadConfig } from "./config"; + +/** Activity slot ids from the pre-counter inbox publisher. */ +const LEGACY_ACTIVITY_SLOT_IDS = [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "github-pr-review-count", +]; + +const config = loadConfig(); + +const targets = [ + { name: "open PRs (authored/assigned)", ...config.gather.openPrs }, + { + name: "unapproved direct review-requested PRs", + ...config.gather.reviewRequested, + }, +]; + +for (const { name, url, secret } of targets) { + const client = createWebhookObjectClient({ url, secret }); + + for (const id of LEGACY_ACTIVITY_SLOT_IDS) { + await client.send("activity.remove", { id }); + } + + const result = await client.send("counter.reset"); + console.log( + `${name}: cleared ${LEGACY_ACTIVITY_SLOT_IDS.length} legacy slots + counter (${JSON.stringify(result)})`, + ); +} diff --git a/packages/gh-prs-ready-need-review/src/runner.ts b/packages/gh-prs-ready-need-review/src/runner.ts new file mode 100644 index 0000000..a6edb11 --- /dev/null +++ b/packages/gh-prs-ready-need-review/src/runner.ts @@ -0,0 +1,14 @@ +import type { PollEntry } from "./polls/registry"; +import type { PollContext } from "./polls/types"; + +/** Run every registered poll once and publish results to Gather. */ +export async function runPollCycle( + ctx: PollContext, + pollEntries: PollEntry[], +): Promise { + for (const { poll, publish, client } of pollEntries) { + const result = await poll(ctx); + console.log(`[${result.pollId}] ${result.label}: ${result.value}`); + await publish(client, result); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7bb14f..c5b6294 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,15 @@ importers: specifier: workspace:* version: link:../client + packages/gh-prs-ready-need-review: + dependencies: + '@gathertown/webhook-object-sdk': + specifier: ^0.1.1 + version: 0.1.1 + octokit: + specifier: ^5.0.5 + version: 5.0.5 + packages/low-battery-switch: dependencies: '@webhook-objects/client': @@ -333,6 +342,13 @@ packages: cpu: [x64] os: [win32] + '@gathertown/webhook-object-sdk@0.1.1': + resolution: {integrity: sha512-lKy/S8bA1Ux0+RAlQAEn6K5FCmQrdIF3QwE+31OBPUAsOqdFeUbb9xvrSuyus2+nqL/JOIQU3QFcFXuHxDLoiA==} + engines: {node: '>=18'} + + '@gathertown/webhook-object-types@0.1.1': + resolution: {integrity: sha512-jJBooZyBW/lRHwY7njRWjr2y1oaZEWBW719sZElV3ipv2a0EymR/AZZtW8jJrkVWP+OhuVSb7EUHVEE0dMb9mA==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -349,6 +365,113 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@octokit/app@16.1.2': + resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==} + engines: {node: '>= 20'} + + '@octokit/auth-app@8.2.0': + resolution: {integrity: sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-app@9.0.3': + resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-device@8.0.3': + resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-user@6.0.2': + resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} + engines: {node: '>= 20'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/auth-unauthenticated@7.0.3': + resolution: {integrity: sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/oauth-app@8.0.3': + resolution: {integrity: sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==} + engines: {node: '>= 20'} + + '@octokit/oauth-authorization-url@8.0.0': + resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-methods@6.0.2': + resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/openapi-webhooks-types@12.1.0': + resolution: {integrity: sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==} + + '@octokit/plugin-paginate-graphql@6.0.0': + resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.1.0': + resolution: {integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.3': + resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.10': + resolution: {integrity: sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@octokit/webhooks-methods@6.0.0': + resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==} + engines: {node: '>= 20'} + + '@octokit/webhooks@14.2.0': + resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} + engines: {node: '>= 20'} + '@oxc-project/types@0.137.0': resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} @@ -462,6 +585,9 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/aws-lambda@8.10.162': + resolution: {integrity: sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -534,6 +660,12 @@ packages: ast-v8-to-istanbul@1.0.4: resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -549,6 +681,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -628,6 +764,9 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + json-with-bigint@3.5.8: + resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -728,6 +867,10 @@ packages: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} + octokit@5.0.5: + resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==} + engines: {node: '>= 20'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -805,6 +948,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + toad-cache@3.7.1: + resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} + engines: {node: '>=20'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -829,6 +976,12 @@ packages: resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} engines: {node: '>=22.19.0'} + universal-github-app-jwt@2.2.2: + resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + vite-plugin-node@8.0.0: resolution: {integrity: sha512-/jz+hrOULqRfsOwSrA3xU0rm2ED/XLsUkQU3VhuJqaR/gvyGxb0ZRxsamh/U84DCpvIEhYkp2ZwgyDVQ+AmIRQ==} peerDependencies: @@ -1087,6 +1240,13 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@gathertown/webhook-object-sdk@0.1.1': + dependencies: + '@gathertown/webhook-object-types': 0.1.1 + standardwebhooks: 1.0.0 + + '@gathertown/webhook-object-types@0.1.1': {} + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -1103,6 +1263,154 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@octokit/app@16.1.2': + dependencies: + '@octokit/auth-app': 8.2.0 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + + '@octokit/auth-app@8.2.0': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.10 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + toad-cache: 3.7.1 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-app@9.0.3': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.10 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-device@8.0.3': + dependencies: + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.10 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-user@6.0.2': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.10 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/auth-unauthenticated@7.0.3': + dependencies: + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.10 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.3': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.10 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/oauth-app@8.0.3': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/oauth-methods': 6.0.2 + '@types/aws-lambda': 8.10.162 + universal-user-agent: 7.0.3 + + '@octokit/oauth-authorization-url@8.0.0': {} + + '@octokit/oauth-methods@6.0.2': + dependencies: + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/request': 10.0.10 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/openapi-webhooks-types@12.1.0': {} + + '@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.10': + dependencies: + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + content-type: 2.0.0 + json-with-bigint: 3.5.8 + universal-user-agent: 7.0.3 + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + + '@octokit/webhooks-methods@6.0.0': {} + + '@octokit/webhooks@14.2.0': + dependencies: + '@octokit/openapi-webhooks-types': 12.1.0 + '@octokit/request-error': 7.1.0 + '@octokit/webhooks-methods': 6.0.0 + '@oxc-project/types@0.137.0': {} '@polka/url@1.0.0-next.29': {} @@ -1167,6 +1475,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aws-lambda@8.10.162': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1279,6 +1589,10 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + before-after-hook@4.0.0: {} + + bottleneck@2.19.5: {} + chai@6.2.2: {} chalk@4.1.2: @@ -1292,6 +1606,8 @@ snapshots: color-name@1.1.4: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} debounce@2.2.0: {} @@ -1370,6 +1686,8 @@ snapshots: js-tokens@10.0.0: {} + json-with-bigint@3.5.8: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -1441,6 +1759,20 @@ snapshots: obug@2.1.3: {} + octokit@5.0.5: + dependencies: + '@octokit/app': 16.1.2 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/plugin-retry': 8.1.0(@octokit/core@7.0.6) + '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -1520,6 +1852,8 @@ snapshots: tinyrainbow@3.1.0: {} + toad-cache@3.7.1: {} + totalist@3.0.1: {} tslib@2.8.1: @@ -1537,6 +1871,10 @@ snapshots: undici@8.5.0: {} + universal-github-app-jwt@2.2.2: {} + + universal-user-agent@7.0.3: {} + vite-plugin-node@8.0.0(vite@8.1.0(@types/node@26.0.0)(esbuild@0.28.1)(tsx@4.22.4)): dependencies: chalk: 4.1.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fec54ef..bf128ae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,8 @@ minimumReleaseAge: 10080 minimumReleaseAgeExclude: - vite + - "@gathertown/webhook-object-sdk" + - "@gathertown/webhook-object-types" gitBranchLockfile: true enableGlobalVirtualStore: true mergeGitBranchLockfilesBranchPattern: