wip: github integration

This commit is contained in:
Steve Korshakov 2025-08-26 20:08:13 -07:00
parent 57d183d652
commit 941c0ef71e
12 changed files with 630 additions and 34 deletions

View File

@ -62,6 +62,8 @@ spec:
key: /handy-db
- extract:
key: /handy-master
- extract:
key: /handy-github
---
apiVersion: v1
kind: Service

View File

@ -11,6 +11,7 @@
"dev": "lsof -ti tcp:3005 | xargs kill -9 && tsx --env-file=.env --env-file=.env.dev ./sources/main.ts",
"test": "vitest run",
"migrate": "dotenv -e .env.dev -- prisma migrate dev",
"migrate:reset": "dotenv -e .env.dev -- prisma migrate reset",
"generate": "prisma generate",
"postinstall": "prisma generate",
"db": "docker run -d -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=handy -v $(pwd)/.pgdata:/var/lib/postgresql/data -p 5432:5432 postgres",
@ -43,6 +44,7 @@
"fastify-type-provider-zod": "^4.0.2",
"ioredis": "^5.6.1",
"jsonwebtoken": "^9.0.2",
"octokit": "^5.0.3",
"pino-pretty": "^13.0.0",
"prisma": "^6.11.1",
"prisma-json-types-generator": "^3.5.1",

View File

@ -0,0 +1,26 @@
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "githubUserId" TEXT;
-- CreateTable
CREATE TABLE "GithubUser" (
"id" TEXT NOT NULL,
"profile" JSONB NOT NULL,
"token" BYTEA,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "GithubUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GithubOrganization" (
"id" TEXT NOT NULL,
"profile" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "GithubOrganization_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_githubUserId_fkey" FOREIGN KEY ("githubUserId") REFERENCES "GithubUser"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -20,13 +20,16 @@ datasource db {
//
model Account {
id String @id @default(cuid())
publicKey String @unique
seq Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
settings String?
settingsVersion Int @default(0)
id String @id @default(cuid())
publicKey String @unique
seq Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
settings String?
settingsVersion Int @default(0)
githubUserId String?
githubUser GithubUser? @relation(fields: [githubUserId], references: [id])
Session Session[]
AccountPushToken AccountPushToken[]
TerminalAuthRequest TerminalAuthRequest[]
@ -106,6 +109,28 @@ model SessionMessage {
@@index([sessionId, seq])
}
//
// Github
//
model GithubUser {
id String @id
/// [GitHubProfile]
profile Json
token Bytes?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Account Account[]
}
model GithubOrganization {
id String @id
/// [GitHubOrg]
profile Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
//
// Utility
//

View File

@ -12,11 +12,11 @@ import { allocateSessionSeq, allocateUserSeq } from "@/services/seq";
import { randomKeyNaked } from "@/utils/randomKeyNaked";
import { AsyncLock } from "@/utils/lock";
import { auth } from "@/modules/auth";
import {
EventRouter,
ClientConnection,
SessionScopedConnection,
UserScopedConnection,
import {
EventRouter,
ClientConnection,
SessionScopedConnection,
UserScopedConnection,
MachineScopedConnection,
RecipientFilter,
buildNewSessionUpdate,
@ -29,16 +29,18 @@ import {
buildUsageEphemeral,
buildMachineStatusEphemeral
} from "@/modules/eventRouter";
import {
incrementWebSocketConnection,
decrementWebSocketConnection,
sessionAliveEventsCounter,
import {
incrementWebSocketConnection,
decrementWebSocketConnection,
sessionAliveEventsCounter,
machineAliveEventsCounter,
websocketEventsCounter,
httpRequestsCounter,
httpRequestDurationHistogram
} from "@/modules/metrics";
import { activityCache } from "@/modules/sessionCache";
import { encryptBytes, encryptString } from "@/modules/encrypt";
import { GitHubProfile } from "./types";
declare module 'fastify' {
@ -88,7 +90,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
// Increment request counter
httpRequestsCounter.inc({ method, route, status });
// Record request duration
httpRequestDurationHistogram.observe({ method, route, status }, duration);
});
@ -239,6 +241,143 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
return reply.send({ success: true });
});
// GitHub OAuth parameters
typed.get('/v1/connect/github/params', {
preHandler: app.authenticate,
schema: {
response: {
200: z.object({
url: z.string()
}),
400: z.object({
error: z.string()
}),
500: z.object({
error: z.string()
})
}
}
}, async (request, reply) => {
const clientId = process.env.GITHUB_CLIENT_ID;
const redirectUri = process.env.GITHUB_REDIRECT_URL;
if (!clientId || !redirectUri) {
return reply.code(400).send({ error: 'GitHub OAuth not configured' });
}
// Generate ephemeral state token (5 minutes TTL)
const state = await auth.createGithubToken(request.userId);
// Build complete OAuth URL
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: 'user',
state: state
});
const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
return reply.send({ url });
});
// GitHub OAuth callback (GET for redirect from GitHub)
typed.get('/v1/connect/github/callback', {
schema: {
querystring: z.object({
code: z.string(),
state: z.string()
})
}
}, async (request, reply) => {
const { code, state } = request.query;
// Verify the state token to get userId
const tokenData = await auth.verifyGithubToken(state);
if (!tokenData) {
return reply.redirect('https://app.happy.engineering?error=invalid_state');
}
const userId = tokenData.userId;
const clientId = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
if (!clientId || !clientSecret) {
return reply.redirect('https://app.happy.engineering?error=server_config');
}
try {
// Exchange code for access token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: clientId,
client_secret: clientSecret,
code: code
})
});
const tokenResponseData = await tokenResponse.json() as {
access_token?: string;
error?: string;
error_description?: string;
};
if (tokenResponseData.error) {
return reply.redirect(`https://app.happy.engineering?error=${encodeURIComponent(tokenResponseData.error)}`);
}
const accessToken = tokenResponseData.access_token;
// Get user info from GitHub
const userResponse = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.github.v3+json',
}
});
const userData = await userResponse.json() as GitHubProfile;
if (!userResponse.ok) {
return reply.redirect('https://app.happy.engineering?error=github_user_fetch_failed');
}
// Store GitHub user and connect to account
const githubUser = await db.githubUser.upsert({
where: { id: userData.id.toString() },
update: {
profile: userData,
token: encryptString(['user', userId, 'github', 'token'], accessToken!)
},
create: {
id: userData.id.toString(),
profile: userData,
token: encryptString(['user', userId, 'github', 'token'], accessToken!)
}
});
// Link GitHub user to account
await db.account.update({
where: { id: userId },
data: { githubUserId: githubUser.id }
});
log({ module: 'github-oauth' }, `GitHub account connected successfully for user ${userId}: ${userData.login}`);
// Redirect to app with success
return reply.redirect(`https://app.happy.engineering?github=connected&user=${encodeURIComponent(userData.login)}`);
} catch (error) {
log({ module: 'github-oauth' }, `Error in GitHub GET callback: ${error}`);
return reply.redirect('https://app.happy.engineering?error=server_error');
}
});
// Account auth request
typed.post('/v1/auth/account/request', {
schema: {
@ -331,11 +470,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
// Check if OpenAI API key is configured on server
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (!OPENAI_API_KEY) {
return reply.code(500).send({
error: 'OpenAI API key not configured on server'
return reply.code(500).send({
error: 'OpenAI API key not configured on server'
});
}
// Generate ephemeral token from OpenAI
const response = await fetch('https://api.openai.com/v1/realtime/sessions', {
method: 'POST',
@ -348,11 +487,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
voice: 'verse',
}),
});
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.status}`);
}
const data = await response.json() as {
client_secret: {
value: string;
@ -360,14 +499,14 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
};
id: string;
};
return reply.send({
token: data.client_secret.value
});
} catch (error) {
log({ module: 'openai', level: 'error' }, 'Failed to generate ephemeral token', error);
return reply.code(500).send({
error: 'Failed to generate ephemeral token'
return reply.code(500).send({
error: 'Failed to generate ephemeral token'
});
}
});
@ -642,11 +781,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
where: { id: request.userId },
select: { settings: true, settingsVersion: true }
});
if (!user) {
return reply.code(500).send({ error: 'Failed to get account settings' });
}
return reply.send({
settings: user.settings,
settingsVersion: user.settingsVersion
@ -690,14 +829,14 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
where: { id: userId },
select: { settings: true, settingsVersion: true }
});
if (!currentUser) {
return reply.code(500).send({
success: false,
error: 'Failed to update account settings'
});
}
// Check current version
if (currentUser.settingsVersion !== expectedVersion) {
return reply.code(200).send({
@ -1270,7 +1409,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
socket.on('disconnect', () => {
websocketEventsCounter.inc({ event_type: 'disconnect' });
// Cleanup connections
eventRouter.removeConnection(userId, connection);
decrementWebSocketConnection(connection.connectionType);
@ -1319,7 +1458,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
// Track metrics
websocketEventsCounter.inc({ event_type: 'session-alive' });
sessionAliveEventsCounter.inc();
// Basic validation
if (!data || typeof data.time !== 'number' || !data.sid) {
return;
@ -1364,7 +1503,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
// Track metrics
websocketEventsCounter.inc({ event_type: 'machine-alive' });
machineAliveEventsCounter.inc();
// Basic validation
if (!data || typeof data.time !== 'number' || !data.machineId) {
return;

39
sources/app/types.ts Normal file
View File

@ -0,0 +1,39 @@
export interface GitHubProfile {
id: number;
login: string;
type: string;
site_admin: boolean;
avatar_url: string;
gravatar_id: string | null;
name: string | null;
company: string | null;
blog: string | null;
location: string | null;
email: string | null;
hireable: boolean | null;
bio: string | null;
twitter_username: string | null;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
updated_at: string;
// Private user fields (only available when authenticated)
private_gists?: number;
total_private_repos?: number;
owned_private_repos?: number;
disk_usage?: number;
collaborators?: number;
two_factor_authentication?: boolean;
plan?: {
collaborators: number;
name: string;
space: number;
private_repos: number;
};
}
export interface GitHubOrg {
}

8
sources/dbtypes.ts Normal file
View File

@ -0,0 +1,8 @@
import { GitHubProfile as GitHubProfileType, GitHubOrg as GitHubOrgType } from "./app/types";
declare global {
namespace PrismaJson {
type GitHubProfile = GitHubProfileType;
type GitHubOrg = GitHubOrgType;
}
}

View File

@ -8,6 +8,8 @@ import { startMetricsServer } from "@/app/metrics";
import { activityCache } from "@/modules/sessionCache";
import { auth } from "./modules/auth";
import { startDatabaseMetricsUpdater } from "@/modules/metrics";
import { initEncrypt } from "./modules/encrypt";
import { initGithub } from "./modules/github";
async function main() {
@ -22,6 +24,8 @@ async function main() {
await redis.ping();
// Initialize auth module
await initEncrypt();
await initGithub();
await auth.init();
//

View File

@ -10,6 +10,8 @@ interface TokenCacheEntry {
interface AuthTokens {
generator: Awaited<ReturnType<typeof privacyKit.createPersistentTokenGenerator>>;
verifier: Awaited<ReturnType<typeof privacyKit.createPersistentTokenVerifier>>;
githubVerifier: Awaited<ReturnType<typeof privacyKit.createEphemeralTokenVerifier>>;
githubGenerator: Awaited<ReturnType<typeof privacyKit.createEphemeralTokenGenerator>>;
}
class AuthModule {
@ -27,13 +29,25 @@ class AuthModule {
service: 'handy',
seed: process.env.HANDY_MASTER_SECRET!
});
const verifier = await privacyKit.createPersistentTokenVerifier({
service: 'handy',
publicKey: generator.publicKey
});
this.tokens = { generator, verifier };
const githubVerifier = await privacyKit.createEphemeralTokenVerifier({
service: 'github-happy',
publicKey: generator.publicKey,
});
const githubGenerator = await privacyKit.createEphemeralTokenGenerator({
service: 'github-happy',
seed: process.env.HANDY_MASTER_SECRET!,
ttl: 5 * 60 * 1000 // 5 minutes
});
this.tokens = { generator, verifier, githubVerifier, githubGenerator };
log({ module: 'auth' }, 'Auth module initialized');
}
@ -133,6 +147,35 @@ class AuthModule {
};
}
async createGithubToken(userId: string): Promise<string> {
if (!this.tokens) {
throw new Error('Auth module not initialized');
}
const payload = { user: userId, purpose: 'github-oauth' };
const token = await this.tokens.githubGenerator.new(payload);
return token;
}
async verifyGithubToken(token: string): Promise<{ userId: string } | null> {
if (!this.tokens) {
throw new Error('Auth module not initialized');
}
try {
const verified = await this.tokens.githubVerifier.verify(token);
if (!verified) {
return null;
}
return { userId: verified.user as string };
} catch (error) {
log({ module: 'auth', level: 'error' }, `GitHub token verification failed: ${error}`);
return null;
}
}
// Cleanup old entries (optional - can be called periodically)
cleanup(): void {
// Note: Since tokens are cached "forever" as requested,

View File

@ -0,0 +1,26 @@
import { KeyTree, crypto } from "privacy-kit";
let keyTree: KeyTree | null = null;
export async function initEncrypt() {
keyTree = new KeyTree(await crypto.deriveSecureKey({
key: process.env.HANDY_MASTER_SECRET!,
usage: 'happy-server-tokens'
}));
}
export function encryptString(path: string[], string: string) {
return keyTree!.symmetricEncrypt(path, string);
}
export function encryptBytes(path: string[], bytes: Uint8Array) {
return keyTree!.symmetricEncrypt(path, bytes);
}
export function decryptString(path: string[], encrypted: Uint8Array) {
return keyTree!.symmetricDecryptBuffer(path, encrypted);
}
export function decryptBytes(path: string[], encrypted: Uint8Array) {
return keyTree!.symmetricDecryptBuffer(path, encrypted);
}

19
sources/modules/github.ts Normal file
View File

@ -0,0 +1,19 @@
import { App } from "octokit";
let app: App | null = null;
export async function initGithub() {
if (
process.env.GITHUB_APP_ID &&
process.env.GITHUB_PRIVATE_KEY &&
process.env.GITHUB_CLIENT_ID &&
process.env.GITHUB_CLIENT_SECRET &&
process.env.GITHUB_REDIRECT_URL &&
process.env.GITHUB_WEBHOOK_SECRET
) {
app = new App({
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_PRIVATE_KEY,
});
}
}

263
yarn.lock
View File

@ -381,6 +381,222 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a"
integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==
"@octokit/app@^16.0.1":
version "16.1.0"
resolved "https://registry.yarnpkg.com/@octokit/app/-/app-16.1.0.tgz#e0d7472fc2e7ae7b0ab3f1e4cca8c2aba5f052ad"
integrity sha512-OdKHnm0CYLk8Setr47CATT4YnRTvWkpTYvE+B/l2B0mjszlfOIit3wqPHVslD2jfc1bD4UbO7Mzh6gjCuMZKsA==
dependencies:
"@octokit/auth-app" "^8.1.0"
"@octokit/auth-unauthenticated" "^7.0.1"
"@octokit/core" "^7.0.2"
"@octokit/oauth-app" "^8.0.1"
"@octokit/plugin-paginate-rest" "^13.0.0"
"@octokit/types" "^14.0.0"
"@octokit/webhooks" "^14.0.0"
"@octokit/auth-app@^8.1.0":
version "8.1.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-8.1.0.tgz#19dacfe49ea62a1f40189ac222925e8a757b6a7c"
integrity sha512-6bWhyvLXqCSfHiqlwzn9pScLZ+Qnvh/681GR/UEEPCMIVwfpRDBw0cCzy3/t2Dq8B7W2X/8pBgmw6MOiyE0DXQ==
dependencies:
"@octokit/auth-oauth-app" "^9.0.1"
"@octokit/auth-oauth-user" "^6.0.0"
"@octokit/request" "^10.0.2"
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
toad-cache "^3.7.0"
universal-github-app-jwt "^2.2.0"
universal-user-agent "^7.0.0"
"@octokit/auth-oauth-app@^9.0.1":
version "9.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz#d8d2e950c95e9fcbe6f2fb98d4539ee8c9871766"
integrity sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==
dependencies:
"@octokit/auth-oauth-device" "^8.0.1"
"@octokit/auth-oauth-user" "^6.0.0"
"@octokit/request" "^10.0.2"
"@octokit/types" "^14.0.0"
universal-user-agent "^7.0.0"
"@octokit/auth-oauth-device@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz#232ec13e299dd6bf199fe237527d04ec12decffb"
integrity sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==
dependencies:
"@octokit/oauth-methods" "^6.0.0"
"@octokit/request" "^10.0.2"
"@octokit/types" "^14.0.0"
universal-user-agent "^7.0.0"
"@octokit/auth-oauth-user@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz#ba643060824536cd848c72d835061b1c00007286"
integrity sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==
dependencies:
"@octokit/auth-oauth-device" "^8.0.1"
"@octokit/oauth-methods" "^6.0.0"
"@octokit/request" "^10.0.2"
"@octokit/types" "^14.0.0"
universal-user-agent "^7.0.0"
"@octokit/auth-token@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-6.0.0.tgz#b02e9c08a2d8937df09a2a981f226ad219174c53"
integrity sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==
"@octokit/auth-unauthenticated@^7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.1.tgz#427b8a52e672318f84757307e766a448c741116b"
integrity sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ==
dependencies:
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
"@octokit/core@^7.0.2":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-7.0.3.tgz#0b5288995fed66920128d41cfeea34979d48a360"
integrity sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==
dependencies:
"@octokit/auth-token" "^6.0.0"
"@octokit/graphql" "^9.0.1"
"@octokit/request" "^10.0.2"
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
before-after-hook "^4.0.0"
universal-user-agent "^7.0.0"
"@octokit/endpoint@^11.0.0":
version "11.0.0"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-11.0.0.tgz#189fcc022721b4c49d0307eea6be3de1cfb53026"
integrity sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==
dependencies:
"@octokit/types" "^14.0.0"
universal-user-agent "^7.0.2"
"@octokit/graphql@^9.0.1":
version "9.0.1"
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-9.0.1.tgz#eb258fc9981403d2d751720832652c385b6c1613"
integrity sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==
dependencies:
"@octokit/request" "^10.0.2"
"@octokit/types" "^14.0.0"
universal-user-agent "^7.0.0"
"@octokit/oauth-app@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-8.0.1.tgz#7cb889945c3ccacfd0ff9aa9f05e9748b439f7f3"
integrity sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ==
dependencies:
"@octokit/auth-oauth-app" "^9.0.1"
"@octokit/auth-oauth-user" "^6.0.0"
"@octokit/auth-unauthenticated" "^7.0.1"
"@octokit/core" "^7.0.2"
"@octokit/oauth-authorization-url" "^8.0.0"
"@octokit/oauth-methods" "^6.0.0"
"@types/aws-lambda" "^8.10.83"
universal-user-agent "^7.0.0"
"@octokit/oauth-authorization-url@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz#fdbab39a07d38faaad8621a5fdf04bc0c36d63e7"
integrity sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==
"@octokit/oauth-methods@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz#a138bbbec6762b52249f7c47d0c548fc1cf6ad7b"
integrity sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==
dependencies:
"@octokit/oauth-authorization-url" "^8.0.0"
"@octokit/request" "^10.0.2"
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
"@octokit/openapi-types@^25.1.0":
version "25.1.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-25.1.0.tgz#5a72a9dfaaba72b5b7db375fd05e90ca90dc9682"
integrity sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==
"@octokit/openapi-webhooks-types@12.0.3":
version "12.0.3"
resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz#fa44fb31fbb4c444c5fd640dbbf537f00c20617c"
integrity sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==
"@octokit/plugin-paginate-graphql@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz#acdefd7e85ce24716e7ad7352f2df4d29d0e273b"
integrity sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==
"@octokit/plugin-paginate-rest@^13.0.0":
version "13.1.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz#ca5bb1c7b85a583691263c1f788f607e9bcb74b3"
integrity sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==
dependencies:
"@octokit/types" "^14.1.0"
"@octokit/plugin-rest-endpoint-methods@^16.0.0":
version "16.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz#ba30ca387fc2ac8bd93cf9f951174736babebd97"
integrity sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==
dependencies:
"@octokit/types" "^14.1.0"
"@octokit/plugin-retry@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-8.0.1.tgz#ee4a0487d31b97ad3deaf737faad68abeca3c227"
integrity sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==
dependencies:
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
bottleneck "^2.15.3"
"@octokit/plugin-throttling@^11.0.1":
version "11.0.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-11.0.1.tgz#31a0b5e759f0313514d9522a4103360f17ffc2e4"
integrity sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==
dependencies:
"@octokit/types" "^14.0.0"
bottleneck "^2.15.3"
"@octokit/request-error@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-7.0.0.tgz#48ae2cd79008315605d00e83664891a10a5ddb97"
integrity sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==
dependencies:
"@octokit/types" "^14.0.0"
"@octokit/request@^10.0.2":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.3.tgz#2ffdb88105ce20d25dcab8a592a7040ea48306c7"
integrity sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==
dependencies:
"@octokit/endpoint" "^11.0.0"
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
fast-content-type-parse "^3.0.0"
universal-user-agent "^7.0.2"
"@octokit/types@^14.0.0", "@octokit/types@^14.1.0":
version "14.1.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-14.1.0.tgz#3bf9b3a3e3b5270964a57cc9d98592ed44f840f2"
integrity sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==
dependencies:
"@octokit/openapi-types" "^25.1.0"
"@octokit/webhooks-methods@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz#34abf78aec6f826fe561cfe79d2ebb1950d1d25f"
integrity sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==
"@octokit/webhooks@^14.0.0":
version "14.1.3"
resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-14.1.3.tgz#2d0bed71b07745c0b33363d69e0ae0e440469a18"
integrity sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==
dependencies:
"@octokit/openapi-webhooks-types" "12.0.3"
"@octokit/request-error" "^7.0.0"
"@octokit/webhooks-methods" "^6.0.0"
"@opentelemetry/api@^1.4.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
@ -719,6 +935,11 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/aws-lambda@^8.10.83":
version "8.10.152"
resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.152.tgz#f68424a8175f0a54a2a941e65b76c3f51f3bd89d"
integrity sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==
"@types/body-parser@*":
version "1.19.5"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
@ -1029,11 +1250,21 @@ base64id@2.0.0, base64id@~2.0.0:
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
before-after-hook@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9"
integrity sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==
bintrees@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==
bottleneck@^2.15.3:
version "2.19.5"
resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91"
integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@ -1431,6 +1662,11 @@ expect-type@^1.2.1:
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f"
integrity sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==
fast-content-type-parse@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb"
integrity sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==
fast-copy@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35"
@ -1903,6 +2139,23 @@ obliterator@^2.0.4:
resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.5.tgz#031e0145354b0c18840336ae51d41e7d6d2c76aa"
integrity sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==
octokit@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/octokit/-/octokit-5.0.3.tgz#1e4f110e28218ab9676c28da5f28ab403fe5b643"
integrity sha512-+bwYsAIRmYv30NTmBysPIlgH23ekVDriB07oRxlPIAH5PI0yTMSxg5i5Xy0OetcnZw+nk/caD4szD7a9YZ3QyQ==
dependencies:
"@octokit/app" "^16.0.1"
"@octokit/core" "^7.0.2"
"@octokit/oauth-app" "^8.0.1"
"@octokit/plugin-paginate-graphql" "^6.0.0"
"@octokit/plugin-paginate-rest" "^13.0.0"
"@octokit/plugin-rest-endpoint-methods" "^16.0.0"
"@octokit/plugin-retry" "^8.0.1"
"@octokit/plugin-throttling" "^11.0.1"
"@octokit/request-error" "^7.0.0"
"@octokit/types" "^14.0.0"
"@octokit/webhooks" "^14.0.0"
on-exit-leak-free@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8"
@ -2503,6 +2756,16 @@ undici-types@~7.8.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294"
integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==
universal-github-app-jwt@^2.2.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz#38537e5a7d154085a35f97601a5e30e9e17717df"
integrity sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==
universal-user-agent@^7.0.0, universal-user-agent@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz#c05870a58125a2dc00431f2df815a77fe69736be"
integrity sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==
url-join@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"