From fcc3642ad1c0dc84a54e472edffb3bff4dc3b442 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 29 Nov 2022 10:15:59 +0900 Subject: [PATCH 001/140] chore: update binary name for exact project name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 592b3e5..4511e2e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test:watch": "jest --watch" }, "bin": { - "solv": "./bin/solv.cjs" + "cage": "./bin/cage.cjs" }, "files": [ "dist", From 91c40de6a22b240e6720680d4297d1a27c802bd8 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 29 Nov 2022 11:14:22 +0900 Subject: [PATCH 002/140] feat(logger): implement basic logger --- package.json | 2 ++ src/index.ts | 5 ++++- src/utils/logger.ts | 46 +++++++++++++++++++++++++++++++++++++++++++++ src/utils/type.ts | 1 + yarn.lock | 5 +++++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/utils/logger.ts create mode 100644 src/utils/type.ts diff --git a/package.json b/package.json index 4511e2e..5792792 100644 --- a/package.json +++ b/package.json @@ -54,5 +54,7 @@ "typescript": "^4.8.3" }, "dependencies": { + "chalk": "^4.1.2", + "dayjs": "^1.11.6" } } diff --git a/src/index.ts b/src/index.ts index a54ca4e..d11ccae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ +import { Logger } from "./utils/logger"; + async function main() { - console.log("Hello World from Cage! 🐦"); + Logger.clear(); + Logger.info("Hello World from Cage! 🐦"); } main(); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..4e43437 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,46 @@ +import * as dayjs from "dayjs"; +import * as chalk from "chalk"; + +import { Fn } from "./type"; + +type LoggerFn = (content: string) => void; +type LogLevel = "silly" | "info" | "warn" | "error"; + +const LogLevels: LogLevel[] = ["warn", "info", "silly", "error"]; +const LOG_LEVEL_COLOR_MAP: Record> = { + silly: chalk.cyan, + info: chalk.cyan, + warn: chalk.yellow, + error: chalk.red, +}; + +function createLoggerFn(level: LogLevel): LoggerFn { + const MAX_WIDTH = Math.max(...LogLevels.map(l => l.length)); + const tokens = chalk.green( + [ + chalk.cyan(dayjs().format("YYYY-MM-DD HH:mm:ss")), + LOG_LEVEL_COLOR_MAP[level](level.toUpperCase().padEnd(MAX_WIDTH, " ")), + ] + .map(t => `[${t}]`) + .join(""), + ); + + return content => { + const formattedString = `${tokens} ${content}`; + console.log(formattedString); + }; +} + +interface LoggerType extends Record { + clear(): void; +} + +export const Logger: LoggerType = { + info: createLoggerFn("info"), + error: createLoggerFn("error"), + warn: createLoggerFn("warn"), + silly: createLoggerFn("silly"), + clear() { + console.clear(); + }, +}; diff --git a/src/utils/type.ts b/src/utils/type.ts new file mode 100644 index 0000000..7799e9a --- /dev/null +++ b/src/utils/type.ts @@ -0,0 +1 @@ +export type Fn = (arg: TArg) => TReturn; diff --git a/yarn.lock b/yarn.lock index b67c548..6777262 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1964,6 +1964,11 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.11.6: + version "1.11.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" + integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== + debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" From f4d84a70800943451cdef2695057da5f1abf7ee0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 30 Nov 2022 14:12:24 +0900 Subject: [PATCH 003/140] feat(twitter): implement basic twitter auth logic --- .gitignore | 1 + nodemon.json | 2 +- package.json | 8 +- src/index.ts | 23 ++- src/utils/fetcher.ts | 45 ++++++ src/utils/generateRandomString.ts | 8 + src/utils/logger.ts | 49 ++++-- src/utils/measureTime.ts | 11 ++ src/utils/noop.ts | 2 + src/utils/parseCookie.ts | 8 + src/utils/sleep.ts | 5 + src/utils/type.ts | 14 +- src/watchers/base.ts | 9 ++ src/watchers/index.ts | 4 + src/watchers/twitter/auth.ts | 209 ++++++++++++++++++++++++++ src/watchers/twitter/constants.ts | 2 + src/watchers/twitter/helper.ts | 49 ++++++ src/watchers/twitter/index.ts | 45 ++++++ tsconfig.json | 8 +- yarn.lock | 240 ++++++++++++++++++++++++++++-- 20 files changed, 713 insertions(+), 29 deletions(-) create mode 100644 src/utils/fetcher.ts create mode 100644 src/utils/generateRandomString.ts create mode 100644 src/utils/measureTime.ts create mode 100644 src/utils/noop.ts create mode 100644 src/utils/parseCookie.ts create mode 100644 src/utils/sleep.ts create mode 100644 src/watchers/base.ts create mode 100644 src/watchers/index.ts create mode 100644 src/watchers/twitter/auth.ts create mode 100644 src/watchers/twitter/constants.ts create mode 100644 src/watchers/twitter/helper.ts create mode 100644 src/watchers/twitter/index.ts diff --git a/.gitignore b/.gitignore index 0592f5b..830bfad 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /yarn-error.log /dist /coverage +/.env diff --git a/nodemon.json b/nodemon.json index c64ae9c..3407c6e 100644 --- a/nodemon.json +++ b/nodemon.json @@ -3,5 +3,5 @@ "ignore": ["*.test.js", "bin", "dist", ".git"], "watch": ["src"], "ext": "ts", - "exec": "ts-node ./src/index.ts" + "exec": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts" } diff --git a/package.json b/package.json index 5792792..f6ff3c0 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,11 @@ "@types/node-fetch": "^2.6.2", "@types/prompts": "^2.0.14", "@types/replace-ext": "^2.0.0", + "@types/set-cookie-parser": "^2.4.2", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/parser": "^5.38.0", + "dotenv": "^16.0.3", "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", @@ -51,10 +53,14 @@ "semantic-release": "^19.0.5", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", + "tsconfig-paths": "^4.1.0", "typescript": "^4.8.3" }, "dependencies": { "chalk": "^4.1.2", - "dayjs": "^1.11.6" + "dayjs": "^1.11.6", + "node-fetch": "^2.6.7", + "puppeteer": "^19.3.0", + "set-cookie-parser": "^2.5.1" } } diff --git a/src/index.ts b/src/index.ts index d11ccae..810c6a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,29 @@ -import { Logger } from "./utils/logger"; +import * as chalk from "chalk"; + +import { AVAILABLE_PROVIDERS } from "@watchers"; + +import { Logger } from "@utils/logger"; +import { Env, ProviderInitializeContext } from "@utils/type"; async function main() { Logger.clear(); Logger.info("Hello World from Cage! 🐦"); + + const initializeContext: ProviderInitializeContext = { + env: process.env as Env, + }; + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work("info", `Try to initialize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { + await provider.initialize(initializeContext); + }); + } + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work("info", `Try to finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { + await provider.finalize(); + }); + } } main(); diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts new file mode 100644 index 0000000..d1a3a6a --- /dev/null +++ b/src/utils/fetcher.ts @@ -0,0 +1,45 @@ +import fetch, { Headers, RequestInit, Response } from "node-fetch"; + +import { parseCookie } from "@utils/parseCookie"; + +export class Fetcher { + private readonly cookies: Record = {}; + + private getCookieString(): string { + return Object.entries(this.cookies) + .map(([key, value]) => `${key}=${value}`) + .join("; "); + } + private setCookies(setCookie: string | null) { + if (!setCookie) { + return; + } + + const cookies = parseCookie(setCookie); + for (const cookie of cookies) { + if (cookie.name && cookie.value) { + this.cookies[cookie.name] = cookie.value; + } + } + } + public getCookies(): Record { + return { ...this.cookies }; + } + + public async fetchJson(url: string, options: RequestInit = {}): Promise { + const response = await this.fetch(url, options); + return response.json(); + } + public async fetch(url: string, options: RequestInit = {}): Promise { + const headers = new Headers(options.headers); + headers.set("cookie", this.getCookieString()); + + const response = await fetch(url, { + ...options, + headers, + }); + + this.setCookies(response.headers.get("set-cookie")); + return response; + } +} diff --git a/src/utils/generateRandomString.ts b/src/utils/generateRandomString.ts new file mode 100644 index 0000000..96cd0cf --- /dev/null +++ b/src/utils/generateRandomString.ts @@ -0,0 +1,8 @@ +export function generateRandomString(length: number, chars: string) { + let result = ""; + for (let i = length; i > 0; --i) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + + return result; +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 4e43437..98541a6 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,13 +1,14 @@ import * as dayjs from "dayjs"; import * as chalk from "chalk"; -import { Fn } from "./type"; +import { measureTime } from "@utils/measureTime"; +import { Fn } from "@utils/type"; -type LoggerFn = (content: string) => void; +type LoggerFn = (content: string, breakLine?: boolean) => void; type LogLevel = "silly" | "info" | "warn" | "error"; const LogLevels: LogLevel[] = ["warn", "info", "silly", "error"]; -const LOG_LEVEL_COLOR_MAP: Record> = { +const LOG_LEVEL_COLOR_MAP: Record> = { silly: chalk.cyan, info: chalk.cyan, warn: chalk.yellow, @@ -18,29 +19,47 @@ function createLoggerFn(level: LogLevel): LoggerFn { const MAX_WIDTH = Math.max(...LogLevels.map(l => l.length)); const tokens = chalk.green( [ - chalk.cyan(dayjs().format("YYYY-MM-DD HH:mm:ss")), + chalk.cyan(dayjs().format("HH:mm:ss.SSS")), LOG_LEVEL_COLOR_MAP[level](level.toUpperCase().padEnd(MAX_WIDTH, " ")), ] .map(t => `[${t}]`) .join(""), ); - return content => { + return (content, breakLine = true) => { const formattedString = `${tokens} ${content}`; - console.log(formattedString); + if (breakLine) { + console.log(formattedString); + } else { + process.stdout.write(formattedString); + } }; } interface LoggerType extends Record { clear(): void; + work(level: LogLevel, message: string, work: () => void | Promise, done?: string): Promise; } -export const Logger: LoggerType = { - info: createLoggerFn("info"), - error: createLoggerFn("error"), - warn: createLoggerFn("warn"), - silly: createLoggerFn("silly"), - clear() { - console.clear(); - }, -}; +export const Logger: LoggerType = (() => { + const loggers: Record = { + info: createLoggerFn("info"), + error: createLoggerFn("error"), + warn: createLoggerFn("warn"), + silly: createLoggerFn("silly"), + }; + + return { + ...loggers, + work: async (level: LogLevel, message: string, work: () => void | Promise, done = "done.") => { + loggers[level](message, false); + + const elapsedTime = await measureTime(() => work()); + + process.stdout.write(`${done} ${chalk.gray(`(${elapsedTime}ms)`)}\n`); + }, + clear: () => { + console.clear(); + }, + }; +})(); diff --git a/src/utils/measureTime.ts b/src/utils/measureTime.ts new file mode 100644 index 0000000..5c9901a --- /dev/null +++ b/src/utils/measureTime.ts @@ -0,0 +1,11 @@ +import * as dayjs from "dayjs"; + +import { Work } from "@utils/type"; + +export async function measureTime(work: Work) { + const startedAt = dayjs(); + await work(); + const endedAt = dayjs(); + + return endedAt.diff(startedAt); +} diff --git a/src/utils/noop.ts b/src/utils/noop.ts new file mode 100644 index 0000000..4b1407a --- /dev/null +++ b/src/utils/noop.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-function +export const noop = () => {}; diff --git a/src/utils/parseCookie.ts b/src/utils/parseCookie.ts new file mode 100644 index 0000000..06cbad2 --- /dev/null +++ b/src/utils/parseCookie.ts @@ -0,0 +1,8 @@ +import * as setCookieParser from "set-cookie-parser"; + +export function parseCookie(setCookie: string) { + return setCookieParser + .splitCookiesString(setCookie) + .map(cookie => setCookieParser.parse(cookie)) + .flat(); +} diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..b71f6bd --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,5 @@ +export function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} diff --git a/src/utils/type.ts b/src/utils/type.ts index 7799e9a..bbd9c06 100644 --- a/src/utils/type.ts +++ b/src/utils/type.ts @@ -1 +1,13 @@ -export type Fn = (arg: TArg) => TReturn; +import type { TWITTER_PROVIDER_ENV_KEYS } from "@watchers/twitter"; + +export type Fn = (...arg: TArg) => TReturn; +export type Work = Fn<[], Promise | void>; + +export type KnownEnvKeys = `CAGE_${TWITTER_PROVIDER_ENV_KEYS}`; +export type Env = { + [key in KnownEnvKeys]?: string | undefined; +}; + +export interface ProviderInitializeContext { + readonly env: Readonly; +} diff --git a/src/watchers/base.ts b/src/watchers/base.ts new file mode 100644 index 0000000..a50fe36 --- /dev/null +++ b/src/watchers/base.ts @@ -0,0 +1,9 @@ +import { ProviderInitializeContext } from "@utils/type"; + +export abstract class BaseWatcher { + public abstract getName(): string; + + public abstract initialize(context: ProviderInitializeContext): Promise; + public abstract finalize(): Promise; + public abstract startWatch(): Promise; +} diff --git a/src/watchers/index.ts b/src/watchers/index.ts new file mode 100644 index 0000000..97db815 --- /dev/null +++ b/src/watchers/index.ts @@ -0,0 +1,4 @@ +import { BaseWatcher } from "@watchers/base"; +import { TwitterWatcher } from "@watchers/twitter"; + +export const AVAILABLE_PROVIDERS: ReadonlyArray = [new TwitterWatcher()]; diff --git a/src/watchers/twitter/auth.ts b/src/watchers/twitter/auth.ts new file mode 100644 index 0000000..82bf79e --- /dev/null +++ b/src/watchers/twitter/auth.ts @@ -0,0 +1,209 @@ +import { Fetcher } from "@utils/fetcher"; +import { Fn } from "@utils/type"; + +export class TwitterAuth { + private readonly tasks: Array>> = []; + private csrf: string | null = null; + private flowToken = ""; + + public constructor( + private readonly fetcher: Fetcher, + private readonly getHeaders: Fn<[], Record>, + private readonly guestTokenHandler: Fn<[string], void>, + private readonly csrfHandler: Fn<[string], void>, + ) {} + + public doInstrumentation() { + this.tasks.push(async () => { + const data = await this.fetcher.fetchJson<{ flow_token: string }>( + "https://twitter.com/i/api/1.1/onboarding/task.json", + { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginJsInstrumentationSubtask", + js_instrumentation: { + response: JSON.stringify({ + rf: { + eca553b518cf2bf0dac5687d57cc1857738793960864216fb28429c367b1074c: 96, + bf7ee80b5b9e0822c133ba94f7c8fa22efadc740ccfe7e83a8907335d7668441: 128, + aea7b2935d0508b33d370215bb57788afe530c7250f289c4e2f347579a363c98: 154, + aa1900ce941819d2ce7b3985d94c4b05781f7b9bd3b2389cd1b28f1b8156ebc8: -115, + }, + s: "CKq_55Z6_WsJSB5d7xiFWMeLaWUckgaJw1bADMTt3WNVnsZGXlu4q9EDeem5Azx-pPMUR8M2_VRpnn0411iltwDyynQKh_ONDxmO52V2TrialbGMyy1Zh-J1Xj_xeXDRI-DPpTG-xe7qTtzMZj8i60xSwZv15BV0S496tJ00pSNlfYBEk-YK85q2kO3cGQg7ZPoHDjxIJPRcintmYd9ioVMFTMYcmrnhGkpk6Qf7GqwbL1XEkAAtuqB33AD_xH-V3P3Dl2z4RQQsL-xL6-LwuEtJW4F6l91N4Fqx0jCN9AaP42CM-tMNu0lJvZw32QTGhE8VzFg1Db26CTym4q9qlAAAAYTGoKew", + }), + link: "next_link", + }, + }, + ], + }), + }, + ); + + this.flowToken = data.flow_token; + }); + + return this; + } + public setUserId(userId: string) { + this.tasks.push(async () => { + const data = await this.fetcher.fetchJson<{ flow_token: string }>( + "https://twitter.com/i/api/1.1/onboarding/task.json", + { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginEnterUserIdentifierSSO", + settings_list: { + setting_responses: [ + { key: "user_identifier", response_data: { text_data: { result: userId } } }, + ], + link: "next_link", + }, + }, + ], + }), + }, + ); + + this.flowToken = data.flow_token; + }); + + return this; + } + public setPassword(password: string) { + this.tasks.push(async () => { + const data = await this.fetcher.fetchJson<{ flow_token: string }>( + "https://twitter.com/i/api/1.1/onboarding/task.json", + { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginEnterPassword", + enter_password: { password, link: "next_link" }, + }, + ], + }), + }, + ); + + this.flowToken = data.flow_token; + }); + + return this; + } + public doDuplicationCheck() { + this.tasks.push(async () => { + const data = await this.fetcher.fetchJson<{ flow_token: string }>( + "https://twitter.com/i/api/1.1/onboarding/task.json", + { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "AccountDuplicationCheck", + check_logged_in_account: { link: "AccountDuplicationCheck_false" }, + }, + ], + }), + }, + ); + + this.flowToken = data.flow_token; + this.csrf = this.fetcher.getCookies()["ct0"] || this.csrf; + }); + + return this; + } + + public async action() { + const { guest_token } = await this.fetcher.fetchJson<{ guest_token: string }>( + "https://api.twitter.com/1.1/guest/activate.json", + { + method: "POST", + headers: this.getHeaders(), + }, + ); + + this.guestTokenHandler(guest_token); + + const { flow_token } = await this.fetcher.fetchJson<{ flow_token: string }>( + "https://twitter.com/i/api/1.1/onboarding/task.json?flow_name=login", + { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + input_flow_data: { + flow_context: { debug_overrides: {}, start_location: { location: "manual_link" } }, + }, + subtask_versions: { + action_list: 2, + alert_dialog: 1, + app_download_cta: 1, + check_logged_in_account: 1, + choice_selection: 3, + contacts_live_sync_permission_prompt: 0, + cta: 7, + email_verification: 2, + end_flow: 1, + enter_date: 1, + enter_email: 2, + enter_password: 5, + enter_phone: 2, + enter_recaptcha: 1, + enter_text: 5, + enter_username: 2, + generic_urt: 3, + in_app_notification: 1, + interest_picker: 3, + js_instrumentation: 1, + menu_dialog: 1, + notifications_permission_prompt: 2, + open_account: 2, + open_home_timeline: 1, + open_link: 1, + phone_verification: 4, + privacy_options: 1, + security_key: 3, + select_avatar: 4, + select_banner: 2, + settings_list: 7, + show_code: 1, + sign_up: 2, + sign_up_review: 4, + tweet_selection_urt: 1, + update_users: 1, + upload_media: 1, + user_recommendations_list: 4, + user_recommendations_urt: 1, + wait_spinner: 3, + web_modal: 1, + }, + }), + }, + ); + + this.flowToken = flow_token; + + for (const task of this.tasks) { + await task(); + } + + if (!this.csrf) { + throw new Error("CSRF token not found"); + } + + this.csrfHandler(this.csrf); + } +} diff --git a/src/watchers/twitter/constants.ts b/src/watchers/twitter/constants.ts new file mode 100644 index 0000000..3fac7c3 --- /dev/null +++ b/src/watchers/twitter/constants.ts @@ -0,0 +1,2 @@ +export const TWITTER_AUTH_TOKEN = + "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"; diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts new file mode 100644 index 0000000..085b16a --- /dev/null +++ b/src/watchers/twitter/helper.ts @@ -0,0 +1,49 @@ +import { TwitterAuth } from "@watchers/twitter/auth"; +import { TWITTER_AUTH_TOKEN } from "@watchers/twitter/constants"; + +import { generateRandomString } from "@utils/generateRandomString"; +import { Fetcher } from "@utils/fetcher"; + +export class TwitterHelper { + private readonly fetcher: Fetcher = new Fetcher(); + + private authToken: string = TWITTER_AUTH_TOKEN; + private csrf: string | null = generateRandomString(32, "0123456789abcdefghijklmnopqrstuvwxyz"); + private guest: string | null = null; + + private getHeaders() { + const headers = { + "Content-type": "application/json", + "x-twitter-active-user": "yes", + "x-twitter-client-language": "ko", + Referer: "https://twitter.com/i/flow/login", + }; + + if (this.authToken) { + headers["authorization"] = `Bearer ${this.authToken}`; + } + + if (this.csrf) { + headers["x-csrf-token"] = this.csrf; + } + + if (this.guest) { + headers["x-guest-token"] = this.guest; + } + + return headers; + } + + public doAuth() { + return new TwitterAuth( + this.fetcher, + this.getHeaders.bind(this), + token => { + this.guest = token; + }, + token => { + this.csrf = token; + }, + ); + } +} diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts new file mode 100644 index 0000000..551975b --- /dev/null +++ b/src/watchers/twitter/index.ts @@ -0,0 +1,45 @@ +import { BaseWatcher } from "@watchers/base"; +import { TwitterHelper } from "@watchers/twitter/helper"; + +import { ProviderInitializeContext } from "@utils/type"; + +export type TWITTER_PROVIDER_ENV_KEYS = "TWITTER_USER_ID" | "TWITTER_PASSWORD"; + +export class TwitterWatcher extends BaseWatcher { + private readonly signHelper: TwitterHelper = new TwitterHelper(); + + public constructor() { + super(); + } + + public getName(): string { + return "Twitter"; + } + + public async initialize({ env }: ProviderInitializeContext): Promise { + if (!env.CAGE_TWITTER_USER_ID) { + throw new Error("Environment variable 'CAGE_TWITTER_USER_ID' should be provided."); + } else if (!env.CAGE_TWITTER_PASSWORD) { + throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); + } + + await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async finalize(): Promise {} + + public async startWatch(): Promise { + return Promise.resolve(undefined); + } + + private async login(userId: string, password: string) { + await this.signHelper + .doAuth() + .doInstrumentation() + .setUserId(userId) + .setPassword(password) + .doDuplicationCheck() + .action(); + } +} diff --git a/tsconfig.json b/tsconfig.json index 53154cd..e62a5e8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,13 @@ "noImplicitAny": false, "strictBindCallApply": true, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "@root/*": ["./src/*"], + "@utils/*": ["./src/utils/*"], + "@watchers/*": ["./src/watchers/*"], + "@watchers": ["./src/watchers"] + } }, "exclude": ["node_modules", "**/*spec.ts", "./*.ts"] } diff --git a/yarn.lock b/yarn.lock index 6777262..108a0e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1185,6 +1185,13 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== +"@types/set-cookie-parser@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz#b6a955219b54151bfebd4521170723df5e13caad" + integrity sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w== + dependencies: + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -1207,6 +1214,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^5.38.0": version "5.39.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz#778b2d9e7f293502c7feeea6c74dca8eb3e67511" @@ -1530,6 +1544,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + before-after-hook@^2.2.0: version "2.2.3" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" @@ -1552,6 +1571,15 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bottleneck@^2.18.1: version "2.19.5" resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" @@ -1603,11 +1631,24 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtins@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" @@ -1718,6 +1759,11 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -1924,7 +1970,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^7.0.0: +cosmiconfig@7.0.1, cosmiconfig@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== @@ -1940,6 +1986,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1969,7 +2022,7 @@ dayjs@^1.11.6: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2067,6 +2120,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +devtools-protocol@0.0.1056733: + version "0.0.1056733" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz#55bb1d56761014cc221131cca5e6bad94eefb2b9" + integrity sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA== + dezalgo@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" @@ -2111,6 +2169,11 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -2140,6 +2203,13 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + env-ci@^5.0.0: version "5.5.0" resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.5.0.tgz#43364e3554d261a586dec707bc32be81112b545f" @@ -2349,6 +2419,17 @@ expect@^29.0.0, expect@^29.1.2: jest-message-util "^29.1.2" jest-util "^29.1.2" +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2399,6 +2480,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2492,6 +2580,11 @@ fromentries@^1.3.2: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -2552,6 +2645,13 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -2722,7 +2822,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^5.0.0: +https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -2749,6 +2849,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" @@ -2810,7 +2915,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3857,6 +3962,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -3919,6 +4029,11 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -3980,7 +4095,7 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.7: +node-fetch@2.6.7, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -4271,7 +4386,7 @@ npmlog@^6.0.0, npmlog@^6.0.2: gauge "^4.0.3" set-blocking "^2.0.0" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -4487,6 +4602,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -4566,6 +4686,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +progress@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-all-reject-late@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" @@ -4604,16 +4729,57 @@ promzard@^0.3.0: dependencies: read "1" +proxy-from-env@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer-core@19.3.0: + version "19.3.0" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.3.0.tgz#deba854f3dd3f74a04db200274827a67e200f39e" + integrity sha512-P8VAAOBnBJo/7DKJnj1b0K9kZBF2D8lkdL94CjJ+DZKCp182LQqYemPI9omUSZkh4bgykzXjZhaVR1qtddTTQg== + dependencies: + cross-fetch "3.1.5" + debug "4.3.4" + devtools-protocol "0.0.1056733" + extract-zip "2.0.1" + https-proxy-agent "5.0.1" + proxy-from-env "1.1.0" + rimraf "3.0.2" + tar-fs "2.1.1" + unbzip2-stream "1.4.3" + ws "8.10.0" + +puppeteer@^19.3.0: + version "19.3.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.3.0.tgz#defa8b6a6401b23cdc4f634c441b55d61f6b3d65" + integrity sha512-WJbi/ULaeuFOz7cfMgJlJCBAZiyqIFeQ6os4h5ex3PVTt2qosXgwI9eruFZqFAwJRv8x5pOuMhWR0aSRgyDqEg== + dependencies: + cosmiconfig "7.0.1" + devtools-protocol "0.0.1056733" + https-proxy-agent "5.0.1" + progress "2.0.3" + proxy-from-env "1.1.0" + puppeteer-core "19.3.0" + q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -4698,7 +4864,7 @@ read@1, read@^1.0.7, read@~1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -4815,7 +4981,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -4924,6 +5090,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-cookie-parser@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" + integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5185,6 +5356,27 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tar-fs@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" @@ -5255,7 +5447,7 @@ through2@~2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3": +through@2, "through@>=2.2.7 <3", through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -5342,6 +5534,15 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tsconfig-paths@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz#f8ef7d467f08ae3a695335bf1ece088c5538d2c1" + integrity sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -5411,6 +5612,14 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" integrity sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg== +unbzip2-stream@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" @@ -5579,6 +5788,11 @@ write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@8.10.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" + integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -5635,6 +5849,14 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From db9ab2b5a72969438a9d1bfd6efbdcb368810744 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 30 Nov 2022 16:46:53 +0900 Subject: [PATCH 004/140] feat(twitter): implement collecting follower data feature --- .gitignore | 2 + package.json | 1 + src/index.ts | 38 ++++++++++- src/utils/fetcher.ts | 14 +++- src/utils/logger.ts | 27 ++++---- src/utils/measureTime.ts | 21 ++++-- src/utils/type.ts | 13 ++++ src/watchers/base.ts | 9 ++- src/watchers/twitter/auth.ts | 9 --- src/watchers/twitter/helper.ts | 119 +++++++++++++++++++++++++++++---- src/watchers/twitter/index.ts | 38 +++++++++-- src/watchers/twitter/types.ts | 77 +++++++++++++++++++++ src/watchers/twitter/utils.ts | 55 +++++++++++++++ yarn.lock | 5 ++ 14 files changed, 377 insertions(+), 51 deletions(-) create mode 100644 src/watchers/twitter/types.ts create mode 100644 src/watchers/twitter/utils.ts diff --git a/.gitignore b/.gitignore index 830bfad..4e65f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /dist /coverage /.env +/followers.json +/dump diff --git a/package.json b/package.json index f6ff3c0..05ddd12 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ }, "dependencies": { "chalk": "^4.1.2", + "cronstrue": "^2.20.0", "dayjs": "^1.11.6", "node-fetch": "^2.6.7", "puppeteer": "^19.3.0", diff --git a/src/index.ts b/src/index.ts index 810c6a9..b31ade9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import * as chalk from "chalk"; +import * as fs from "fs-extra"; import { AVAILABLE_PROVIDERS } from "@watchers"; @@ -14,16 +15,49 @@ async function main() { }; for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work("info", `Try to initialize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { + await Logger.work( + "debug", + `loading lastly dumped data of \`${chalk.green(provider.getName())}\` watcher ... `, + async () => { + const fileName = `${provider.getName().toLowerCase()}.json`; + const filePath = `./dump/${fileName}`; + + if (!fs.existsSync(filePath)) { + throw new Error(`Dump file \`${fileName}\` does not exist.`); + } + + provider.hydrate(await fs.readJSON(filePath)); + }, + ); + } + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work("info", `initialize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { await provider.initialize(initializeContext); }); } for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work("info", `Try to finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { + await Logger.work("info", `finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { await provider.finalize(); }); } + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work( + "debug", + `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, + async () => { + const data = provider.serialize(); + const filePath = `./dump/${provider.getName().toLowerCase()}.json`; + + await fs.ensureFile(filePath); + await fs.writeJson(filePath, data, { spaces: 4 }); + }, + ); + } + + Logger.info("Bye World from Cage! 🐦"); } main(); diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index d1a3a6a..d914159 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,8 +1,9 @@ import fetch, { Headers, RequestInit, Response } from "node-fetch"; import { parseCookie } from "@utils/parseCookie"; +import { Hydratable, Serializable } from "@utils/type"; -export class Fetcher { +export class Fetcher implements Serializable, Hydratable { private readonly cookies: Record = {}; private getCookieString(): string { @@ -42,4 +43,15 @@ export class Fetcher { this.setCookies(response.headers.get("set-cookie")); return response; } + + public serialize(): Record { + return { + cookies: this.cookies, + }; + } + public hydrate(data: Record): void { + Object.keys(data.cookies).forEach(key => { + this.cookies[key] = data.cookies[key]; + }); + } } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 98541a6..03ddf58 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -5,28 +5,24 @@ import { measureTime } from "@utils/measureTime"; import { Fn } from "@utils/type"; type LoggerFn = (content: string, breakLine?: boolean) => void; -type LogLevel = "silly" | "info" | "warn" | "error"; +type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; -const LogLevels: LogLevel[] = ["warn", "info", "silly", "error"]; const LOG_LEVEL_COLOR_MAP: Record> = { silly: chalk.cyan, info: chalk.cyan, warn: chalk.yellow, error: chalk.red, + debug: chalk.magenta, }; function createLoggerFn(level: LogLevel): LoggerFn { - const MAX_WIDTH = Math.max(...LogLevels.map(l => l.length)); - const tokens = chalk.green( - [ - chalk.cyan(dayjs().format("HH:mm:ss.SSS")), - LOG_LEVEL_COLOR_MAP[level](level.toUpperCase().padEnd(MAX_WIDTH, " ")), - ] - .map(t => `[${t}]`) - .join(""), - ); + const levelString = LOG_LEVEL_COLOR_MAP[level](level.toUpperCase()); return (content, breakLine = true) => { + const tokens = chalk.green( + [chalk.cyan(dayjs().format("HH:mm:ss.SSS")), levelString].map(t => `[${t}]`).join(""), + ); + const formattedString = `${tokens} ${content}`; if (breakLine) { console.log(formattedString); @@ -47,6 +43,7 @@ export const Logger: LoggerType = (() => { error: createLoggerFn("error"), warn: createLoggerFn("warn"), silly: createLoggerFn("silly"), + debug: createLoggerFn("debug"), }; return { @@ -54,7 +51,13 @@ export const Logger: LoggerType = (() => { work: async (level: LogLevel, message: string, work: () => void | Promise, done = "done.") => { loggers[level](message, false); - const elapsedTime = await measureTime(() => work()); + const { elapsedTime, exception } = await measureTime(() => work()); + + if (exception) { + process.stdout.write(`failed. (${elapsedTime}ms)`); + loggers.error(`Last task failed with error: ${exception.message}`); + return; + } process.stdout.write(`${done} ${chalk.gray(`(${elapsedTime}ms)`)}\n`); }, diff --git a/src/utils/measureTime.ts b/src/utils/measureTime.ts index 5c9901a..6fc4520 100644 --- a/src/utils/measureTime.ts +++ b/src/utils/measureTime.ts @@ -2,10 +2,23 @@ import * as dayjs from "dayjs"; import { Work } from "@utils/type"; -export async function measureTime(work: Work) { +interface MeasureTimeResult { + readonly elapsedTime: number; + readonly exception?: Error; +} + +export async function measureTime(work: Work): Promise { const startedAt = dayjs(); - await work(); - const endedAt = dayjs(); + try { + await work(); + } catch (e) { + return { + elapsedTime: dayjs().diff(startedAt), + exception: e as Error, + }; + } - return endedAt.diff(startedAt); + return { + elapsedTime: dayjs().diff(startedAt), + }; } diff --git a/src/utils/type.ts b/src/utils/type.ts index bbd9c06..5a7d606 100644 --- a/src/utils/type.ts +++ b/src/utils/type.ts @@ -11,3 +11,16 @@ export type Env = { export interface ProviderInitializeContext { readonly env: Readonly; } + +export interface Follower { + readonly id: string; + readonly screenName: string; +} + +export interface Serializable { + serialize(): Record; +} + +export interface Hydratable { + hydrate(data: Record): void; +} diff --git a/src/watchers/base.ts b/src/watchers/base.ts index a50fe36..c78e6d3 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,9 +1,12 @@ -import { ProviderInitializeContext } from "@utils/type"; +import { Hydratable, ProviderInitializeContext, Serializable } from "@utils/type"; -export abstract class BaseWatcher { +export abstract class BaseWatcher implements Serializable, Hydratable { public abstract getName(): string; public abstract initialize(context: ProviderInitializeContext): Promise; public abstract finalize(): Promise; - public abstract startWatch(): Promise; + public abstract doWatch(): Promise; + + public abstract serialize(): Record; + public abstract hydrate(data: Record): void; } diff --git a/src/watchers/twitter/auth.ts b/src/watchers/twitter/auth.ts index 82bf79e..0260d28 100644 --- a/src/watchers/twitter/auth.ts +++ b/src/watchers/twitter/auth.ts @@ -3,14 +3,12 @@ import { Fn } from "@utils/type"; export class TwitterAuth { private readonly tasks: Array>> = []; - private csrf: string | null = null; private flowToken = ""; public constructor( private readonly fetcher: Fetcher, private readonly getHeaders: Fn<[], Record>, private readonly guestTokenHandler: Fn<[string], void>, - private readonly csrfHandler: Fn<[string], void>, ) {} public doInstrumentation() { @@ -121,7 +119,6 @@ export class TwitterAuth { ); this.flowToken = data.flow_token; - this.csrf = this.fetcher.getCookies()["ct0"] || this.csrf; }); return this; @@ -199,11 +196,5 @@ export class TwitterAuth { for (const task of this.tasks) { await task(); } - - if (!this.csrf) { - throw new Error("CSRF token not found"); - } - - this.csrfHandler(this.csrf); } } diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index 085b16a..b4aaa45 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -1,21 +1,33 @@ import { TwitterAuth } from "@watchers/twitter/auth"; import { TWITTER_AUTH_TOKEN } from "@watchers/twitter/constants"; +import { FollowerAPIResponse } from "@watchers/twitter/types"; import { generateRandomString } from "@utils/generateRandomString"; import { Fetcher } from "@utils/fetcher"; +import { Follower, Hydratable, Serializable } from "@utils/type"; +import { findBottomCursorFromFollower, findUserDataFromFollower } from "@watchers/twitter/utils"; -export class TwitterHelper { +type FollowerCursor = string; +type GetFollowersResult = [Follower[], (() => Promise) | null]; + +export class TwitterHelper implements Serializable, Hydratable { private readonly fetcher: Fetcher = new Fetcher(); + private readonly defaultCsrf = generateRandomString(32, "0123456789abcdefghijklmnopqrstuvwxyz"); private authToken: string = TWITTER_AUTH_TOKEN; - private csrf: string | null = generateRandomString(32, "0123456789abcdefghijklmnopqrstuvwxyz"); private guest: string | null = null; + private currentUserId: string | null = null; + + public get isLogged() { + return Boolean(this.currentUserId); + } private getHeaders() { const headers = { "Content-type": "application/json", "x-twitter-active-user": "yes", "x-twitter-client-language": "ko", + "x-csrf-token": this.fetcher.getCookies().ct0 || this.defaultCsrf, Referer: "https://twitter.com/i/flow/login", }; @@ -23,27 +35,106 @@ export class TwitterHelper { headers["authorization"] = `Bearer ${this.authToken}`; } - if (this.csrf) { - headers["x-csrf-token"] = this.csrf; - } - if (this.guest) { headers["x-guest-token"] = this.guest; } return headers; } + private getUserId() { + if (!this.currentUserId) { + const { twid } = this.fetcher.getCookies(); + if (!twid) { + throw new Error("twid cookie not found"); + } + + const [, userId] = twid.replace(/"/g, "").split("="); + this.currentUserId = userId; + } + + return this.currentUserId; + } public doAuth() { - return new TwitterAuth( - this.fetcher, - this.getHeaders.bind(this), - token => { - this.guest = token; - }, - token => { - this.csrf = token; + return new TwitterAuth(this.fetcher, this.getHeaders.bind(this), token => (this.guest = token)); + } + public async getFollowers(cursor: FollowerCursor | null = null): Promise { + const variables: Record = { + userId: this.getUserId(), + count: 100, + includePromotedContent: false, + withSuperFollowsUserFields: true, + withDownvotePerspective: false, + withReactionsMetadata: false, + withReactionsPerspective: false, + withSuperFollowsTweetFields: true, + }; + + if (cursor) { + variables.cursor = cursor; + } + + const features = { + responsive_web_twitter_blue_verified_badge_is_enabled: true, + verified_phone_label_enabled: false, + responsive_web_graphql_timeline_navigation_enabled: true, + unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: true, + tweetypie_unmention_optimization_enabled: true, + responsive_web_uc_gql_enabled: true, + vibe_api_enabled: true, + responsive_web_edit_tweet_api_enabled: true, + graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, + standardized_nudges_misinfo: true, + tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false, + interactive_text_enabled: true, + responsive_web_text_conversations_enabled: false, + responsive_web_enhance_cards_enabled: true, + }; + + const params = new URLSearchParams({ + variables: JSON.stringify(variables), + features: JSON.stringify(features), + }); + + const data = await this.fetcher.fetchJson( + `https://twitter.com/i/api/graphql/_gXC5CopoM8fIgawvyGpIg/Followers?${params.toString()}`, + { + headers: this.getHeaders(), + method: "GET", }, ); + + const bottomCursor = findBottomCursorFromFollower(data); + if (!bottomCursor) { + throw new Error("Failed to find bottom cursor"); + } + + const users = findUserDataFromFollower(data); + if (!users) { + throw new Error("Failed to find users"); + } + + return [ + users.map(u => ({ + id: u.name, + screenName: u.screen_name, + })), + users.length >= 100 ? this.getFollowers.bind(this, bottomCursor) : null, + ]; + } + + public serialize(): Record { + return { + fetcher: this.fetcher.serialize(), + authToken: this.authToken, + guest: this.guest, + currentUserId: this.currentUserId, + }; + } + public hydrate(data: Record): void { + this.fetcher.hydrate(data.fetcher as Record); + this.authToken = data.authToken as string; + this.guest = data.guest as string | null; + this.currentUserId = data.currentUserId as string | null; } } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 551975b..a7e5356 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -1,7 +1,8 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ import { BaseWatcher } from "@watchers/base"; import { TwitterHelper } from "@watchers/twitter/helper"; -import { ProviderInitializeContext } from "@utils/type"; +import { Follower, ProviderInitializeContext } from "@utils/type"; export type TWITTER_PROVIDER_ENV_KEYS = "TWITTER_USER_ID" | "TWITTER_PASSWORD"; @@ -23,14 +24,39 @@ export class TwitterWatcher extends BaseWatcher { throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); } - await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); + if (!this.signHelper.isLogged) { + await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); + } } - - // eslint-disable-next-line @typescript-eslint/no-empty-function public async finalize(): Promise {} - public async startWatch(): Promise { - return Promise.resolve(undefined); + public async doWatch(): Promise {} + + public serialize(): Record { + return { + helper: this.signHelper.serialize(), + }; + } + public hydrate(data: Record): void { + this.signHelper.hydrate(data.helper); + } + + private async getAllFollowers() { + const result = await this.signHelper.getFollowers(); + const followers: Follower[] = [...result[0]]; + let next = result[1]; + while (true) { + if (!next) { + break; + } + + const [nextFollowers, nextFunction] = await next(); + followers.push(...nextFollowers); + + next = nextFunction; + } + + return followers; } private async login(userId: string, password: string) { diff --git a/src/watchers/twitter/types.ts b/src/watchers/twitter/types.ts new file mode 100644 index 0000000..ba2e416 --- /dev/null +++ b/src/watchers/twitter/types.ts @@ -0,0 +1,77 @@ +export interface FollowerAPIResponse { + data: { + user: { + result: { + __typename: string; + timeline: { + timeline: { + instructions: Array; + }; + }; + }; + }; + }; +} +export interface AddEntryInstruction { + type: "TimelineAddEntries"; + entries: InstructionEntry[]; +} +export interface OtherInstruction { + type: "TimelineClearCache" | "TimelineTerminateTimeline"; +} +export interface InstructionEntry { + entryId: string; + sortIndex: string; + content: TimelineItemContent | TimelineCursorContent; +} +export interface FollowerUser { + legacy: { + blocked_by: boolean; + blocking: boolean; + can_dm: boolean; + can_media_tag: boolean; + created_at: string; + default_profile: boolean; + default_profile_image: boolean; + description: string; + fast_followers_count: number; + favourites_count: number; + follow_request_sent: boolean; + followed_by: boolean; + followers_count: number; + following: boolean; + friends_count: number; + has_custom_timelines: boolean; + is_translator: boolean; + listed_count: number; + location: string; + media_count: number; + muting: boolean; + name: string; + normal_followers_count: number; + notifications: boolean; + possibly_sensitive: boolean; + profile_banner_url: string; + profile_image_url_https: string; + profile_interstitial_type: string; + protected: boolean; + screen_name: string; + statuses_count: number; + translator_type: string; + verified: boolean; + want_retweets: boolean; + }; +} +export interface TimelineItemContent { + entryType: "TimelineTimelineItem"; + itemContent: { + user_results: { + result: FollowerUser; + }; + }; +} +export interface TimelineCursorContent { + entryType: "TimelineTimelineCursor"; + cursorType: "Bottom" | "Top"; + value: string; +} diff --git a/src/watchers/twitter/utils.ts b/src/watchers/twitter/utils.ts new file mode 100644 index 0000000..a42ee59 --- /dev/null +++ b/src/watchers/twitter/utils.ts @@ -0,0 +1,55 @@ +import { + AddEntryInstruction, + FollowerAPIResponse, + TimelineCursorContent, + TimelineItemContent, +} from "@watchers/twitter/types"; + +export function findAddEntryInstructionFromFollower(follower: FollowerAPIResponse): AddEntryInstruction | null { + const { instructions } = follower.data.user.result.timeline.timeline; + const addEntryInstruction = instructions.find(instruction => instruction.type === "TimelineAddEntries") as + | AddEntryInstruction + | undefined; + + if (!addEntryInstruction) { + return null; + } + + return addEntryInstruction; +} + +export function findBottomCursorFromFollower(response: FollowerAPIResponse) { + const addEntryInstruction = findAddEntryInstructionFromFollower(response); + if (!addEntryInstruction) { + return null; + } + + const entries = addEntryInstruction.entries; + const cursors = entries + .map(entry => entry.content) + .filter((content): content is TimelineCursorContent => { + return content.entryType === "TimelineTimelineCursor"; + }); + + const bottomCursor = cursors.find(cursor => cursor.cursorType === "Bottom"); + if (!bottomCursor) { + return null; + } + + return bottomCursor.value; +} + +export function findUserDataFromFollower(response: FollowerAPIResponse) { + const addEntryInstruction = findAddEntryInstructionFromFollower(response); + if (!addEntryInstruction) { + return null; + } + + const entries = addEntryInstruction.entries; + return entries + .map(entry => entry.content) + .filter((content): content is TimelineItemContent => { + return content.entryType === "TimelineTimelineItem"; + }) + .map(content => content.itemContent.user_results.result.legacy); +} diff --git a/yarn.lock b/yarn.lock index 108a0e5..e0772a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1986,6 +1986,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cronstrue@^2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.20.0.tgz#709ef674ccf385f3c0e77c40a5141911e38b5d05" + integrity sha512-cGGxqXGt+yf46nQw2nipLpQLaqlXbeAdcR2HoHYar91bpWRFViw16XCwbSLs0uwhzzYjHwN9BVw5SYIXNqIFwg== + cross-fetch@3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" From 07f98fb9e6d8db32564afd5125cc6b9aaf16efe1 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 30 Nov 2022 17:13:57 +0900 Subject: [PATCH 005/140] refactor(core): add basic watching feature through cron --- package.json | 2 ++ src/index.ts | 73 +++++++++++++++++++++++++++++++++++++++------------- yarn.lock | 17 ++++++++++++ 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 05ddd12..24cf953 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/listr": "^0.14.4", "@types/lodash.chunk": "^4.2.7", "@types/node": "^18.6.4", + "@types/node-cron": "^3.0.6", "@types/node-fetch": "^2.6.2", "@types/prompts": "^2.0.14", "@types/replace-ext": "^2.0.0", @@ -60,6 +61,7 @@ "chalk": "^4.1.2", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", + "node-cron": "^3.0.2", "node-fetch": "^2.6.7", "puppeteer": "^19.3.0", "set-cookie-parser": "^2.5.1" diff --git a/src/index.ts b/src/index.ts index b31ade9..1a4a2fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import * as chalk from "chalk"; import * as fs from "fs-extra"; +import * as cron from "node-cron"; +import cronstrue from "cronstrue"; import { AVAILABLE_PROVIDERS } from "@watchers"; @@ -37,27 +39,62 @@ async function main() { }); } - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work("info", `finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { - await provider.finalize(); - }); - } + const providerNames = AVAILABLE_PROVIDERS.map(p => `\`${chalk.green(p.getName())}\``).join(", "); + const formattedCron = cronstrue.toString("0 * * * * *"); - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work( - "debug", - `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, - async () => { - const data = provider.serialize(); - const filePath = `./dump/${provider.getName().toLowerCase()}.json`; + Logger.info(`start to watch through ${providerNames} provider${AVAILABLE_PROVIDERS.length === 1 ? "" : "s"}:`); + Logger.info(` - watching cron rule: \`0 * * * * *\` ${chalk.gray(`(${formattedCron})`)}`); - await fs.ensureFile(filePath); - await fs.writeJson(filePath, data, { spaces: 4 }); - }, - ); - } + const task = cron.schedule("0 * * * * *", async () => { + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work( + "info", + `determine followers data through \`${chalk.green(provider.getName())}\` watcher ... `, + async () => { + await provider.doWatch(); + }, + ); + } + }); + + let cleaningUp = false; + const cleanUp = async () => { + if (cleaningUp) { + return; + } + + cleaningUp = true; + task.stop(); + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work("info", `finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { + await provider.finalize(); + }); + } + + for (const provider of AVAILABLE_PROVIDERS) { + await Logger.work( + "debug", + `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, + async () => { + const data = provider.serialize(); + const filePath = `./dump/${provider.getName().toLowerCase()}.json`; + + await fs.ensureFile(filePath); + await fs.writeJson(filePath, data, { spaces: 4 }); + }, + ); + } + + Logger.info("Bye World from Cage! 🐦"); + }; - Logger.info("Bye World from Cage! 🐦"); + process.on("exit", cleanUp); + process.on("SIGTERM", cleanUp); + process.on("SIGINT", cleanUp); + process.on("SIGUSR1", cleanUp); + process.on("SIGUSR2", cleanUp); + process.on("uncaughtException", cleanUp); } main(); diff --git a/yarn.lock b/yarn.lock index e0772a6..dd748db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/node-cron@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.6.tgz#689d9db9a9b4fabd5a0137fe9cb927f81955b1af" + integrity sha512-Qu9dpjkgj2JmzRmDMVzpt2dFKuJ7wma0mxEvbbgomwkhAdHKT2LpSLYHawzd9OeeP4HsyhmcV9o/xLgJyPNcgw== + "@types/node-fetch@^2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" @@ -4093,6 +4098,13 @@ nerf-dart@^1.0.0: resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" integrity sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g== +node-cron@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" + integrity sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ== + dependencies: + uuid "8.3.2" + node-emoji@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -5686,6 +5698,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" From 9498d915c7c23382f46c59055e9668466cce02b1 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 1 Dec 2022 17:21:59 +0900 Subject: [PATCH 006/140] ci: update project name for codecov configuration --- .github/workflows/ci.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a304770..5b14d3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - name: solv + name: cage - name: Build run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b809943..96b0a21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,7 @@ jobs: uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - name: solv + name: cage - uses: sarisia/actions-status-discord@v1 if: always() From 8265e554e0abafb965f4ee561539c17f20d149ff Mon Sep 17 00:00:00 2001 From: Sophia Date: Fri, 2 Dec 2022 10:25:43 +0900 Subject: [PATCH 007/140] feat(core): implement (un)follower detection through database --- .gitignore | 1 + package.json | 9 +- src/app.ts | 199 +++++++++++++++ src/index.ts | 101 +------- src/models/follower-log.ts | 38 +++ src/models/follower.ts | 33 +++ src/utils/getFollowerDiff.ts | 23 ++ src/utils/logger.ts | 115 +++++---- src/utils/measureTime.ts | 24 +- src/utils/type.ts | 17 +- src/watchers/base.ts | 16 +- src/watchers/index.ts | 2 +- src/watchers/twitter/helper.ts | 35 ++- src/watchers/twitter/index.ts | 55 ++--- tsconfig.json | 4 +- yarn.lock | 431 +++++++++++++++++++++++++++++++-- 16 files changed, 878 insertions(+), 225 deletions(-) create mode 100644 src/app.ts create mode 100644 src/models/follower-log.ts create mode 100644 src/models/follower.ts create mode 100644 src/utils/getFollowerDiff.ts diff --git a/.gitignore b/.gitignore index 4e65f4f..fbd1ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /.env /followers.json /dump +/database.sqlite \ No newline at end of file diff --git a/package.json b/package.json index 24cf953..138f4ce 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,9 @@ "@types/fs-extra": "^9.0.13", "@types/jest": "^29.1.1", "@types/listr": "^0.14.4", + "@types/lodash": "^4.14.191", "@types/lodash.chunk": "^4.2.7", - "@types/node": "^18.6.4", + "@types/node": "^18.11.10", "@types/node-cron": "^3.0.6", "@types/node-fetch": "^2.6.2", "@types/prompts": "^2.0.14", @@ -61,9 +62,13 @@ "chalk": "^4.1.2", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", + "lodash": "^4.17.21", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", "puppeteer": "^19.3.0", - "set-cookie-parser": "^2.5.1" + "reflect-metadata": "^0.1.13", + "set-cookie-parser": "^2.5.1", + "sqlite3": "^5.1.2", + "typeorm": "^0.3.10" } } diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..81abf60 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,199 @@ +import * as _ from "lodash"; +import { DataSource } from "typeorm"; +import * as chalk from "chalk"; +import * as fs from "fs-extra"; +import * as path from "path"; + +import { AVAILABLE_WATCHERS } from "@watchers"; + +import { Follower } from "@models/follower"; +import { FollowerLog, FollowerLogType } from "@models/follower-log"; + +import { getFollowerDiff } from "@utils/getFollowerDiff"; +import { Env, Loggable, ProviderInitializeContext } from "@utils/type"; +import { sleep } from "@utils/sleep"; + +export class App extends Loggable { + private readonly followerDataSource: DataSource; + private readonly initializeContext: Readonly = { + env: process.env as Env, + }; + + private cleaningUp = false; + + public constructor() { + super("App"); + + this.followerDataSource = new DataSource({ + type: "sqlite", + database: path.join(process.cwd(), "./data.sqlite"), + entities: [Follower, FollowerLog], + dropSchema: true, + synchronize: true, + }); + + process.on("exit", this.cleanUp.bind(this)); + process.on("SIGTERM", this.cleanUp.bind(this)); + process.on("SIGINT", this.cleanUp.bind(this)); + process.on("SIGUSR1", this.cleanUp.bind(this)); + process.on("SIGUSR2", this.cleanUp.bind(this)); + process.on("uncaughtException", this.cleanUp.bind(this)); + } + + public async run() { + this.logger.clear(); + this.logger.info("Hello World from Cage! 🐦"); + + await this.logger.work({ + level: "info", + message: "initialize database ... ", + work: () => this.followerDataSource.initialize(), + }); + + for (const provider of AVAILABLE_WATCHERS) { + await this.logger.work({ + level: "debug", + message: `loading lastly dumped data of \`${chalk.green(provider.getName())}\` watcher ... `, + work: async () => { + const fileName = `${provider.getName().toLowerCase()}.json`; + const filePath = `./dump/${fileName}`; + + if (!fs.existsSync(filePath)) { + throw new Error(`Dump file \`${fileName}\` does not exist.`); + } + + provider.hydrate(await fs.readJSON(filePath)); + }, + }); + } + + for (const provider of AVAILABLE_WATCHERS) { + await this.logger.work({ + level: "info", + message: `initialize \`${chalk.green(provider.getName())}\` watcher ... `, + work: () => provider.initialize(this.initializeContext), + }); + } + + const providerNames = AVAILABLE_WATCHERS.map(p => `\`${chalk.green(p.getName())}\``).join(", "); + + this.logger.info( + `start to watch through ${providerNames} watchers${AVAILABLE_WATCHERS.length === 1 ? "" : "s"}.`, + ); + + while (true) { + try { + await this.onCycle(); + } catch (e) { + if (!(e instanceof Error)) { + throw e; + } + + this.logger.error(`An error occurred while processing scheduled task: ${e.message}`); + } + + this.logger.info("watching task done. now wait for 1 minute."); + await sleep(60000); + } + } + + private async cleanUp() { + if (this.cleaningUp) { + return; + } + + this.cleaningUp = true; + + for (const provider of AVAILABLE_WATCHERS) { + await this.logger.work({ + level: "info", + message: `finalize \`${chalk.green(provider.getName())}\` watcher ... `, + work: () => provider.finalize(), + }); + } + + for (const provider of AVAILABLE_WATCHERS) { + await this.logger.work({ + level: "debug", + message: `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, + work: async () => { + const data = provider.serialize(); + const filePath = `./dump/${provider.getName().toLowerCase()}.json`; + + await fs.ensureFile(filePath); + await fs.writeJson(filePath, data, { spaces: 4 }); + }, + }); + } + + this.logger.info("Bye World from Cage! 🐦"); + } + + private onCycle = async () => { + for (const provider of AVAILABLE_WATCHERS) { + const currentTime = new Date(); + const providerName = provider.getName(); + const nameToken = `\`${chalk.green(providerName)}\``; + const newFollowers = await this.logger.work({ + level: "info", + message: `determine followers data through ${nameToken} watcher ... `, + work: () => provider.doWatch(), + }); + + if (!newFollowers) { + this.logger.error(`no followers data was returned from ${nameToken} watcher.`); + continue; + } + + const allFollowers = await Follower.find({ + where: { from: providerName }, + relations: ["followerLogs"], + }); + const oldFollowers = allFollowers.filter(f => f.isFollowing); + const followerLogsMap = _.chain(allFollowers) + .keyBy("id") + .mapValues(f => f.followerLogs) + .value(); + + const { removed, added } = getFollowerDiff(oldFollowers, newFollowers); + + if (added.length > 0) { + for (const follower of added) { + const newLog = new FollowerLog(); + newLog.type = FollowerLogType.Follow; + newLog.createdAt = currentTime; + + follower.isFollowing = true; + follower.followerLogs = [...(followerLogsMap[follower.id] || []), newLog]; + } + + await Follower.save(added); + } + + if (removed.length > 0) { + for (const follower of removed) { + const newLog = new FollowerLog(); + newLog.type = FollowerLogType.Unfollow; + newLog.createdAt = currentTime; + + follower.isFollowing = false; + follower.followerLogs = [...followerLogsMap[follower.id], newLog]; + } + + await Follower.save(removed); + } + + if (added.length === 0 && removed.length === 0) { + this.logger.info(`no changes found from ${nameToken} watcher.`); + } else { + this.logger.info( + `found ${chalk.green(added.length)} new follower(s) and ${chalk.red( + removed.length, + )} removed follower(s).`, + ); + } + + await Follower.update({ from: providerName }, { lastlyCheckedAt: currentTime }); + } + }; +} diff --git a/src/index.ts b/src/index.ts index 1a4a2fb..f4cbdd9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,100 +1,3 @@ -import * as chalk from "chalk"; -import * as fs from "fs-extra"; -import * as cron from "node-cron"; -import cronstrue from "cronstrue"; +import { App } from "@root/app"; -import { AVAILABLE_PROVIDERS } from "@watchers"; - -import { Logger } from "@utils/logger"; -import { Env, ProviderInitializeContext } from "@utils/type"; - -async function main() { - Logger.clear(); - Logger.info("Hello World from Cage! 🐦"); - - const initializeContext: ProviderInitializeContext = { - env: process.env as Env, - }; - - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work( - "debug", - `loading lastly dumped data of \`${chalk.green(provider.getName())}\` watcher ... `, - async () => { - const fileName = `${provider.getName().toLowerCase()}.json`; - const filePath = `./dump/${fileName}`; - - if (!fs.existsSync(filePath)) { - throw new Error(`Dump file \`${fileName}\` does not exist.`); - } - - provider.hydrate(await fs.readJSON(filePath)); - }, - ); - } - - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work("info", `initialize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { - await provider.initialize(initializeContext); - }); - } - - const providerNames = AVAILABLE_PROVIDERS.map(p => `\`${chalk.green(p.getName())}\``).join(", "); - const formattedCron = cronstrue.toString("0 * * * * *"); - - Logger.info(`start to watch through ${providerNames} provider${AVAILABLE_PROVIDERS.length === 1 ? "" : "s"}:`); - Logger.info(` - watching cron rule: \`0 * * * * *\` ${chalk.gray(`(${formattedCron})`)}`); - - const task = cron.schedule("0 * * * * *", async () => { - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work( - "info", - `determine followers data through \`${chalk.green(provider.getName())}\` watcher ... `, - async () => { - await provider.doWatch(); - }, - ); - } - }); - - let cleaningUp = false; - const cleanUp = async () => { - if (cleaningUp) { - return; - } - - cleaningUp = true; - task.stop(); - - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work("info", `finalize \`${chalk.green(provider.getName())}\` watcher ... `, async () => { - await provider.finalize(); - }); - } - - for (const provider of AVAILABLE_PROVIDERS) { - await Logger.work( - "debug", - `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, - async () => { - const data = provider.serialize(); - const filePath = `./dump/${provider.getName().toLowerCase()}.json`; - - await fs.ensureFile(filePath); - await fs.writeJson(filePath, data, { spaces: 4 }); - }, - ); - } - - Logger.info("Bye World from Cage! 🐦"); - }; - - process.on("exit", cleanUp); - process.on("SIGTERM", cleanUp); - process.on("SIGINT", cleanUp); - process.on("SIGUSR1", cleanUp); - process.on("SIGUSR2", cleanUp); - process.on("uncaughtException", cleanUp); -} - -main(); +new App().run().then(); diff --git a/src/models/follower-log.ts b/src/models/follower-log.ts new file mode 100644 index 0000000..b43a8a2 --- /dev/null +++ b/src/models/follower-log.ts @@ -0,0 +1,38 @@ +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + RelationId, + UpdateDateColumn, +} from "typeorm"; + +import { Follower } from "@models/follower"; + +export enum FollowerLogType { + Follow = "follow", + Unfollow = "unfollow", +} + +@Entity({ name: "follower-logs" }) +export class FollowerLog extends BaseEntity { + @PrimaryGeneratedColumn() + public id!: number; + + @Column({ type: "varchar", length: 255 }) + public type!: FollowerLogType; + + @CreateDateColumn() + public createdAt!: Date; + + @UpdateDateColumn() + public updatedAt!: Date; + + @ManyToOne(() => Follower, follower => follower.followerLogs) + public follower!: Follower; + + @RelationId((followerLog: FollowerLog) => followerLog.follower) + public followerId!: string; +} diff --git a/src/models/follower.ts b/src/models/follower.ts new file mode 100644 index 0000000..13693bb --- /dev/null +++ b/src/models/follower.ts @@ -0,0 +1,33 @@ +import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, RelationId, UpdateDateColumn } from "typeorm"; + +import { FollowerLog } from "@models/follower-log"; + +@Entity({ name: "followers" }) +export class Follower extends BaseEntity { + @Column({ type: "varchar", length: 255, primary: true }) + public id!: string; + + @Column({ type: "varchar", length: 255 }) + public from!: string; + + @Column({ type: "varchar", length: 255 }) + public displayName!: string; + + @Column({ type: "boolean" }) + public isFollowing!: boolean; + + @Column({ type: "datetime" }) + public lastlyCheckedAt!: Date; + + @CreateDateColumn() + public createdAt!: Date; + + @UpdateDateColumn() + public updatedAt!: Date; + + @OneToMany(() => FollowerLog, followerLog => followerLog.follower, { cascade: true }) + public followerLogs!: FollowerLog[]; + + @RelationId((follower: Follower) => follower.followerLogs) + public followerLogIds!: number[]; +} diff --git a/src/utils/getFollowerDiff.ts b/src/utils/getFollowerDiff.ts new file mode 100644 index 0000000..41ecc0c --- /dev/null +++ b/src/utils/getFollowerDiff.ts @@ -0,0 +1,23 @@ +import * as _ from "lodash"; + +import { Follower } from "@models/follower"; + +export function getFollowerDiff(oldFollowers: Follower[], newFollowers: Follower[]) { + const oldFollowerMap = _.chain(oldFollowers).keyBy("id").value(); + const newFollowerMap = _.chain(newFollowers).keyBy("id").value(); + + // get old & new follower ids in different variable + const oldFollowerIds = oldFollowers.map(follower => follower.id); + const newFollowerIds = newFollowers.map(follower => follower.id); + + // get new follower ids + const newFollowerIdsOnly = newFollowerIds.filter(id => !oldFollowerIds.includes(id)); + + // get removed follower ids + const removedFollowerIdsOnly = oldFollowerIds.filter(id => !newFollowerIds.includes(id)); + + return { + added: newFollowerIdsOnly.map(id => newFollowerMap[id]), + removed: removedFollowerIdsOnly.map(id => oldFollowerMap[id]), + }; +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 03ddf58..7351380 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -8,61 +8,94 @@ type LoggerFn = (content: string, breakLine?: boolean) => void; type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; const LOG_LEVEL_COLOR_MAP: Record> = { - silly: chalk.cyan, + silly: chalk.blue, info: chalk.cyan, warn: chalk.yellow, error: chalk.red, debug: chalk.magenta, }; -function createLoggerFn(level: LogLevel): LoggerFn { - const levelString = LOG_LEVEL_COLOR_MAP[level](level.toUpperCase()); +interface WorkOptions { + level: LogLevel; + message: string; + failedLevel?: LogLevel; + done?: string; + work: () => T | Promise; +} - return (content, breakLine = true) => { - const tokens = chalk.green( - [chalk.cyan(dayjs().format("HH:mm:ss.SSS")), levelString].map(t => `[${t}]`).join(""), - ); +export class Logger implements Record { + private static readonly buffer: string[] = []; + private static isLocked = false; - const formattedString = `${tokens} ${content}`; - if (breakLine) { - console.log(formattedString); - } else { - process.stdout.write(formattedString); + private static setLock(lock: boolean) { + if (!lock && Logger.isLocked) { + Logger.buffer.forEach(message => process.stdout.write(message)); + Logger.buffer.length = 0; } - }; -} -interface LoggerType extends Record { - clear(): void; - work(level: LogLevel, message: string, work: () => void | Promise, done?: string): Promise; -} + Logger.isLocked = lock; + } -export const Logger: LoggerType = (() => { - const loggers: Record = { - info: createLoggerFn("info"), - error: createLoggerFn("error"), - warn: createLoggerFn("warn"), - silly: createLoggerFn("silly"), - debug: createLoggerFn("debug"), - }; + public readonly info: LoggerFn; + public readonly warn: LoggerFn; + public readonly error: LoggerFn; + public readonly debug: LoggerFn; + public readonly silly: LoggerFn; + + public constructor(private readonly name: string) { + this.info = this.createLoggerFunction("info"); + this.warn = this.createLoggerFunction("warn"); + this.error = this.createLoggerFunction("error"); + this.debug = this.createLoggerFunction("debug"); + this.silly = this.createLoggerFunction("silly"); + } - return { - ...loggers, - work: async (level: LogLevel, message: string, work: () => void | Promise, done = "done.") => { - loggers[level](message, false); + private createLoggerFunction = (level: LogLevel): LoggerFn => { + const levelString = LOG_LEVEL_COLOR_MAP[level](level.toUpperCase()); - const { elapsedTime, exception } = await measureTime(() => work()); + return (content, breakLine = true) => { + const tokens = chalk.green( + [chalk.cyan(dayjs().format("HH:mm:ss.SSS")), chalk.yellow(this.name), levelString] + .map(t => `[${t}]`) + .join(""), + ); - if (exception) { - process.stdout.write(`failed. (${elapsedTime}ms)`); - loggers.error(`Last task failed with error: ${exception.message}`); - return; + const formattedString = `${tokens} ${content}${breakLine ? "\n" : ""}`; + if (Logger.isLocked) { + Logger.buffer.push(formattedString); + } else { + process.stdout.write(formattedString); } + }; + }; - process.stdout.write(`${done} ${chalk.gray(`(${elapsedTime}ms)`)}\n`); - }, - clear: () => { - console.clear(); - }, + public clear = () => { + console.clear(); }; -})(); + + public work = async ({ + work, + failedLevel = "error", + level, + message, + done = "done.", + }: WorkOptions): Promise => { + this[level](message, false); + + Logger.setLock(true); + const measuredData = await measureTime(work); + const time = chalk.gray(`(${measuredData.elapsedTime}ms)`); + + if ("exception" in measuredData) { + process.stdout.write(`failed. ${time}\n`); + this[failedLevel](`Last task failed with error: ${measuredData.exception.message}`); + Logger.setLock(false); + return null; + } + + process.stdout.write(`${done} ${time}\n`); + Logger.setLock(false); + + return measuredData.data; + }; +} diff --git a/src/utils/measureTime.ts b/src/utils/measureTime.ts index 6fc4520..b3d1db8 100644 --- a/src/utils/measureTime.ts +++ b/src/utils/measureTime.ts @@ -2,23 +2,31 @@ import * as dayjs from "dayjs"; import { Work } from "@utils/type"; -interface MeasureTimeResult { +interface SucceededMeasureTimeResult { readonly elapsedTime: number; - readonly exception?: Error; + readonly data: T; } -export async function measureTime(work: Work): Promise { +interface FailedMeasureTimeResult { + readonly elapsedTime: number; + readonly exception: Error; +} + +export type MeasureTimeResult = SucceededMeasureTimeResult | FailedMeasureTimeResult; + +export async function measureTime(work: Work): Promise> { const startedAt = dayjs(); try { - await work(); + const result = await work(); + + return { + elapsedTime: dayjs().diff(startedAt), + data: result, + }; } catch (e) { return { elapsedTime: dayjs().diff(startedAt), exception: e as Error, }; } - - return { - elapsedTime: dayjs().diff(startedAt), - }; } diff --git a/src/utils/type.ts b/src/utils/type.ts index 5a7d606..ab0399e 100644 --- a/src/utils/type.ts +++ b/src/utils/type.ts @@ -1,7 +1,9 @@ import type { TWITTER_PROVIDER_ENV_KEYS } from "@watchers/twitter"; +import { Logger } from "@utils/logger"; export type Fn = (...arg: TArg) => TReturn; -export type Work = Fn<[], Promise | void>; +export type Work = Fn<[], Promise | T>; +export type Nullable = T | null | undefined; export type KnownEnvKeys = `CAGE_${TWITTER_PROVIDER_ENV_KEYS}`; export type Env = { @@ -12,11 +14,6 @@ export interface ProviderInitializeContext { readonly env: Readonly; } -export interface Follower { - readonly id: string; - readonly screenName: string; -} - export interface Serializable { serialize(): Record; } @@ -24,3 +21,11 @@ export interface Serializable { export interface Hydratable { hydrate(data: Record): void; } + +export abstract class Loggable { + protected readonly logger: Logger; + + protected constructor(protected readonly name: string) { + this.logger = new Logger(name); + } +} diff --git a/src/watchers/base.ts b/src/watchers/base.ts index c78e6d3..5cc948c 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,11 +1,19 @@ -import { Hydratable, ProviderInitializeContext, Serializable } from "@utils/type"; +import { Hydratable, Loggable, ProviderInitializeContext, Serializable } from "@utils/type"; +import { Follower } from "@root/models/follower"; -export abstract class BaseWatcher implements Serializable, Hydratable { - public abstract getName(): string; +export abstract class BaseWatcher extends Loggable implements Serializable, Hydratable { + protected constructor(name: string) { + super(name); + } + + public getName(): string { + return this.name; + } public abstract initialize(context: ProviderInitializeContext): Promise; public abstract finalize(): Promise; - public abstract doWatch(): Promise; + + public abstract doWatch(): Promise; public abstract serialize(): Record; public abstract hydrate(data: Record): void; diff --git a/src/watchers/index.ts b/src/watchers/index.ts index 97db815..f284ddf 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,4 +1,4 @@ import { BaseWatcher } from "@watchers/base"; import { TwitterWatcher } from "@watchers/twitter"; -export const AVAILABLE_PROVIDERS: ReadonlyArray = [new TwitterWatcher()]; +export const AVAILABLE_WATCHERS: ReadonlyArray = [new TwitterWatcher()]; diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index b4aaa45..c839329 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -1,14 +1,14 @@ import { TwitterAuth } from "@watchers/twitter/auth"; import { TWITTER_AUTH_TOKEN } from "@watchers/twitter/constants"; -import { FollowerAPIResponse } from "@watchers/twitter/types"; +import { FollowerAPIResponse, FollowerUser } from "@watchers/twitter/types"; +import { findBottomCursorFromFollower, findUserDataFromFollower } from "@watchers/twitter/utils"; import { generateRandomString } from "@utils/generateRandomString"; import { Fetcher } from "@utils/fetcher"; -import { Follower, Hydratable, Serializable } from "@utils/type"; -import { findBottomCursorFromFollower, findUserDataFromFollower } from "@watchers/twitter/utils"; +import { Hydratable, Serializable } from "@utils/type"; type FollowerCursor = string; -type GetFollowersResult = [Follower[], (() => Promise) | null]; +type GetFollowersResult = [FollowerUser["legacy"][], (() => Promise) | null]; export class TwitterHelper implements Serializable, Hydratable { private readonly fetcher: Fetcher = new Fetcher(); @@ -50,6 +50,7 @@ export class TwitterHelper implements Serializable, Hydratable { const [, userId] = twid.replace(/"/g, "").split("="); this.currentUserId = userId; + console.log(userId); } return this.currentUserId; @@ -58,6 +59,7 @@ export class TwitterHelper implements Serializable, Hydratable { public doAuth() { return new TwitterAuth(this.fetcher, this.getHeaders.bind(this), token => (this.guest = token)); } + public async getFollowers(cursor: FollowerCursor | null = null): Promise { const variables: Record = { userId: this.getUserId(), @@ -114,13 +116,24 @@ export class TwitterHelper implements Serializable, Hydratable { throw new Error("Failed to find users"); } - return [ - users.map(u => ({ - id: u.name, - screenName: u.screen_name, - })), - users.length >= 100 ? this.getFollowers.bind(this, bottomCursor) : null, - ]; + return [users, users.length >= 100 ? this.getFollowers.bind(this, bottomCursor) : null]; + } + public async getAllFollowers(): Promise { + const result = await this.getFollowers(); + const followers: FollowerUser["legacy"][] = [...result[0]]; + let next = result[1]; + while (true) { + if (!next) { + break; + } + + const [nextFollowers, nextFunction] = await next(); + followers.push(...nextFollowers); + + next = nextFunction; + } + + return followers; } public serialize(): Record { diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index a7e5356..001c2af 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -2,19 +2,17 @@ import { BaseWatcher } from "@watchers/base"; import { TwitterHelper } from "@watchers/twitter/helper"; -import { Follower, ProviderInitializeContext } from "@utils/type"; +import { Follower } from "@models/follower"; + +import { ProviderInitializeContext } from "@utils/type"; export type TWITTER_PROVIDER_ENV_KEYS = "TWITTER_USER_ID" | "TWITTER_PASSWORD"; export class TwitterWatcher extends BaseWatcher { - private readonly signHelper: TwitterHelper = new TwitterHelper(); + private readonly helper: TwitterHelper = new TwitterHelper(); public constructor() { - super(); - } - - public getName(): string { - return "Twitter"; + super("Twitter"); } public async initialize({ env }: ProviderInitializeContext): Promise { @@ -24,43 +22,40 @@ export class TwitterWatcher extends BaseWatcher { throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); } - if (!this.signHelper.isLogged) { + if (!this.helper.isLogged) { await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); } } public async finalize(): Promise {} - public async doWatch(): Promise {} + public async doWatch() { + const checkedAt = new Date(); + const allFollowers = await this.helper.getAllFollowers(); + this.logger.silly(`Total followers: ${allFollowers.length}`); + + return allFollowers.map(user => { + const follower = Follower.create(); + follower.from = this.getName(); + follower.displayName = user.name; + follower.isFollowing = true; + follower.lastlyCheckedAt = checkedAt; + follower.id = `${follower.from}:${user.screen_name}`; + + return follower; + }); + } public serialize(): Record { return { - helper: this.signHelper.serialize(), + helper: this.helper.serialize(), }; } public hydrate(data: Record): void { - this.signHelper.hydrate(data.helper); - } - - private async getAllFollowers() { - const result = await this.signHelper.getFollowers(); - const followers: Follower[] = [...result[0]]; - let next = result[1]; - while (true) { - if (!next) { - break; - } - - const [nextFollowers, nextFunction] = await next(); - followers.push(...nextFollowers); - - next = nextFunction; - } - - return followers; + this.helper.hydrate(data.helper); } private async login(userId: string, password: string) { - await this.signHelper + await this.helper .doAuth() .doInstrumentation() .setUserId(userId) diff --git a/tsconfig.json b/tsconfig.json index e62a5e8..1893988 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,9 @@ "@root/*": ["./src/*"], "@utils/*": ["./src/utils/*"], "@watchers/*": ["./src/watchers/*"], - "@watchers": ["./src/watchers"] + "@watchers": ["./src/watchers"], + "@followers/*": ["./src/followers/*"], + "@models/*": ["./src/models/*"] } }, "exclude": ["node_modules", "**/*spec.ts", "./*.ts"] diff --git a/yarn.lock b/yarn.lock index dd748db..2403e40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -324,7 +324,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@gar/promisify@^1.1.3": +"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -610,6 +610,21 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -699,6 +714,14 @@ dependencies: ansi-styles "^4.3.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/fs@^2.1.0", "@npmcli/fs@^2.1.1": version "2.1.2" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" @@ -750,6 +773,14 @@ pacote "^13.0.3" semver "^7.3.5" +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@npmcli/move-file@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" @@ -1011,6 +1042,16 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sqltools/formatter@^1.2.2": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" + integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1135,6 +1176,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97" integrity sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw== +"@types/lodash@^4.14.191": + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" @@ -1153,11 +1199,16 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@^18.6.4": +"@types/node@*": version "18.8.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.3.tgz#ce750ab4017effa51aed6a7230651778d54e327c" integrity sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w== +"@types/node@^18.11.10": + version "18.11.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34" + integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -1341,7 +1392,7 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" -agentkeepalive@^4.2.1: +agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== @@ -1411,6 +1462,11 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -1419,6 +1475,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + "aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -1429,6 +1490,14 @@ archy@~1.0.0: resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -1654,6 +1723,14 @@ buffer@^5.2.1, buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtins@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" @@ -1661,6 +1738,30 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3: version "16.1.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" @@ -1804,6 +1905,18 @@ cli-columns@^4.0.0: string-width "^4.2.3" strip-ansi "^6.0.1" +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + cli-table3@^0.6.1, cli-table3@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" @@ -1877,7 +1990,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -1915,7 +2028,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -console-control-strings@^1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== @@ -2022,6 +2135,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +date-fns@^2.28.0: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -2125,6 +2243,11 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2179,7 +2302,7 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -dotenv@^16.0.3: +dotenv@^16.0.0, dotenv@^16.0.3: version "16.0.3" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== @@ -2206,7 +2329,7 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encoding@^0.1.13: +encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -2626,6 +2749,21 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" @@ -2693,7 +2831,7 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob@^7.1.3, glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -2789,6 +2927,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + hook-std@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hook-std/-/hook-std-2.0.0.tgz#ff9aafdebb6a989a354f729bb6445cf4a3a7077c" @@ -2823,6 +2966,15 @@ http-cache-semantics@^4.1.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -2859,7 +3011,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3814,7 +3966,7 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -3848,6 +4000,28 @@ make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6, make-fetch-happen@^10.2.0: socks-proxy-agent "^7.0.0" ssri "^9.0.0" +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -3984,6 +4158,17 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + minipass-fetch@^2.0.3: version "2.1.2" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" @@ -4010,7 +4195,7 @@ minipass-json-stream@^1.0.1: jsonparse "^1.3.1" minipass "^3.0.0" -minipass-pipeline@^1.2.4: +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== @@ -4031,7 +4216,14 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: dependencies: yallist "^4.0.0" -minizlib@^2.1.1, minizlib@^2.1.2: +minipass@^3.1.0, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -4078,12 +4270,21 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@^0.6.3: +negotiator@^0.6.2, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -4098,6 +4299,11 @@ nerf-dart@^1.0.0: resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" integrity sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g== +node-addon-api@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + node-cron@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" @@ -4119,6 +4325,22 @@ node-fetch@2.6.7, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-gyp@^9.0.0, node-gyp@^9.1.0: version "9.2.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.2.0.tgz#b3b56144828a98018a4cfb3033095e0f5b874d72" @@ -4161,6 +4383,13 @@ nodemon@^2.0.20: touch "^3.1.0" undefsafe "^2.0.5" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + nopt@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" @@ -4393,6 +4622,16 @@ npm@^8.3.0: which "^2.0.2" write-file-atomic "^4.0.1" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + npmlog@^6.0.0, npmlog@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" @@ -4403,6 +4642,11 @@ npmlog@^6.0.0, npmlog@^6.0.2: gauge "^4.0.3" set-blocking "^2.0.0" +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4589,6 +4833,23 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -4935,6 +5196,11 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -5019,21 +5285,26 @@ rxjs@^6.5.1: dependencies: tslib "^1.9.0" +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + semantic-release@^19.0.5: version "19.0.5" resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-19.0.5.tgz#d7fab4b33fc20f1288eafd6c441e5d0938e5e174" @@ -5112,6 +5383,14 @@ set-cookie-parser@^2.5.1: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5124,7 +5403,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5160,6 +5439,15 @@ smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + socks-proxy-agent@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" @@ -5247,6 +5535,24 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sqlite3@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.2.tgz#f50d5b1482b6972fb650daf6f718e6507c6cfb0f" + integrity sha512-D0Reg6pRWAFXFUnZKsszCI67tthFD8fGPewRddDCX6w4cYwz3MbvuwRICbL+YQjBAh9zbw+lJ/V9oC8nG5j6eg== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + node-addon-api "^4.2.0" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + ssri@^9.0.0, ssri@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" @@ -5394,6 +5700,18 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.0.2: + version "6.1.12" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" + integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" @@ -5449,6 +5767,20 @@ text-table@^0.2.0, text-table@~0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + through2@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" @@ -5565,6 +5897,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.3.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -5619,6 +5956,29 @@ type-fest@^1.0.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +typeorm@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.10.tgz#aa2857fd4b078c912ca693b7eee01b6535704458" + integrity sha512-VMKiM84EpJQ+Mz9xDIPqnfplWhyUy1d8ccaKdMY9obifxJOTFnv8GYVyPsGwG8Lk7Nb8MlttHyHWENGAhBA3WA== + dependencies: + "@sqltools/formatter" "^1.2.2" + app-root-path "^3.0.0" + buffer "^6.0.3" + chalk "^4.1.0" + cli-highlight "^2.1.11" + date-fns "^2.28.0" + debug "^4.3.3" + dotenv "^16.0.0" + glob "^7.2.0" + js-yaml "^4.1.0" + mkdirp "^1.0.4" + reflect-metadata "^0.1.13" + sha.js "^2.4.11" + tslib "^2.3.1" + uuid "^8.3.2" + xml2js "^0.4.23" + yargs "^17.3.1" + typescript@^4.8.3: version "4.8.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" @@ -5642,6 +6002,13 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" @@ -5649,6 +6016,13 @@ unique-filename@^2.0.0: dependencies: unique-slug "^3.0.0" +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + unique-slug@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" @@ -5698,7 +6072,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -uuid@8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -5771,7 +6145,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.5: +wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== @@ -5815,6 +6189,19 @@ ws@8.10.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -5845,7 +6232,7 @@ yargs-parser@^21.0.0, yargs-parser@^21.0.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^16.2.0: +yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== From 17286e7aae575662c797c1555bcc1f2ba4e050d3 Mon Sep 17 00:00:00 2001 From: Sophia Date: Fri, 2 Dec 2022 12:34:01 +0900 Subject: [PATCH 008/140] feat(config): implement basic file-based configuration feature --- .gitignore | 3 +- package.json | 3 ++ src/app.ts | 80 +++++++++++++++++++++++--------------- src/utils/config.ts | 95 +++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 57 ++++++++++++++++++++++++++- 5 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 src/utils/config.ts diff --git a/.gitignore b/.gitignore index fbd1ca2..5fd4ee6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ /.env /followers.json /dump -/database.sqlite \ No newline at end of file +/data.sqlite +/config.json \ No newline at end of file diff --git a/package.json b/package.json index 138f4ce..15f83c6 100644 --- a/package.json +++ b/package.json @@ -59,12 +59,15 @@ "typescript": "^4.8.3" }, "dependencies": { + "ajv": "^8.11.2", + "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", "lodash": "^4.17.21", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", + "pretty-ms": "^7.0.1", "puppeteer": "^19.3.0", "reflect-metadata": "^0.1.13", "set-cookie-parser": "^2.5.1", diff --git a/src/app.ts b/src/app.ts index 81abf60..9899dbc 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,8 +3,7 @@ import { DataSource } from "typeorm"; import * as chalk from "chalk"; import * as fs from "fs-extra"; import * as path from "path"; - -import { AVAILABLE_WATCHERS } from "@watchers"; +import * as prettyMilliseconds from "pretty-ms"; import { Follower } from "@models/follower"; import { FollowerLog, FollowerLogType } from "@models/follower-log"; @@ -12,6 +11,7 @@ import { FollowerLog, FollowerLogType } from "@models/follower-log"; import { getFollowerDiff } from "@utils/getFollowerDiff"; import { Env, Loggable, ProviderInitializeContext } from "@utils/type"; import { sleep } from "@utils/sleep"; +import { Config } from "@utils/config"; export class App extends Loggable { private readonly followerDataSource: DataSource; @@ -20,10 +20,10 @@ export class App extends Loggable { }; private cleaningUp = false; + private config: Config | null = null; public constructor() { super("App"); - this.followerDataSource = new DataSource({ type: "sqlite", database: path.join(process.cwd(), "./data.sqlite"), @@ -44,46 +44,57 @@ export class App extends Loggable { this.logger.clear(); this.logger.info("Hello World from Cage! 🐦"); + this.config = await Config.create(); + if (!this.config) { + process.exit(-1); + return; + } + + const targetWatchers = this.config.watchers; await this.logger.work({ level: "info", message: "initialize database ... ", work: () => this.followerDataSource.initialize(), }); - for (const provider of AVAILABLE_WATCHERS) { + for (const watcher of targetWatchers) { await this.logger.work({ level: "debug", - message: `loading lastly dumped data of \`${chalk.green(provider.getName())}\` watcher ... `, + message: `loading lastly dumped data of \`${chalk.green(watcher.getName())}\` watcher ... `, work: async () => { - const fileName = `${provider.getName().toLowerCase()}.json`; + const fileName = `${watcher.getName().toLowerCase()}.json`; const filePath = `./dump/${fileName}`; if (!fs.existsSync(filePath)) { throw new Error(`Dump file \`${fileName}\` does not exist.`); } - provider.hydrate(await fs.readJSON(filePath)); + watcher.hydrate(await fs.readJSON(filePath)); }, }); } - for (const provider of AVAILABLE_WATCHERS) { + for (const watcher of targetWatchers) { await this.logger.work({ level: "info", - message: `initialize \`${chalk.green(provider.getName())}\` watcher ... `, - work: () => provider.initialize(this.initializeContext), + message: `initialize \`${chalk.green(watcher.getName())}\` watcher ... `, + work: () => watcher.initialize(this.initializeContext), }); } - const providerNames = AVAILABLE_WATCHERS.map(p => `\`${chalk.green(p.getName())}\``).join(", "); - - this.logger.info( - `start to watch through ${providerNames} watchers${AVAILABLE_WATCHERS.length === 1 ? "" : "s"}.`, - ); + const watcherNames = targetWatchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); + this.logger.info(`start to watch through ${watcherNames} watchers${targetWatchers.length === 1 ? "" : "s"}.`); while (true) { try { await this.onCycle(); + + this.logger.info( + `watching task done. now wait for ${prettyMilliseconds(this.config.watchInterval, { + verbose: true, + })}.`, + ); + await sleep(this.config.watchInterval); } catch (e) { if (!(e instanceof Error)) { throw e; @@ -91,34 +102,36 @@ export class App extends Loggable { this.logger.error(`An error occurred while processing scheduled task: ${e.message}`); } - - this.logger.info("watching task done. now wait for 1 minute."); - await sleep(60000); } } private async cleanUp() { + if (!this.config) { + throw new Error("Config is not loaded."); + } + if (this.cleaningUp) { return; } this.cleaningUp = true; - for (const provider of AVAILABLE_WATCHERS) { + const targetWatchers = this.config.watchers; + for (const watcher of targetWatchers) { await this.logger.work({ level: "info", - message: `finalize \`${chalk.green(provider.getName())}\` watcher ... `, - work: () => provider.finalize(), + message: `finalize \`${chalk.green(watcher.getName())}\` watcher ... `, + work: () => watcher.finalize(), }); } - for (const provider of AVAILABLE_WATCHERS) { + for (const watcher of targetWatchers) { await this.logger.work({ level: "debug", - message: `saving serialized data of \`${chalk.green(provider.getName())}\` watcher ... `, + message: `saving serialized data of \`${chalk.green(watcher.getName())}\` watcher ... `, work: async () => { - const data = provider.serialize(); - const filePath = `./dump/${provider.getName().toLowerCase()}.json`; + const data = watcher.serialize(); + const filePath = `./dump/${watcher.getName().toLowerCase()}.json`; await fs.ensureFile(filePath); await fs.writeJson(filePath, data, { spaces: 4 }); @@ -130,14 +143,19 @@ export class App extends Loggable { } private onCycle = async () => { - for (const provider of AVAILABLE_WATCHERS) { + if (!this.config) { + throw new Error("Config is not loaded."); + } + + const targetWatchers = this.config.watchers; + for (const watcher of targetWatchers) { const currentTime = new Date(); - const providerName = provider.getName(); - const nameToken = `\`${chalk.green(providerName)}\``; + const watcherName = watcher.getName(); + const nameToken = `\`${chalk.green(watcherName)}\``; const newFollowers = await this.logger.work({ level: "info", message: `determine followers data through ${nameToken} watcher ... `, - work: () => provider.doWatch(), + work: () => watcher.doWatch(), }); if (!newFollowers) { @@ -146,7 +164,7 @@ export class App extends Loggable { } const allFollowers = await Follower.find({ - where: { from: providerName }, + where: { from: watcherName }, relations: ["followerLogs"], }); const oldFollowers = allFollowers.filter(f => f.isFollowing); @@ -193,7 +211,7 @@ export class App extends Loggable { ); } - await Follower.update({ from: providerName }, { lastlyCheckedAt: currentTime }); + await Follower.update({ from: watcherName }, { lastlyCheckedAt: currentTime }); } }; } diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..f218bd2 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,95 @@ +import * as path from "path"; +import * as fs from "fs-extra"; +import * as chalk from "chalk"; +import Ajv, { JSONSchemaType } from "ajv"; +import betterAjvErrors from "better-ajv-errors"; + +import { AVAILABLE_WATCHERS } from "@watchers"; + +import { Logger } from "@utils/logger"; + +const ajv = new Ajv({ allErrors: true }); + +interface RawConfigData { + target: string[]; + watchInterval: number; +} + +const schema: JSONSchemaType = { + type: "object", + properties: { + target: { + type: "array", + items: { + type: "string", + enum: AVAILABLE_WATCHERS.map(w => w.getName().toLowerCase()), + }, + }, + watchInterval: { + type: "integer", + }, + }, + required: ["target", "watchInterval"], + additionalProperties: false, +}; + +const validate = ajv.compile(schema); + +export class Config { + private static readonly logger = new Logger("Config"); + private static readonly DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./config.json"); + + public static async create(filePath: string = Config.DEFAULT_CONFIG_PATH) { + const pathToken = `\`${chalk.green(filePath)}\``; + + if (!fs.existsSync(filePath)) { + Config.logger.warn( + `config file ${pathToken} does not exist. we will make a new default config file for you.`, + ); + + await fs.writeJSON( + filePath, + { + targetProviders: ["twitter"], + watchInterval: 60000, + }, + { + spaces: 4, + }, + ); + + Config.logger.warn(`config file ${pathToken} has been created successfully.`); + } + + return Config.logger.work({ + level: "info", + message: `loading config file from ${pathToken} ... `, + work: async () => { + const data: RawConfigData = await fs.readJSON(filePath); + const valid = validate(data); + if (!valid && validate.errors) { + const output = betterAjvErrors(schema, data, validate.errors, { + format: "cli", + indent: 4, + }); + + throw new Error(`config file is invalid.\n${output}`); + } + + return new Config(data); + }, + }); + } + + public get watchers() { + return AVAILABLE_WATCHERS.filter(w => this.target.includes(w.getName().toLowerCase())); + } + public get target() { + return [...this.rawData.target]; + } + public get watchInterval() { + return this.rawData.watchInterval; + } + + private constructor(private readonly rawData: RawConfigData) {} +} diff --git a/yarn.lock b/yarn.lock index 2403e40..ce15be8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== @@ -343,6 +343,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== +"@humanwhocodes/momoa@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.4.tgz#8b9e7a629651d15009c3587d07a222deeb829385" + integrity sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -1419,6 +1424,16 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.11.2: + version "8.11.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" + integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1628,6 +1643,17 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== +better-ajv-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz#6412d58fa4d460ff6ccbd9e65c5fef9781cc5286" + integrity sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA== + dependencies: + "@babel/code-frame" "^7.16.0" + "@humanwhocodes/momoa" "^2.0.2" + chalk "^4.1.2" + jsonpointer "^5.0.0" + leven "^3.1.0 < 4" + bin-links@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" @@ -3692,6 +3718,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -3726,6 +3757,11 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + just-diff-apply@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" @@ -3746,7 +3782,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -leven@^3.1.0: +leven@^3.1.0, "leven@^3.1.0 < 4": version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== @@ -4833,6 +4869,11 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + parse5-htmlparser2-tree-adapter@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" @@ -4954,6 +4995,13 @@ pretty-format@^29.0.0, pretty-format@^29.1.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-ms@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== + dependencies: + parse-ms "^2.1.0" + proc-log@^2.0.0, proc-log@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" @@ -5218,6 +5266,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" From bb5954b1d06527ab15a046cef013c33a7707cefd Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 4 Dec 2022 18:51:06 +0900 Subject: [PATCH 009/140] refactor: update type `Fn` for better usage --- src/app.ts | 2 +- src/utils/fetcher.ts | 2 +- src/utils/logger.ts | 4 ++-- src/utils/measureTime.ts | 2 +- src/utils/{type.ts => types.ts} | 9 +++++++-- src/watchers/base.ts | 2 +- src/watchers/twitter/auth.ts | 8 ++++---- src/watchers/twitter/helper.ts | 2 +- src/watchers/twitter/index.ts | 2 +- 9 files changed, 19 insertions(+), 14 deletions(-) rename src/utils/{type.ts => types.ts} (69%) diff --git a/src/app.ts b/src/app.ts index 9899dbc..c313965 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,7 +9,7 @@ import { Follower } from "@models/follower"; import { FollowerLog, FollowerLogType } from "@models/follower-log"; import { getFollowerDiff } from "@utils/getFollowerDiff"; -import { Env, Loggable, ProviderInitializeContext } from "@utils/type"; +import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; import { sleep } from "@utils/sleep"; import { Config } from "@utils/config"; diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index d914159..0a2a15e 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,7 +1,7 @@ import fetch, { Headers, RequestInit, Response } from "node-fetch"; import { parseCookie } from "@utils/parseCookie"; -import { Hydratable, Serializable } from "@utils/type"; +import { Hydratable, Serializable } from "@utils/types"; export class Fetcher implements Serializable, Hydratable { private readonly cookies: Record = {}; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 7351380..1601f3d 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -2,12 +2,12 @@ import * as dayjs from "dayjs"; import * as chalk from "chalk"; import { measureTime } from "@utils/measureTime"; -import { Fn } from "@utils/type"; +import { Fn } from "@utils/types"; type LoggerFn = (content: string, breakLine?: boolean) => void; type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; -const LOG_LEVEL_COLOR_MAP: Record> = { +const LOG_LEVEL_COLOR_MAP: Record> = { silly: chalk.blue, info: chalk.cyan, warn: chalk.yellow, diff --git a/src/utils/measureTime.ts b/src/utils/measureTime.ts index b3d1db8..dd3be56 100644 --- a/src/utils/measureTime.ts +++ b/src/utils/measureTime.ts @@ -1,6 +1,6 @@ import * as dayjs from "dayjs"; -import { Work } from "@utils/type"; +import { Work } from "@utils/types"; interface SucceededMeasureTimeResult { readonly elapsedTime: number; diff --git a/src/utils/type.ts b/src/utils/types.ts similarity index 69% rename from src/utils/type.ts rename to src/utils/types.ts index ab0399e..eecf9b8 100644 --- a/src/utils/type.ts +++ b/src/utils/types.ts @@ -1,8 +1,13 @@ import type { TWITTER_PROVIDER_ENV_KEYS } from "@watchers/twitter"; import { Logger } from "@utils/logger"; -export type Fn = (...arg: TArg) => TReturn; -export type Work = Fn<[], Promise | T>; +export type Fn = TArgs extends unknown[] + ? (...arg: TArgs) => TReturn + : TArgs extends void + ? () => TReturn + : (...arg: [TArgs]) => TReturn; +export type AsyncFn = Fn>; +export type Work = Fn | T>; export type Nullable = T | null | undefined; export type KnownEnvKeys = `CAGE_${TWITTER_PROVIDER_ENV_KEYS}`; diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 5cc948c..2b53adb 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,4 +1,4 @@ -import { Hydratable, Loggable, ProviderInitializeContext, Serializable } from "@utils/type"; +import { Hydratable, Loggable, ProviderInitializeContext, Serializable } from "@utils/types"; import { Follower } from "@root/models/follower"; export abstract class BaseWatcher extends Loggable implements Serializable, Hydratable { diff --git a/src/watchers/twitter/auth.ts b/src/watchers/twitter/auth.ts index 0260d28..3967128 100644 --- a/src/watchers/twitter/auth.ts +++ b/src/watchers/twitter/auth.ts @@ -1,14 +1,14 @@ import { Fetcher } from "@utils/fetcher"; -import { Fn } from "@utils/type"; +import { AsyncFn, Fn } from "@utils/types"; export class TwitterAuth { - private readonly tasks: Array>> = []; + private readonly tasks: Array> = []; private flowToken = ""; public constructor( private readonly fetcher: Fetcher, - private readonly getHeaders: Fn<[], Record>, - private readonly guestTokenHandler: Fn<[string], void>, + private readonly getHeaders: Fn>, + private readonly guestTokenHandler: Fn, ) {} public doInstrumentation() { diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index c839329..059a218 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -5,7 +5,7 @@ import { findBottomCursorFromFollower, findUserDataFromFollower } from "@watcher import { generateRandomString } from "@utils/generateRandomString"; import { Fetcher } from "@utils/fetcher"; -import { Hydratable, Serializable } from "@utils/type"; +import { Hydratable, Serializable } from "@utils/types"; type FollowerCursor = string; type GetFollowersResult = [FollowerUser["legacy"][], (() => Promise) | null]; diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 001c2af..882df47 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -4,7 +4,7 @@ import { TwitterHelper } from "@watchers/twitter/helper"; import { Follower } from "@models/follower"; -import { ProviderInitializeContext } from "@utils/type"; +import { ProviderInitializeContext } from "@utils/types"; export type TWITTER_PROVIDER_ENV_KEYS = "TWITTER_USER_ID" | "TWITTER_PASSWORD"; From bd7a4101b53880d6a3077d89731d060b7e9d45ab Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 4 Dec 2022 23:04:16 +0900 Subject: [PATCH 010/140] test: add test code for logger utility class --- jest.config.ts | 33 ++++++++-------- package.json | 3 +- src/app.ts | 7 ++-- src/utils/config.ts | 2 +- src/utils/logger.spec.ts | 83 ++++++++++++++++++++++++++++++++++++++++ src/utils/logger.ts | 6 +-- src/utils/measureTime.ts | 2 +- tsconfig.json | 13 ++++--- 8 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 src/utils/logger.spec.ts diff --git a/jest.config.ts b/jest.config.ts index f1b09c6..b974cc4 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,20 +1,21 @@ -import { JestConfigWithTsJest } from "ts-jest"; +import type { JestConfigWithTsJest } from "ts-jest"; +import { pathsToModuleNameMapper } from "ts-jest"; +import { compilerOptions } from "./tsconfig.json"; -const config: JestConfigWithTsJest = { - preset: "ts-jest", +const jestConfig: JestConfigWithTsJest = { + moduleFileExtensions: ["js", "json", "ts"], + rootDir: "./src", + testRegex: ".*\\.spec\\.ts$", + transform: { + "^.+\\.(t|j)s$": "ts-jest", + }, + collectCoverageFrom: ["**/*.ts", "!coverage/**"], + coverageDirectory: "../coverage", testEnvironment: "node", - collectCoverage: true, - coverageReporters: ["html", "json", "text"], - collectCoverageFrom: [ - "!**/*.js", - "src/**/*.ts", - "!src/index.ts", - "!**/node_modules/**", - "!**/dist/**", - "!**/bin/**", - ], - modulePathIgnorePatterns: ["/dist/"], - testPathIgnorePatterns: ["/dist/"], + roots: [""], + modulePaths: [compilerOptions.baseUrl], // <-- This will be set to 'baseUrl' value + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/../" }), + watchAll: true, }; -export = config; +export = jestConfig; diff --git a/package.json b/package.json index 15f83c6..474f26d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", "test": "jest", - "test:watch": "jest --watch" + "coverage": "jest --coverage" }, "bin": { "cage": "./bin/cage.cjs" @@ -72,6 +72,7 @@ "reflect-metadata": "^0.1.13", "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", + "strip-ansi": "^6.0.1", "typeorm": "^0.3.10" } } diff --git a/src/app.ts b/src/app.ts index c313965..8310949 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,9 +1,9 @@ import * as _ from "lodash"; import { DataSource } from "typeorm"; -import * as chalk from "chalk"; +import chalk from "chalk"; import * as fs from "fs-extra"; import * as path from "path"; -import * as prettyMilliseconds from "pretty-ms"; +import prettyMilliseconds from "pretty-ms"; import { Follower } from "@models/follower"; import { FollowerLog, FollowerLogType } from "@models/follower-log"; @@ -37,7 +37,6 @@ export class App extends Loggable { process.on("SIGINT", this.cleanUp.bind(this)); process.on("SIGUSR1", this.cleanUp.bind(this)); process.on("SIGUSR2", this.cleanUp.bind(this)); - process.on("uncaughtException", this.cleanUp.bind(this)); } public async run() { @@ -107,7 +106,7 @@ export class App extends Loggable { private async cleanUp() { if (!this.config) { - throw new Error("Config is not loaded."); + return; } if (this.cleaningUp) { diff --git a/src/utils/config.ts b/src/utils/config.ts index f218bd2..59305a7 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as fs from "fs-extra"; -import * as chalk from "chalk"; +import chalk from "chalk"; import Ajv, { JSONSchemaType } from "ajv"; import betterAjvErrors from "better-ajv-errors"; diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts new file mode 100644 index 0000000..9080322 --- /dev/null +++ b/src/utils/logger.spec.ts @@ -0,0 +1,83 @@ +import stripAnsi from "strip-ansi"; + +import { Logger, LogLevel } from "@utils/logger"; +import { sleep } from "@utils/sleep"; + +describe("Logger class", () => { + const TARGET_LOG_LEVELS: LogLevel[] = ["info", "warn", "error", "debug", "silly"]; + let target: Logger; + let buffer: string[]; + let logMockFn: jest.Mock; + + function clearBuffer() { + buffer.length = 0; + } + + beforeEach(() => { + target = new Logger("Test"); + buffer = []; + logMockFn = jest.fn().mockImplementation((data: string) => { + buffer.push(stripAnsi(data)); + }); + + jest.spyOn(process.stdout, "write").mockImplementation(logMockFn); + }); + + it("should provide methods for all log levels", () => { + const mockedLogContent = "test"; + for (const logLevel of TARGET_LOG_LEVELS) { + clearBuffer(); + target[logLevel](mockedLogContent); + + expect(target[logLevel]).toBeDefined(); + expect(buffer).toHaveLength(1); + expect(buffer[0]).toContain(logLevel.toUpperCase()); + expect(buffer[0].trim().endsWith(mockedLogContent)).toBeTruthy(); + } + }); + + it("should provide a method to clear the console", () => { + const clearMock = jest.spyOn(console, "clear"); + target.clear(); + + expect(target.clear).toBeDefined(); + expect(clearMock).toHaveBeenCalled(); + + clearMock.mockClear(); + }); + + it("should provide a method to log a work", async () => { + const mockedWork = async () => { + await sleep(1000); + return 1; + }; + + const mockedWorkResult = await target.work({ + work: mockedWork, + message: "Test", + level: "info", + }); + + expect(target.work).toBeDefined(); + expect(mockedWorkResult).toBe(1); + expect(buffer[0].trim()).toMatch(/Test$/); + expect(buffer[1].trim()).toMatch("done."); + }); + + it("should print failed information when a work fails", async () => { + const mockedWork = async () => { + await sleep(1000); + throw new Error("Test"); + }; + + await target.work({ + work: mockedWork, + message: "Test", + level: "info", + }); + + expect(buffer[0].trim()).toMatch(/Test$/); + expect(buffer[1].trim()).toMatch(/failed. \([0-9]*?ms\)/); + expect(buffer[2].trim().includes("Last task failed with error: Test")).toBeTruthy(); + }); +}); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 1601f3d..e372a95 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,11 +1,11 @@ -import * as dayjs from "dayjs"; -import * as chalk from "chalk"; +import dayjs from "dayjs"; +import chalk from "chalk"; import { measureTime } from "@utils/measureTime"; import { Fn } from "@utils/types"; type LoggerFn = (content: string, breakLine?: boolean) => void; -type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; +export type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; const LOG_LEVEL_COLOR_MAP: Record> = { silly: chalk.blue, diff --git a/src/utils/measureTime.ts b/src/utils/measureTime.ts index dd3be56..14cac01 100644 --- a/src/utils/measureTime.ts +++ b/src/utils/measureTime.ts @@ -1,4 +1,4 @@ -import * as dayjs from "dayjs"; +import dayjs from "dayjs"; import { Work } from "@utils/types"; diff --git a/tsconfig.json b/tsconfig.json index 1893988..4139b16 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,5 @@ { "compilerOptions": { - "strict": true, - "module": "commonjs", "declaration": false, "moduleResolution": "Node", "removeComments": true, @@ -13,7 +11,7 @@ "inlineSourceMap": true, "resolveJsonModule": true, "outDir": "./dist", - "baseUrl": ".", + "esModuleInterop": true, "incremental": true, "skipLibCheck": true, "strictNullChecks": true, @@ -21,6 +19,12 @@ "strictBindCallApply": true, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, + "module": "commonjs", + "strict": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "baseUrl": "./", + "rootDir": ".", "paths": { "@root/*": ["./src/*"], "@utils/*": ["./src/utils/*"], @@ -29,6 +33,5 @@ "@followers/*": ["./src/followers/*"], "@models/*": ["./src/models/*"] } - }, - "exclude": ["node_modules", "**/*spec.ts", "./*.ts"] + } } From 40a6db5e5e93847e920b60b7f9175cad18de83e8 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 4 Dec 2022 23:23:40 +0900 Subject: [PATCH 011/140] refactor(core): use throttle instead of waiting for main task loop --- src/app.ts | 20 +++++--------------- src/utils/throttle.spec.ts | 33 +++++++++++++++++++++++++++++++++ src/utils/throttle.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 src/utils/throttle.spec.ts create mode 100644 src/utils/throttle.ts diff --git a/src/app.ts b/src/app.ts index 8310949..b23946e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,10 +8,10 @@ import prettyMilliseconds from "pretty-ms"; import { Follower } from "@models/follower"; import { FollowerLog, FollowerLogType } from "@models/follower-log"; +import { Config } from "@utils/config"; +import { throttle } from "@utils/throttle"; import { getFollowerDiff } from "@utils/getFollowerDiff"; import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; -import { sleep } from "@utils/sleep"; -import { Config } from "@utils/config"; export class App extends Loggable { private readonly followerDataSource: DataSource; @@ -86,14 +86,8 @@ export class App extends Loggable { while (true) { try { - await this.onCycle(); - - this.logger.info( - `watching task done. now wait for ${prettyMilliseconds(this.config.watchInterval, { - verbose: true, - })}.`, - ); - await sleep(this.config.watchInterval); + const [, elapsedTime] = await throttle(this.onCycle.bind(this), 1000, true); + this.logger.debug(`last task finished in ${chalk.green(prettyMilliseconds(elapsedTime))}.`); } catch (e) { if (!(e instanceof Error)) { throw e; @@ -105,11 +99,7 @@ export class App extends Loggable { } private async cleanUp() { - if (!this.config) { - return; - } - - if (this.cleaningUp) { + if (!this.config || this.cleaningUp) { return; } diff --git a/src/utils/throttle.spec.ts b/src/utils/throttle.spec.ts new file mode 100644 index 0000000..7543810 --- /dev/null +++ b/src/utils/throttle.spec.ts @@ -0,0 +1,33 @@ +import { throttle } from "@utils/throttle"; + +describe("throttle() Function", function () { + it("should return the result of the target function", async function () { + const target = async () => 1; + const result = await throttle(target, 0); + + expect(result).toBe(1); + }); + + it("should return the result of the target promise", async function () { + const target = Promise.resolve(1); + const result = await throttle(target, 0); + + expect(result).toBe(1); + }); + + it("should return the result of the target function with the elapsed time", async function () { + const target = async () => 1; + const [result, elapsedTime] = await throttle(target, 1000, true); + + expect(result).toBe(1); + expect(elapsedTime).toBeGreaterThanOrEqual(1000); + }); + + it("should return the result of the target promise with the elapsed time", async function () { + const target = Promise.resolve(1); + const [result, elapsedTime] = await throttle(target, 1000, true); + + expect(result).toBe(1); + expect(elapsedTime).toBeGreaterThanOrEqual(1000); + }); +}); diff --git a/src/utils/throttle.ts b/src/utils/throttle.ts new file mode 100644 index 0000000..f053c56 --- /dev/null +++ b/src/utils/throttle.ts @@ -0,0 +1,26 @@ +import { AsyncFn } from "@utils/types"; +import { sleep } from "@utils/sleep"; + +type Task = Promise | AsyncFn; + +export async function throttle(task: Task, waitFor: number): Promise; +export async function throttle( + task: Task, + waitFor: number, + elapsedTime: boolean, +): Promise<[TData, number]>; + +export async function throttle( + task: Task, + waitFor: number, + elapsedTime = false, +): Promise { + const startedAt = Date.now(); + const [result] = await Promise.all([typeof task === "function" ? task() : task, await sleep(waitFor)]); + + if (elapsedTime) { + return [result, Date.now() - startedAt]; + } + + return result; +} From e77e2a16ef2a4099bcdceddb075823036b89faa0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 4 Dec 2022 23:24:46 +0900 Subject: [PATCH 012/140] chore: use node built-in watch option instead of `nodemon` --- nodemon.json | 7 ---- package.json | 3 +- yarn.lock | 115 +++++---------------------------------------------- 3 files changed, 12 insertions(+), 113 deletions(-) delete mode 100644 nodemon.json diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index 3407c6e..0000000 --- a/nodemon.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "verbose": true, - "ignore": ["*.test.js", "bin", "dist", ".git"], - "watch": ["src"], - "ext": "ts", - "exec": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts" -} diff --git a/package.json b/package.json index 474f26d..106b4f2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "build": "tsc --project ./tsconfig.build.json", - "dev": "nodemon", + "dev": "node --watch -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", "semantic-release": "semantic-release", "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", @@ -49,7 +49,6 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.1.2", - "nodemon": "^2.0.20", "prettier": "^2.7.1", "rimraf": "^3.0.2", "semantic-release": "^19.0.5", diff --git a/yarn.lock b/yarn.lock index ce15be8..5cf00d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1482,7 +1482,7 @@ any-promise@^1.0.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -1666,7 +1666,7 @@ bin-links@^3.0.3: rimraf "^3.0.0" write-file-atomic "^4.0.0" -binary-extensions@^2.0.0, binary-extensions@^2.2.0: +binary-extensions@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== @@ -1700,7 +1700,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1876,21 +1876,6 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chokidar@^3.5.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -2183,13 +2168,6 @@ debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, de dependencies: ms "2.1.2" -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -2765,7 +2743,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2843,7 +2821,7 @@ git-log-parser@^1.2.0: through2 "~2.0.0" traverse "~0.6.6" -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3042,11 +3020,6 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== - ignore-walk@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" @@ -3154,13 +3127,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-cidr@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" @@ -3190,7 +3156,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4296,7 +4262,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: +ms@^2.0.0, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -4403,22 +4369,6 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== -nodemon@^2.0.20: - version "2.0.20" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" - integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== - dependencies: - chokidar "^3.5.2" - debug "^3.2.7" - ignore-by-default "^1.0.1" - minimatch "^3.1.2" - pstree.remy "^1.1.8" - semver "^5.7.1" - simple-update-notifier "^1.0.7" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.5" - nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -4433,13 +4383,6 @@ nopt@^6.0.0: dependencies: abbrev "^1.0.0" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== - dependencies: - abbrev "1" - normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4470,7 +4413,7 @@ normalize-package-data@^4.0.0: semver "^7.3.5" validate-npm-package-license "^3.0.4" -normalize-path@^3.0.0, normalize-path@~3.0.0: +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -4931,7 +4874,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -5060,11 +5003,6 @@ proxy-from-env@1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -5222,13 +5160,6 @@ readdir-scoped-modules@^1.1.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -5404,7 +5335,7 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.4.tgz#13053c0d4aa11d070a2f2872b6b1e3ae1e1971b4" integrity sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA== -"semver@2 || 3 || 4 || 5", semver@^5.7.1: +"semver@2 || 3 || 4 || 5": version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5421,11 +5352,6 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@~7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -5470,13 +5396,6 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" -simple-update-notifier@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" - integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== - dependencies: - semver "~7.0.0" - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -5698,7 +5617,7 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -5876,13 +5795,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -6050,11 +5962,6 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" From f0d726c798c89e84a5ffe361eb29e90436ef8cb3 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 4 Dec 2022 23:30:10 +0900 Subject: [PATCH 013/140] test: remove `watchAll` option for jest configuration --- jest.config.ts | 1 - package.json | 8 +- yarn.lock | 732 ++++++++++++++++++++++++++++--------------------- 3 files changed, 416 insertions(+), 325 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index b974cc4..61114b0 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -15,7 +15,6 @@ const jestConfig: JestConfigWithTsJest = { roots: [""], modulePaths: [compilerOptions.baseUrl], // <-- This will be set to 'baseUrl' value moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/../" }), - watchAll: true, }; export = jestConfig; diff --git a/package.json b/package.json index 106b4f2..d9d0439 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", "test": "jest", - "coverage": "jest --coverage" + "coverage": "jest --coverage --watchAll" }, "bin": { "cage": "./bin/cage.cjs" @@ -31,7 +31,7 @@ "@semantic-release/npm": "^9.0.1", "@semantic-release/release-notes-generator": "^10.0.3", "@types/fs-extra": "^9.0.13", - "@types/jest": "^29.1.1", + "@types/jest": "^29.2.3", "@types/listr": "^0.14.4", "@types/lodash": "^4.14.191", "@types/lodash.chunk": "^4.2.7", @@ -48,14 +48,14 @@ "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", - "jest": "^29.1.2", + "jest": "^29.3.1", "prettier": "^2.7.1", "rimraf": "^3.0.2", "semantic-release": "^19.0.5", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.0", - "typescript": "^4.8.3" + "typescript": "^4.9.3" }, "dependencies": { "ajv": "^8.11.2", diff --git a/yarn.lock b/yarn.lock index 5cf00d8..00492b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -374,61 +374,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.1.2.tgz#0ae975a70004696f8320490fcaa1a4152f7b62e4" - integrity sha512-ujEBCcYs82BTmRxqfHMQggSlkUZP63AE5YEaTPj7eFyJOzukkTorstOUC7L6nE3w5SYadGVAnTsQ/ZjTGL0qYQ== +"@jest/console@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.3.1.tgz#3e3f876e4e47616ea3b1464b9fbda981872e9583" + integrity sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.1.2" - jest-util "^29.1.2" + jest-message-util "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" -"@jest/core@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.1.2.tgz#e5ce7a71e7da45156a96fb5eeed11d18b67bd112" - integrity sha512-sCO2Va1gikvQU2ynDN8V4+6wB7iVrD2CvT0zaRst4rglf56yLly0NQ9nuRRAWFeimRf+tCdFsb1Vk1N9LrrMPA== +"@jest/core@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1" + integrity sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw== dependencies: - "@jest/console" "^29.1.2" - "@jest/reporters" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.3.1" + "@jest/reporters" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.0.0" - jest-config "^29.1.2" - jest-haste-map "^29.1.2" - jest-message-util "^29.1.2" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-resolve-dependencies "^29.1.2" - jest-runner "^29.1.2" - jest-runtime "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" - jest-watcher "^29.1.2" + jest-changed-files "^29.2.0" + jest-config "^29.3.1" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" + jest-regex-util "^29.2.0" + jest-resolve "^29.3.1" + jest-resolve-dependencies "^29.3.1" + jest-runner "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" + jest-watcher "^29.3.1" micromatch "^4.0.4" - pretty-format "^29.1.2" + pretty-format "^29.3.1" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.1.2.tgz#bb51a43fce9f960ba9a48f0b5b556f30618ebc0a" - integrity sha512-rG7xZ2UeOfvOVzoLIJ0ZmvPl4tBEQ2n73CZJSlzUjPw4or1oSWC0s0Rk0ZX+pIBJ04aVr6hLWFn1DFtrnf8MhQ== +"@jest/environment@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6" + integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag== dependencies: - "@jest/fake-timers" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.1.2" + jest-mock "^29.3.1" "@jest/expect-utils@^29.1.2": version "29.1.2" @@ -437,46 +437,53 @@ dependencies: jest-get-type "^29.0.0" -"@jest/expect@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.1.2.tgz#334a86395f621f1ab63ad95b06a588b9114d7b7a" - integrity sha512-FXw/UmaZsyfRyvZw3M6POgSNqwmuOXJuzdNiMWW9LCYo0GRoRDhg+R5iq5higmRTHQY7hx32+j7WHwinRmoILQ== +"@jest/expect-utils@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" + integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g== dependencies: - expect "^29.1.2" - jest-snapshot "^29.1.2" + jest-get-type "^29.2.0" -"@jest/fake-timers@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.1.2.tgz#f157cdf23b4da48ce46cb00fea28ed1b57fc271a" - integrity sha512-GppaEqS+QQYegedxVMpCe2xCXxxeYwQ7RsNx55zc8f+1q1qevkZGKequfTASI7ejmg9WwI+SJCrHe9X11bLL9Q== +"@jest/expect@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd" + integrity sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg== dependencies: - "@jest/types" "^29.1.2" + expect "^29.3.1" + jest-snapshot "^29.3.1" + +"@jest/fake-timers@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67" + integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A== + dependencies: + "@jest/types" "^29.3.1" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^29.1.2" - jest-mock "^29.1.2" - jest-util "^29.1.2" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" + jest-util "^29.3.1" -"@jest/globals@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.1.2.tgz#826ede84bc280ae7f789cb72d325c48cd048b9d3" - integrity sha512-uMgfERpJYoQmykAd0ffyMq8wignN4SvLUG6orJQRe9WAlTRc9cdpCaE/29qurXixYJVZWUqIBXhSk8v5xN1V9g== +"@jest/globals@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6" + integrity sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q== dependencies: - "@jest/environment" "^29.1.2" - "@jest/expect" "^29.1.2" - "@jest/types" "^29.1.2" - jest-mock "^29.1.2" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/types" "^29.3.1" + jest-mock "^29.3.1" -"@jest/reporters@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.1.2.tgz#5520898ed0a4ecf69d8b671e1dc8465d0acdfa6e" - integrity sha512-X4fiwwyxy9mnfpxL0g9DD0KcTmEIqP0jUdnc2cfa9riHy+I6Gwwp5vOZiwyg0vZxfSDxrOlK9S4+340W4d+DAA== +"@jest/reporters@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310" + integrity sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -489,13 +496,12 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.1.2" - jest-util "^29.1.2" - jest-worker "^29.1.2" + jest-message-util "^29.3.1" + jest-util "^29.3.1" + jest-worker "^29.3.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" - terminal-link "^2.0.0" v8-to-istanbul "^9.0.1" "@jest/schemas@^29.0.0": @@ -505,51 +511,51 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/source-map@^29.0.0": - version "29.0.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.0.0.tgz#f8d1518298089f8ae624e442bbb6eb870ee7783c" - integrity sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ== +"@jest/source-map@^29.2.0": + version "29.2.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744" + integrity sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ== dependencies: "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.1.2.tgz#6a8d006eb2b31ce0287d1fc10d12b8ff8504f3c8" - integrity sha512-jjYYjjumCJjH9hHCoMhA8PCl1OxNeGgAoZ7yuGYILRJX9NjgzTN0pCT5qAoYR4jfOP8htIByvAlz9vfNSSBoVg== +"@jest/test-result@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50" + integrity sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw== dependencies: - "@jest/console" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.3.1" + "@jest/types" "^29.3.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.1.2.tgz#10bfd89c08bfdba382eb05cc79c1d23a01238a93" - integrity sha512-fU6dsUqqm8sA+cd85BmeF7Gu9DsXVWFdGn9taxM6xN1cKdcP/ivSgXh5QucFRFz1oZxKv3/9DYYbq0ULly3P/Q== +"@jest/test-sequencer@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d" + integrity sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA== dependencies: - "@jest/test-result" "^29.1.2" + "@jest/test-result" "^29.3.1" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" + jest-haste-map "^29.3.1" slash "^3.0.0" -"@jest/transform@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.1.2.tgz#20f814696e04f090421f6d505c14bbfe0157062a" - integrity sha512-2uaUuVHTitmkx1tHF+eBjb4p7UuzBG7SXIaA/hNIkaMP6K+gXYGxP38ZcrofzqN0HeZ7A90oqsOa97WU7WZkSw== +"@jest/transform@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d" + integrity sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.1.2" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" + convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" - jest-regex-util "^29.0.0" - jest-util "^29.1.2" + jest-haste-map "^29.3.1" + jest-regex-util "^29.2.0" + jest-util "^29.3.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -567,6 +573,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" + integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1148,10 +1166,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.1.1": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.1.2.tgz#7ad8077043ab5f6c108c8111bcc1d224e5600a87" - integrity sha512-y+nlX0h87U0R+wsGn6EBuoRWYyv3KFtwRNP3QWp9+k2tJ2/bqcGS3UxD7jgT+tiwJWWq3UsyV4Y+T6rsMT4XMg== +"@types/jest@^29.2.3": + version "29.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.3.tgz#f5fd88e43e5a9e4221ca361e23790d48fcf0a211" + integrity sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1568,15 +1586,15 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -babel-jest@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.1.2.tgz#540d3241925c55240fb0c742e3ffc5f33a501978" - integrity sha512-IuG+F3HTHryJb7gacC7SQ59A9kO56BctUsT67uJHp1mMCHUOMXpDwOHWGifWqdWVknN2WNkCVQELPjXx0aLJ9Q== +babel-jest@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" + integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA== dependencies: - "@jest/transform" "^29.1.2" + "@jest/transform" "^29.3.1" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.0.2" + babel-preset-jest "^29.2.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -1592,10 +1610,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.0.2: - version "29.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz#ae61483a829a021b146c016c6ad39b8bcc37c2c8" - integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg== +babel-plugin-jest-hoist@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz#23ee99c37390a98cfddf3ef4a78674180d823094" + integrity sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -1620,12 +1638,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.0.2: - version "29.0.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz#e14a7124e22b161551818d89e5bdcfb3b2b0eac7" - integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA== +babel-preset-jest@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc" + integrity sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA== dependencies: - babel-plugin-jest-hoist "^29.0.2" + babel-plugin-jest-hoist "^29.2.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -2087,13 +2105,18 @@ conventional-commits-parser@^3.2.3: split2 "^3.0.0" through2 "^4.0.0" -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -2275,6 +2298,11 @@ diff-sequences@^29.0.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.0.0.tgz#bae49972ef3933556bcb0800b72e8579d19d9e4f" integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== +diff-sequences@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" + integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -2323,10 +2351,10 @@ electron-to-chromium@^1.4.251: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520" integrity sha512-EpuHPqu8YhonqLBXHoU6hDJCD98FCe6KDoet3/gY1qsQ6usjJoHqBH2YIVs8FXaAtHwVL8Uqa/fsYao/vq9VWQ== -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" @@ -2545,7 +2573,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.1.2: +expect@^29.0.0: version "29.1.2" resolved "https://registry.yarnpkg.com/expect/-/expect-29.1.2.tgz#82f8f28d7d408c7c68da3a386a490ee683e1eced" integrity sha512-AuAGn1uxva5YBbBlXb+2JPxJRuemZsmlGcapPXWNSBNsQtAULfjioREGBWuI0EOvYUKjDnrCy8PW5Zlr1md5mw== @@ -2556,6 +2584,17 @@ expect@^29.0.0, expect@^29.1.2: jest-message-util "^29.1.2" jest-util "^29.1.2" +expect@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" + integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== + dependencies: + "@jest/expect-utils" "^29.3.1" + jest-get-type "^29.2.0" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" + extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" @@ -3278,82 +3317,82 @@ java-properties@^1.0.0: resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" integrity sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ== -jest-changed-files@^29.0.0: - version "29.0.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.0.0.tgz#aa238eae42d9372a413dd9a8dadc91ca1806dce0" - integrity sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ== +jest-changed-files@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289" + integrity sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.1.2.tgz#4551068e432f169a53167fe1aef420cf51c8a735" - integrity sha512-ajQOdxY6mT9GtnfJRZBRYS7toNIJayiiyjDyoZcnvPRUPwJ58JX0ci0PKAKUo2C1RyzlHw0jabjLGKksO42JGA== +jest-circus@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a" + integrity sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg== dependencies: - "@jest/environment" "^29.1.2" - "@jest/expect" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.1.2" - jest-matcher-utils "^29.1.2" - jest-message-util "^29.1.2" - jest-runtime "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" + jest-each "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" p-limit "^3.1.0" - pretty-format "^29.1.2" + pretty-format "^29.3.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.1.2.tgz#423b9c5d3ea20a50b1354b8bf3f2a20e72110e89" - integrity sha512-vsvBfQ7oS2o4MJdAH+4u9z76Vw5Q8WBQF5MchDbkylNknZdrPTX1Ix7YRJyTlOWqRaS7ue/cEAn+E4V1MWyMzw== +jest-cli@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d" + integrity sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ== dependencies: - "@jest/core" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/core" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-config "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.1.2.tgz#7d004345ca4c09f5d8f802355f54494e90842f4d" - integrity sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA== +jest-config@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6" + integrity sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.1.2" - "@jest/types" "^29.1.2" - babel-jest "^29.1.2" + "@jest/test-sequencer" "^29.3.1" + "@jest/types" "^29.3.1" + babel-jest "^29.3.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.1.2" - jest-environment-node "^29.1.2" - jest-get-type "^29.0.0" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-runner "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-circus "^29.3.1" + jest-environment-node "^29.3.1" + jest-get-type "^29.2.0" + jest-regex-util "^29.2.0" + jest-resolve "^29.3.1" + jest-runner "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.1.2" + pretty-format "^29.3.1" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3367,67 +3406,82 @@ jest-diff@^29.1.2: jest-get-type "^29.0.0" pretty-format "^29.1.2" -jest-docblock@^29.0.0: - version "29.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.0.0.tgz#3151bcc45ed7f5a8af4884dcc049aee699b4ceae" - integrity sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw== +jest-diff@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" + integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw== dependencies: - detect-newline "^3.0.0" + chalk "^4.0.0" + diff-sequences "^29.3.1" + jest-get-type "^29.2.0" + pretty-format "^29.3.1" -jest-each@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.1.2.tgz#d4c8532c07a846e79f194f7007ce7cb1987d1cd0" - integrity sha512-AmTQp9b2etNeEwMyr4jc0Ql/LIX/dhbgP21gHAizya2X6rUspHn2gysMXaj6iwWuOJ2sYRgP8c1P4cXswgvS1A== +jest-docblock@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" + integrity sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A== dependencies: - "@jest/types" "^29.1.2" - chalk "^4.0.0" - jest-get-type "^29.0.0" - jest-util "^29.1.2" - pretty-format "^29.1.2" + detect-newline "^3.0.0" -jest-environment-node@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.1.2.tgz#005e05cc6ea4b9b5ba55906ab1ce53c82f6907a7" - integrity sha512-C59yVbdpY8682u6k/lh8SUMDJPbOyCHOTgLVVi1USWFxtNV+J8fyIwzkg+RJIVI30EKhKiAGNxYaFr3z6eyNhQ== +jest-each@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132" + integrity sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA== dependencies: - "@jest/environment" "^29.1.2" - "@jest/fake-timers" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/types" "^29.3.1" + chalk "^4.0.0" + jest-get-type "^29.2.0" + jest-util "^29.3.1" + pretty-format "^29.3.1" + +jest-environment-node@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74" + integrity sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag== + dependencies: + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.1.2" - jest-util "^29.1.2" + jest-mock "^29.3.1" + jest-util "^29.3.1" jest-get-type@^29.0.0: version "29.0.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.0.0.tgz#843f6c50a1b778f7325df1129a0fd7aa713aef80" integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== -jest-haste-map@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.1.2.tgz#93f3634aa921b6b654e7c94137b24e02e7ca6ac9" - integrity sha512-xSjbY8/BF11Jh3hGSPfYTa/qBFrm3TPM7WU8pU93m2gqzORVLkHFWvuZmFsTEBPRKndfewXhMOuzJNHyJIZGsw== +jest-get-type@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" + integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== + +jest-haste-map@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843" + integrity sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.3.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.0.0" - jest-util "^29.1.2" - jest-worker "^29.1.2" + jest-regex-util "^29.2.0" + jest-util "^29.3.1" + jest-worker "^29.3.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.1.2.tgz#4c846db14c58219430ccbc4f01a1ec52ebee4fc2" - integrity sha512-TG5gAZJpgmZtjb6oWxBLf2N6CfQ73iwCe6cofu/Uqv9iiAm6g502CAnGtxQaTfpHECBdVEMRBhomSXeLnoKjiQ== +jest-leak-detector@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz#95336d020170671db0ee166b75cd8ef647265518" + integrity sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA== dependencies: - jest-get-type "^29.0.0" - pretty-format "^29.1.2" + jest-get-type "^29.2.0" + pretty-format "^29.3.1" jest-matcher-utils@^29.1.2: version "29.1.2" @@ -3439,6 +3493,16 @@ jest-matcher-utils@^29.1.2: jest-get-type "^29.0.0" pretty-format "^29.1.2" +jest-matcher-utils@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" + integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ== + dependencies: + chalk "^4.0.0" + jest-diff "^29.3.1" + jest-get-type "^29.2.0" + pretty-format "^29.3.1" + jest-message-util@^29.1.2: version "29.1.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.1.2.tgz#c21a33c25f9dc1ebfcd0f921d89438847a09a501" @@ -3454,107 +3518,122 @@ jest-message-util@^29.1.2: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.1.2.tgz#de47807edbb9d4abf8423f1d8d308d670105678c" - integrity sha512-PFDAdjjWbjPUtQPkQufvniXIS3N9Tv7tbibePEjIIprzjgo0qQlyUiVMrT4vL8FaSJo1QXifQUOuPH3HQC/aMA== +jest-message-util@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" + integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA== dependencies: - "@jest/types" "^29.1.2" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.3.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.3.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e" + integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA== + dependencies: + "@jest/types" "^29.3.1" "@types/node" "*" - jest-util "^29.1.2" + jest-util "^29.3.1" jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^29.0.0: - version "29.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.0.0.tgz#b442987f688289df8eb6c16fa8df488b4cd007de" - integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== +jest-regex-util@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" + integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== -jest-resolve-dependencies@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.1.2.tgz#a6919e58a0c7465582cb8ec2d745b4e64ae8647f" - integrity sha512-44yYi+yHqNmH3OoWZvPgmeeiwKxhKV/0CfrzaKLSkZG9gT973PX8i+m8j6pDrTYhhHoiKfF3YUFg/6AeuHw4HQ== +jest-resolve-dependencies@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf" + integrity sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA== dependencies: - jest-regex-util "^29.0.0" - jest-snapshot "^29.1.2" + jest-regex-util "^29.2.0" + jest-snapshot "^29.3.1" -jest-resolve@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.1.2.tgz#9dd8c2fc83e59ee7d676b14bd45a5f89e877741d" - integrity sha512-7fcOr+k7UYSVRJYhSmJHIid3AnDBcLQX3VmT9OSbPWsWz1MfT7bcoerMhADKGvKCoMpOHUQaDHtQoNp/P9JMGg== +jest-resolve@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7" + integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" + jest-haste-map "^29.3.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-util "^29.3.1" + jest-validate "^29.3.1" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.1.2.tgz#f18b2b86101341e047de8c2f51a5fdc4e97d053a" - integrity sha512-yy3LEWw8KuBCmg7sCGDIqKwJlULBuNIQa2eFSVgVASWdXbMYZ9H/X0tnXt70XFoGf92W2sOQDOIFAA6f2BG04Q== +jest-runner@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d" + integrity sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA== dependencies: - "@jest/console" "^29.1.2" - "@jest/environment" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.3.1" + "@jest/environment" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.10.2" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.0.0" - jest-environment-node "^29.1.2" - jest-haste-map "^29.1.2" - jest-leak-detector "^29.1.2" - jest-message-util "^29.1.2" - jest-resolve "^29.1.2" - jest-runtime "^29.1.2" - jest-util "^29.1.2" - jest-watcher "^29.1.2" - jest-worker "^29.1.2" + jest-docblock "^29.2.0" + jest-environment-node "^29.3.1" + jest-haste-map "^29.3.1" + jest-leak-detector "^29.3.1" + jest-message-util "^29.3.1" + jest-resolve "^29.3.1" + jest-runtime "^29.3.1" + jest-util "^29.3.1" + jest-watcher "^29.3.1" + jest-worker "^29.3.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.1.2.tgz#dbcd57103d61115479108d5864bdcd661d9c6783" - integrity sha512-jr8VJLIf+cYc+8hbrpt412n5jX3tiXmpPSYTGnwcvNemY+EOuLNiYnHJ3Kp25rkaAcTWOEI4ZdOIQcwYcXIAZw== - dependencies: - "@jest/environment" "^29.1.2" - "@jest/fake-timers" "^29.1.2" - "@jest/globals" "^29.1.2" - "@jest/source-map" "^29.0.0" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" +jest-runtime@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a" + integrity sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A== + dependencies: + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/globals" "^29.3.1" + "@jest/source-map" "^29.2.0" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" - jest-message-util "^29.1.2" - jest-mock "^29.1.2" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" + jest-regex-util "^29.2.0" + jest-resolve "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.1.2.tgz#7dd277e88c45f2d2ff5888de1612e63c7ceb575b" - integrity sha512-rYFomGpVMdBlfwTYxkUp3sjD6usptvZcONFYNqVlaz4EpHPnDvlWjvmOQ9OCSNKqYZqLM2aS3wq01tWujLg7gg== +jest-snapshot@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e" + integrity sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3562,23 +3641,23 @@ jest-snapshot@^29.1.2: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/expect-utils" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.1.2" + expect "^29.3.1" graceful-fs "^4.2.9" - jest-diff "^29.1.2" - jest-get-type "^29.0.0" - jest-haste-map "^29.1.2" - jest-matcher-utils "^29.1.2" - jest-message-util "^29.1.2" - jest-util "^29.1.2" + jest-diff "^29.3.1" + jest-get-type "^29.2.0" + jest-haste-map "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" natural-compare "^1.4.0" - pretty-format "^29.1.2" + pretty-format "^29.3.1" semver "^7.3.5" jest-util@^29.0.0, jest-util@^29.1.2: @@ -3593,51 +3672,63 @@ jest-util@^29.0.0, jest-util@^29.1.2: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.1.2.tgz#83a728b8f6354da2e52346878c8bc7383516ca51" - integrity sha512-k71pOslNlV8fVyI+mEySy2pq9KdXdgZtm7NHrBX8LghJayc3wWZH0Yr0mtYNGaCU4F1OLPXRkwZR0dBm/ClshA== +jest-util@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" + integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.3.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a" + integrity sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g== + dependencies: + "@jest/types" "^29.3.1" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.0.0" + jest-get-type "^29.2.0" leven "^3.1.0" - pretty-format "^29.1.2" + pretty-format "^29.3.1" -jest-watcher@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.1.2.tgz#de21439b7d889e2fcf62cc2a4779ef1a3f1f3c62" - integrity sha512-6JUIUKVdAvcxC6bM8/dMgqY2N4lbT+jZVsxh0hCJRbwkIEnbr/aPjMQ28fNDI5lB51Klh00MWZZeVf27KBUj5w== +jest-watcher@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a" + integrity sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg== dependencies: - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^29.1.2" + emittery "^0.13.1" + jest-util "^29.3.1" string-length "^4.0.1" -jest-worker@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.1.2.tgz#a68302af61bce82b42a9a57285ca7499d29b2afc" - integrity sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA== +jest-worker@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" + integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw== dependencies: "@types/node" "*" - jest-util "^29.1.2" + jest-util "^29.3.1" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.1.2.tgz#f821a1695ffd6cd0efc3b59d2dfcc70a98582499" - integrity sha512-5wEIPpCezgORnqf+rCaYD1SK+mNN7NsstWzIsuvsnrhR/hSxXWd82oI7DkrbJ+XTD28/eG8SmxdGvukrGGK6Tw== +jest@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122" + integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA== dependencies: - "@jest/core" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/core" "^29.3.1" + "@jest/types" "^29.3.1" import-local "^3.0.2" - jest-cli "^29.1.2" + jest-cli "^29.3.1" js-sdsl@^4.1.4: version "4.1.5" @@ -4938,6 +5029,15 @@ pretty-format@^29.0.0, pretty-format@^29.1.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" + integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-ms@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" @@ -5638,7 +5738,7 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.2.0: +supports-hyperlinks@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== @@ -5712,14 +5812,6 @@ tempy@^1.0.0: type-fest "^0.16.0" unique-string "^2.0.0" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -5944,10 +6036,10 @@ typeorm@^0.3.10: xml2js "^0.4.23" yargs "^17.3.1" -typescript@^4.8.3: - version "4.8.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" - integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== +typescript@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== uglify-js@^3.1.4: version "3.17.3" From 47d9be415b7a0d9f0e9eccabf9a138de216f4fbf Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 02:54:05 +0900 Subject: [PATCH 014/140] fix(core): rewrite new follower and unfollower check routine --- package.json | 2 + src/app.ts | 111 +++++++----------- src/models/follower.ts | 33 ------ src/repositories/base.ts | 53 +++++++++ .../models/user-log.ts} | 16 +-- src/repositories/models/user.ts | 38 ++++++ src/repositories/user-log.ts | 57 +++++++++ src/repositories/user.ts | 30 +++++ src/utils/fetcher.ts | 5 +- src/utils/getFollowerDiff.ts | 4 +- src/utils/getTimestamp.ts | 3 + src/utils/mapBy.ts | 24 ++++ src/utils/types.ts | 2 +- src/watchers/base.ts | 4 +- src/watchers/twitter/helper.ts | 14 ++- src/watchers/twitter/index.ts | 20 ++-- src/watchers/twitter/types.ts | 1 + src/watchers/twitter/utils.ts | 2 +- tsconfig.json | 2 +- yarn.lock | 10 ++ 20 files changed, 298 insertions(+), 133 deletions(-) delete mode 100644 src/models/follower.ts create mode 100644 src/repositories/base.ts rename src/{models/follower-log.ts => repositories/models/user-log.ts} (57%) create mode 100644 src/repositories/models/user.ts create mode 100644 src/repositories/user-log.ts create mode 100644 src/repositories/user.ts create mode 100644 src/utils/getTimestamp.ts create mode 100644 src/utils/mapBy.ts diff --git a/package.json b/package.json index d9d0439..67973d1 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@types/node": "^18.11.10", "@types/node-cron": "^3.0.6", "@types/node-fetch": "^2.6.2", + "@types/pluralize": "^0.0.29", "@types/prompts": "^2.0.14", "@types/replace-ext": "^2.0.0", "@types/set-cookie-parser": "^2.4.2", @@ -66,6 +67,7 @@ "lodash": "^4.17.21", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", + "pluralize": "^8.0.0", "pretty-ms": "^7.0.1", "puppeteer": "^19.3.0", "reflect-metadata": "^0.1.13", diff --git a/src/app.ts b/src/app.ts index b23946e..3c73c67 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,17 +1,22 @@ -import * as _ from "lodash"; -import { DataSource } from "typeorm"; -import chalk from "chalk"; import * as fs from "fs-extra"; import * as path from "path"; +import { DataSource } from "typeorm"; +import chalk from "chalk"; import prettyMilliseconds from "pretty-ms"; +import pluralize from "pluralize"; + +import { UserRepository } from "@repositories/user"; +import { UserLogRepository } from "@repositories/user-log"; + +import { User, UserData } from "@repositories/models/user"; +import { UserLog, UserLogType } from "@root/repositories/models/user-log"; -import { Follower } from "@models/follower"; -import { FollowerLog, FollowerLogType } from "@models/follower-log"; +import { AVAILABLE_WATCHERS } from "@watchers"; import { Config } from "@utils/config"; import { throttle } from "@utils/throttle"; -import { getFollowerDiff } from "@utils/getFollowerDiff"; import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; +import { mapBy } from "@utils/mapBy"; export class App extends Loggable { private readonly followerDataSource: DataSource; @@ -19,6 +24,9 @@ export class App extends Loggable { env: process.env as Env, }; + private readonly userRepository: UserRepository; + private readonly userLogRepository: UserLogRepository; + private cleaningUp = false; private config: Config | null = null; @@ -27,11 +35,14 @@ export class App extends Loggable { this.followerDataSource = new DataSource({ type: "sqlite", database: path.join(process.cwd(), "./data.sqlite"), - entities: [Follower, FollowerLog], + entities: [User, UserLog], dropSchema: true, synchronize: true, }); + this.userRepository = new UserRepository(this.followerDataSource.getRepository(User)); + this.userLogRepository = new UserLogRepository(this.followerDataSource.getRepository(UserLog)); + process.on("exit", this.cleanUp.bind(this)); process.on("SIGTERM", this.cleanUp.bind(this)); process.on("SIGINT", this.cleanUp.bind(this)); @@ -86,8 +97,10 @@ export class App extends Loggable { while (true) { try { - const [, elapsedTime] = await throttle(this.onCycle.bind(this), 1000, true); - this.logger.debug(`last task finished in ${chalk.green(prettyMilliseconds(elapsedTime))}.`); + const [, elapsedTime] = await throttle(this.onCycle.bind(this), this.config.watchInterval, true); + this.logger.debug( + `last task finished in ${chalk.green(prettyMilliseconds(elapsedTime, { verbose: true }))}.`, + ); } catch (e) { if (!(e instanceof Error)) { throw e; @@ -136,71 +149,33 @@ export class App extends Loggable { throw new Error("Config is not loaded."); } - const targetWatchers = this.config.watchers; - for (const watcher of targetWatchers) { - const currentTime = new Date(); - const watcherName = watcher.getName(); - const nameToken = `\`${chalk.green(watcherName)}\``; - const newFollowers = await this.logger.work({ - level: "info", - message: `determine followers data through ${nameToken} watcher ... `, - work: () => watcher.doWatch(), - }); - - if (!newFollowers) { - this.logger.error(`no followers data was returned from ${nameToken} watcher.`); - continue; - } + const startedDate = new Date(); + const allUserData: UserData[] = []; + for (const watcher of AVAILABLE_WATCHERS) { + const userData = await watcher.doWatch(); - const allFollowers = await Follower.find({ - where: { from: watcherName }, - relations: ["followerLogs"], - }); - const oldFollowers = allFollowers.filter(f => f.isFollowing); - const followerLogsMap = _.chain(allFollowers) - .keyBy("id") - .mapValues(f => f.followerLogs) - .value(); - - const { removed, added } = getFollowerDiff(oldFollowers, newFollowers); - - if (added.length > 0) { - for (const follower of added) { - const newLog = new FollowerLog(); - newLog.type = FollowerLogType.Follow; - newLog.createdAt = currentTime; - - follower.isFollowing = true; - follower.followerLogs = [...(followerLogsMap[follower.id] || []), newLog]; - } + allUserData.push(...userData); + } - await Follower.save(added); - } + this.logger.info(`All ${allUserData.length} ${pluralize("follower", allUserData.length)} collected.`); - if (removed.length > 0) { - for (const follower of removed) { - const newLog = new FollowerLog(); - newLog.type = FollowerLogType.Unfollow; - newLog.createdAt = currentTime; + const followingMap = await this.userLogRepository.getFollowStatusMap(); + const oldUsers = await this.userRepository.find(); + const newUsers = await this.userRepository.createFromData(allUserData, startedDate); + const newUserMap = mapBy(newUsers, "id"); - follower.isFollowing = false; - follower.followerLogs = [...followerLogsMap[follower.id], newLog]; - } + // find users who are not followed yet. + const newFollowers = newUsers.filter(p => !followingMap[p.id]); + const unfollowers = oldUsers.filter(p => !newUserMap[p.id] && followingMap[p.id]); - await Follower.save(removed); - } + const newLogs = await this.userLogRepository.batchWriteLogs([ + [newFollowers, UserLogType.Follow], + [unfollowers, UserLogType.Unfollow], + ]); - if (added.length === 0 && removed.length === 0) { - this.logger.info(`no changes found from ${nameToken} watcher.`); - } else { - this.logger.info( - `found ${chalk.green(added.length)} new follower(s) and ${chalk.red( - removed.length, - )} removed follower(s).`, - ); - } + this.logger.info(`all ${newLogs.length} ${pluralize("log", newLogs.length)} saved.`); - await Follower.update({ from: watcherName }, { lastlyCheckedAt: currentTime }); - } + this.logger.info(`tracked ${newFollowers.length} new ${pluralize("follower", newFollowers.length)}.`); + this.logger.info(`tracked ${unfollowers.length} un${pluralize("follower", unfollowers.length)}.`); }; } diff --git a/src/models/follower.ts b/src/models/follower.ts deleted file mode 100644 index 13693bb..0000000 --- a/src/models/follower.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, RelationId, UpdateDateColumn } from "typeorm"; - -import { FollowerLog } from "@models/follower-log"; - -@Entity({ name: "followers" }) -export class Follower extends BaseEntity { - @Column({ type: "varchar", length: 255, primary: true }) - public id!: string; - - @Column({ type: "varchar", length: 255 }) - public from!: string; - - @Column({ type: "varchar", length: 255 }) - public displayName!: string; - - @Column({ type: "boolean" }) - public isFollowing!: boolean; - - @Column({ type: "datetime" }) - public lastlyCheckedAt!: Date; - - @CreateDateColumn() - public createdAt!: Date; - - @UpdateDateColumn() - public updatedAt!: Date; - - @OneToMany(() => FollowerLog, followerLog => followerLog.follower, { cascade: true }) - public followerLogs!: FollowerLog[]; - - @RelationId((follower: Follower) => follower.followerLogs) - public followerLogIds!: number[]; -} diff --git a/src/repositories/base.ts b/src/repositories/base.ts new file mode 100644 index 0000000..dbd92f2 --- /dev/null +++ b/src/repositories/base.ts @@ -0,0 +1,53 @@ +import { BaseEntity, Repository } from "typeorm"; + +import { AsyncFn } from "@utils/types"; + +export abstract class BaseRepository extends Repository { + private readonly buffer: TEntity[] = []; + private inTransaction = false; + + protected constructor(repository: Repository) { + super(repository.target, repository.manager, repository.queryRunner); + } + + public async transaction(task: AsyncFn) { + this.inTransaction = true; + + try { + await task(); + } catch (error) { + await this.closeTransaction(); + throw error; + } + + await this.closeTransaction(); + } + + private async closeTransaction() { + if (this.buffer.length === 0) { + return; + } + + const items = [...this.buffer]; + this.buffer.length = 0; + this.inTransaction = false; + + await this.save(items); + } + + protected async saveItems(item: TEntity): Promise; + protected async saveItems(items: TEntity[]): Promise; + protected async saveItems(items: TEntity[] | TEntity): Promise { + if (this.inTransaction) { + this.buffer.push(...(Array.isArray(items) ? items : [items])); + return items; + } + + if (!Array.isArray(items)) { + const [result] = await this.save([items]); + return result; + } + + return this.save(items); + } +} diff --git a/src/models/follower-log.ts b/src/repositories/models/user-log.ts similarity index 57% rename from src/models/follower-log.ts rename to src/repositories/models/user-log.ts index b43a8a2..b627be7 100644 --- a/src/models/follower-log.ts +++ b/src/repositories/models/user-log.ts @@ -9,20 +9,20 @@ import { UpdateDateColumn, } from "typeorm"; -import { Follower } from "@models/follower"; +import { User } from "@repositories/models/user"; -export enum FollowerLogType { +export enum UserLogType { Follow = "follow", Unfollow = "unfollow", } @Entity({ name: "follower-logs" }) -export class FollowerLog extends BaseEntity { +export class UserLog extends BaseEntity { @PrimaryGeneratedColumn() public id!: number; @Column({ type: "varchar", length: 255 }) - public type!: FollowerLogType; + public type!: UserLogType; @CreateDateColumn() public createdAt!: Date; @@ -30,9 +30,9 @@ export class FollowerLog extends BaseEntity { @UpdateDateColumn() public updatedAt!: Date; - @ManyToOne(() => Follower, follower => follower.followerLogs) - public follower!: Follower; + @ManyToOne(() => User, follower => follower.userLogs) + public user!: User; - @RelationId((followerLog: FollowerLog) => followerLog.follower) - public followerId!: string; + @RelationId((userLog: UserLog) => userLog.user) + public userLogId!: string; } diff --git a/src/repositories/models/user.ts b/src/repositories/models/user.ts new file mode 100644 index 0000000..0f8a19a --- /dev/null +++ b/src/repositories/models/user.ts @@ -0,0 +1,38 @@ +import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, RelationId, UpdateDateColumn } from "typeorm"; + +import { UserLog } from "@root/repositories/models/user-log"; + +export type UserData = Pick; + +@Entity({ name: "users" }) +export class User extends BaseEntity { + @Column({ type: "varchar", length: 255, primary: true }) + public id!: string; + + @Column({ type: "varchar", length: 255 }) + public from!: string; + + @Column({ type: "varchar", length: 255 }) + public uniqueId!: string; + + @Column({ type: "varchar", length: 255 }) + public userId!: string; + + @Column({ type: "varchar", length: 255 }) + public displayName!: string; + + @Column({ type: "datetime" }) + public lastlyCheckedAt!: Date; + + @CreateDateColumn() + public createdAt!: Date; + + @UpdateDateColumn() + public updatedAt!: Date; + + @OneToMany(() => UserLog, followerLog => followerLog.user, { cascade: true }) + public userLogs!: UserLog[]; + + @RelationId((follower: User) => follower.userLogs) + public userLogIds!: number[]; +} diff --git a/src/repositories/user-log.ts b/src/repositories/user-log.ts new file mode 100644 index 0000000..5faf317 --- /dev/null +++ b/src/repositories/user-log.ts @@ -0,0 +1,57 @@ +import { Repository } from "typeorm"; + +import { BaseRepository } from "@repositories/base"; + +import { User } from "@repositories/models/user"; +import { UserLog, UserLogType } from "@repositories/models/user-log"; + +import { mapBy } from "@utils/mapBy"; + +export class UserLogRepository extends BaseRepository { + public constructor(repository: Repository) { + super(repository); + } + + public async writeLog(user: User, type: UserLog["type"]) { + const userLog = this.create(); + userLog.user = user; + userLog.type = type; + + return this.saveItems(userLog); + } + public async writeLogs(users: User[], type: UserLog["type"]) { + const userLogs = users.map(user => { + const userLog = this.create(); + userLog.user = user; + userLog.type = type; + + return userLog; + }); + + return this.saveItems(userLogs); + } + public async batchWriteLogs(items: [User[], UserLog["type"]][]) { + const userLogs = items.flatMap(([users, type]) => + users.map(user => { + const userLog = this.create(); + userLog.user = user; + userLog.type = type; + + return userLog; + }), + ); + + return this.saveItems(userLogs); + } + + public async getFollowStatusMap() { + const data = await this.createQueryBuilder() + .select("type") + .addSelect("userId") + .addSelect("MAX(createdAt)", "createdAt") + .groupBy("userId") + .getRawMany<{ type: UserLog["type"]; userId: string; createdAt: string }>(); + + return mapBy(data, "userId", item => item.type === UserLogType.Follow); + } +} diff --git a/src/repositories/user.ts b/src/repositories/user.ts new file mode 100644 index 0000000..fca363b --- /dev/null +++ b/src/repositories/user.ts @@ -0,0 +1,30 @@ +import { Repository } from "typeorm"; + +import { BaseRepository } from "@repositories/base"; +import { User, UserData } from "@repositories/models/user"; + +export class UserRepository extends BaseRepository { + public constructor(repository: Repository) { + super(repository); + } + + public async createFromData(data: UserData | UserData[], lastlyCheckedAt: Date = new Date()) { + if (!Array.isArray(data)) { + data = [data]; + } + + const users = data.map(item => { + const user = this.create(); + user.id = `${item.from}:${item.uniqueId}`; + user.displayName = item.displayName; + user.userId = item.userId; + user.uniqueId = item.uniqueId; + user.from = item.from; + user.lastlyCheckedAt = lastlyCheckedAt; + + return user; + }); + + return this.saveItems(users); + } +} diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index 0a2a15e..a73651b 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,10 +1,11 @@ -import fetch, { Headers, RequestInit, Response } from "node-fetch"; +import nodeFetch, { Headers, RequestInit, Response } from "node-fetch"; import { parseCookie } from "@utils/parseCookie"; import { Hydratable, Serializable } from "@utils/types"; export class Fetcher implements Serializable, Hydratable { private readonly cookies: Record = {}; + private readonly fetchImpl = nodeFetch; private getCookieString(): string { return Object.entries(this.cookies) @@ -35,7 +36,7 @@ export class Fetcher implements Serializable, Hydratable { const headers = new Headers(options.headers); headers.set("cookie", this.getCookieString()); - const response = await fetch(url, { + const response = await this.fetchImpl(url, { ...options, headers, }); diff --git a/src/utils/getFollowerDiff.ts b/src/utils/getFollowerDiff.ts index 41ecc0c..c13322c 100644 --- a/src/utils/getFollowerDiff.ts +++ b/src/utils/getFollowerDiff.ts @@ -1,8 +1,8 @@ import * as _ from "lodash"; -import { Follower } from "@models/follower"; +import { User } from "@root/repositories/models/user"; -export function getFollowerDiff(oldFollowers: Follower[], newFollowers: Follower[]) { +export function getFollowerDiff(oldFollowers: User[], newFollowers: User[]) { const oldFollowerMap = _.chain(oldFollowers).keyBy("id").value(); const newFollowerMap = _.chain(newFollowers).keyBy("id").value(); diff --git a/src/utils/getTimestamp.ts b/src/utils/getTimestamp.ts new file mode 100644 index 0000000..b51b0ed --- /dev/null +++ b/src/utils/getTimestamp.ts @@ -0,0 +1,3 @@ +export function getTimestamp() { + return new Date().getTime(); +} diff --git a/src/utils/mapBy.ts b/src/utils/mapBy.ts new file mode 100644 index 0000000..0e01225 --- /dev/null +++ b/src/utils/mapBy.ts @@ -0,0 +1,24 @@ +import { Fn } from "@utils/types"; + +export function mapBy, TKey extends keyof TData>( + data: TData[], + key: TKey | Fn, +): Record; +export function mapBy, TKey extends keyof TData, TValue = unknown>( + data: TData[], + key: TKey | Fn, + mapFn: Fn, +): Record; + +export function mapBy, TKey extends keyof TData, TValue = unknown>( + data: TData[], + key: TKey | Fn, + mapFn?: Fn, +): Record | Record { + return data.reduce((acc, item) => { + const itemKey = typeof key === "function" ? key(item) : item[key]; + acc[itemKey as string] = mapFn ? mapFn(item) : item; + + return acc; + }, {} as Record | Record); +} diff --git a/src/utils/types.ts b/src/utils/types.ts index eecf9b8..1945a8e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,7 +6,7 @@ export type Fn = TArgs extends unknown[] : TArgs extends void ? () => TReturn : (...arg: [TArgs]) => TReturn; -export type AsyncFn = Fn>; +export type AsyncFn = Fn>; export type Work = Fn | T>; export type Nullable = T | null | undefined; diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 2b53adb..812a3ab 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,5 +1,5 @@ import { Hydratable, Loggable, ProviderInitializeContext, Serializable } from "@utils/types"; -import { Follower } from "@root/models/follower"; +import { UserData } from "@root/repositories/models/user"; export abstract class BaseWatcher extends Loggable implements Serializable, Hydratable { protected constructor(name: string) { @@ -13,7 +13,7 @@ export abstract class BaseWatcher extends Loggable implements Serializable, Hydr public abstract initialize(context: ProviderInitializeContext): Promise; public abstract finalize(): Promise; - public abstract doWatch(): Promise; + public abstract doWatch(): Promise; public abstract serialize(): Record; public abstract hydrate(data: Record): void; diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index 059a218..0e177b0 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -1,3 +1,5 @@ +import * as fs from "fs-extra"; + import { TwitterAuth } from "@watchers/twitter/auth"; import { TWITTER_AUTH_TOKEN } from "@watchers/twitter/constants"; import { FollowerAPIResponse, FollowerUser } from "@watchers/twitter/types"; @@ -8,7 +10,7 @@ import { Fetcher } from "@utils/fetcher"; import { Hydratable, Serializable } from "@utils/types"; type FollowerCursor = string; -type GetFollowersResult = [FollowerUser["legacy"][], (() => Promise) | null]; +type GetFollowersResult = [FollowerUser[], (() => Promise) | null]; export class TwitterHelper implements Serializable, Hydratable { private readonly fetcher: Fetcher = new Fetcher(); @@ -106,6 +108,12 @@ export class TwitterHelper implements Serializable, Hydratable { }, ); + if (process.env.NODE_ENV === "development") { + await fs.writeJson("followers.json", data, { + spaces: 4, + }); + } + const bottomCursor = findBottomCursorFromFollower(data); if (!bottomCursor) { throw new Error("Failed to find bottom cursor"); @@ -118,9 +126,9 @@ export class TwitterHelper implements Serializable, Hydratable { return [users, users.length >= 100 ? this.getFollowers.bind(this, bottomCursor) : null]; } - public async getAllFollowers(): Promise { + public async getAllFollowers(): Promise { const result = await this.getFollowers(); - const followers: FollowerUser["legacy"][] = [...result[0]]; + const followers: FollowerUser[] = [...result[0]]; let next = result[1]; while (true) { if (!next) { diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 882df47..1b02b24 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -2,7 +2,7 @@ import { BaseWatcher } from "@watchers/base"; import { TwitterHelper } from "@watchers/twitter/helper"; -import { Follower } from "@models/follower"; +import { UserData } from "@root/repositories/models/user"; import { ProviderInitializeContext } from "@utils/types"; @@ -29,20 +29,16 @@ export class TwitterWatcher extends BaseWatcher { public async finalize(): Promise {} public async doWatch() { - const checkedAt = new Date(); const allFollowers = await this.helper.getAllFollowers(); - this.logger.silly(`Total followers: ${allFollowers.length}`); - return allFollowers.map(user => { - const follower = Follower.create(); - follower.from = this.getName(); - follower.displayName = user.name; - follower.isFollowing = true; - follower.lastlyCheckedAt = checkedAt; - follower.id = `${follower.from}:${user.screen_name}`; + this.logger.silly(`Successfully crawled ${allFollowers.length} followers`); - return follower; - }); + return allFollowers.map(user => ({ + uniqueId: user.rest_id, + userId: user.legacy.screen_name, + displayName: user.legacy.name, + from: this.getName().toLowerCase(), + })); } public serialize(): Record { diff --git a/src/watchers/twitter/types.ts b/src/watchers/twitter/types.ts index ba2e416..11aa2c1 100644 --- a/src/watchers/twitter/types.ts +++ b/src/watchers/twitter/types.ts @@ -25,6 +25,7 @@ export interface InstructionEntry { content: TimelineItemContent | TimelineCursorContent; } export interface FollowerUser { + rest_id: string; legacy: { blocked_by: boolean; blocking: boolean; diff --git a/src/watchers/twitter/utils.ts b/src/watchers/twitter/utils.ts index a42ee59..ba4bf43 100644 --- a/src/watchers/twitter/utils.ts +++ b/src/watchers/twitter/utils.ts @@ -51,5 +51,5 @@ export function findUserDataFromFollower(response: FollowerAPIResponse) { .filter((content): content is TimelineItemContent => { return content.entryType === "TimelineTimelineItem"; }) - .map(content => content.itemContent.user_results.result.legacy); + .map(content => content.itemContent.user_results.result); } diff --git a/tsconfig.json b/tsconfig.json index 4139b16..867ab10 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,7 +31,7 @@ "@watchers/*": ["./src/watchers/*"], "@watchers": ["./src/watchers"], "@followers/*": ["./src/followers/*"], - "@models/*": ["./src/models/*"] + "@repositories/*": ["./src/repositories/*"] } } } diff --git a/yarn.lock b/yarn.lock index 00492b4..20bd563 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1242,6 +1242,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/pluralize@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" + integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== + "@types/prettier@^2.1.5": version "2.7.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" @@ -4995,6 +5000,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + postcss-selector-parser@^6.0.10: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" From 91e1ec4c3c3e06391518300b1b001a34c79db72c Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 13:00:03 +0900 Subject: [PATCH 015/140] refactor(core): make fetcher can retry when request throws an error --- src/utils/buildQueryString.ts | 5 + src/utils/fetcher.spec.ts | 265 +++++++++++++++++++++++++++++++++ src/utils/fetcher.ts | 74 +++++++-- src/watchers/twitter/auth.ts | 258 +++++++++++++++----------------- src/watchers/twitter/helper.ts | 52 +++---- 5 files changed, 481 insertions(+), 173 deletions(-) create mode 100644 src/utils/buildQueryString.ts create mode 100644 src/utils/fetcher.spec.ts diff --git a/src/utils/buildQueryString.ts b/src/utils/buildQueryString.ts new file mode 100644 index 0000000..66e729f --- /dev/null +++ b/src/utils/buildQueryString.ts @@ -0,0 +1,5 @@ +export function buildQueryString(queries: Record) { + return Object.entries(queries) + .map(([key, value]) => `${key}=${value}`) + .join("&"); +} diff --git a/src/utils/fetcher.spec.ts b/src/utils/fetcher.spec.ts new file mode 100644 index 0000000..de3d5f2 --- /dev/null +++ b/src/utils/fetcher.spec.ts @@ -0,0 +1,265 @@ +import { Headers, HeadersInit, RequestInit } from "node-fetch"; + +import { Fetcher } from "@utils/fetcher"; +import { throttle } from "@utils/throttle"; + +describe("Fetcher class", function () { + let target: Fetcher; + + beforeEach(() => { + target = new Fetcher(); + + Object.defineProperty(target, "logger", { + value: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + silly: jest.fn(), + }, + }); + }); + + it("should provide a method to fetch data", async () => { + const res = await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + }); + + expect(target.fetch).toBeDefined(); + await expect(res.json()).resolves.toMatchObject({ + userId: 1, + id: 1, + title: "delectus aut autem", + completed: false, + }); + }); + + it("should provide a method to fetch json", async () => { + const res = await target.fetchJson({ + url: "https://jsonplaceholder.typicode.com/todos/1", + }); + + expect(target.fetchJson).toBeDefined(); + expect(res).toMatchObject({ + userId: 1, + id: 1, + title: "delectus aut autem", + completed: false, + }); + }); + + it("should add query params to the url if method is GET and data provided", async () => { + let calledUrl = ""; + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation((url: string) => { + calledUrl = url; + + return Promise.resolve({ + headers: { + get: () => "", + }, + ok: true, + json: () => { + return Promise.resolve({ url }); + }, + }); + }), + }); + + await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + method: "GET", + data: { + test: "test", + }, + }); + + expect(calledUrl).toBe("https://jsonplaceholder.typicode.com/todos/1?test=test"); + }); + + it("should add body as json if method is not GET and data provided", async () => { + let calledBody: any = ""; + let calledHeader: HeadersInit | undefined; + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation((url: string, options: RequestInit) => { + calledBody = options.body; + calledHeader = options.headers; + + return Promise.resolve({ + headers: { + get: () => "", + }, + ok: true, + json: () => { + return Promise.resolve({ url }); + }, + }); + }), + }); + + await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + method: "POST", + data: { + test: "test", + }, + }); + + expect(calledHeader).toBeDefined(); + if (calledHeader && calledHeader instanceof Headers) { + expect(calledHeader.get("Content-Type")).toBe("application/json"); + } + + expect(calledBody).toBe('{"test":"test"}'); + }); + + it("should add cookies to the request if cookies are set", async () => { + let calledHeader: HeadersInit | undefined; + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation((url: string, options: RequestInit) => { + calledHeader = options.headers; + + return Promise.resolve({ + headers: { + get: () => "", + }, + ok: true, + json: () => { + return Promise.resolve({ url }); + }, + }); + }), + }); + + target.hydrate({ + cookies: { + test: "test", + }, + }); + + await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + method: "POST", + data: { + test: "test", + }, + }); + + expect(calledHeader).toBeDefined(); + if (calledHeader && calledHeader instanceof Headers) { + expect(calledHeader.get("cookie")).toBe("test=test"); + } + }); + + it("should retry the request if it fails when retryCount is set", async () => { + let calledCount = 0; + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation(() => { + calledCount++; + + return Promise.resolve({ + headers: { get: () => "" }, + ok: false, + status: 500, + statusText: "Internal Server Error", + }); + }), + }); + + await expect(target.fetch({ url: "", retryCount: 3, retryDelay: 0 })).rejects.toThrow( + "Failed to fetch : (500 Internal Server Error)", + ); + expect(calledCount).toBe(4); + }); + + it("should retry with delay the request if it fails when retryCount & retryDealy is set", async () => { + let calledCount = 0; + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation(() => { + calledCount++; + + return Promise.resolve({ + headers: { get: () => "" }, + ok: false, + status: 500, + statusText: "Internal Server Error", + }); + }), + }); + + const [, elapsedTime] = await throttle( + expect(target.fetch({ url: "", retryCount: 3, retryDelay: 500 })).rejects.toThrow( + "Failed to fetch : (500 Internal Server Error)", + ), + 0, + true, + ); + + expect(calledCount).toBe(4); + expect(elapsedTime).toBeGreaterThanOrEqual(1500); + }); + + it("should store cookies in the cookie jar if set-cookie header is present", async () => { + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation(() => { + return Promise.resolve({ + headers: { + get: (key: string) => { + if (key === "set-cookie") { + return "test=test"; + } + + return ""; + }, + }, + ok: true, + json: () => { + return Promise.resolve({ url: "" }); + }, + }); + }), + }); + + await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + method: "POST", + data: { + test: "test", + }, + }); + + expect(target.serialize().cookies).toMatchObject({ test: "test" }); + }); + + it("should able to get the cookies from the cookie jar", async () => { + Object.defineProperty(target, "fetchImpl", { + value: jest.fn().mockImplementation(() => { + return Promise.resolve({ + headers: { + get: (key: string) => { + if (key === "set-cookie") { + return "test=test"; + } + + return ""; + }, + }, + ok: true, + json: () => { + return Promise.resolve({ url: "" }); + }, + }); + }), + }); + + await target.fetch({ + url: "https://jsonplaceholder.typicode.com/todos/1", + method: "POST", + data: { + test: "test", + }, + }); + + expect(target.getCookies()).toMatchObject({ test: "test" }); + }); +}); diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index a73651b..560aac0 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,11 +1,24 @@ -import nodeFetch, { Headers, RequestInit, Response } from "node-fetch"; +import nodeFetch, { Headers, Response } from "node-fetch"; +import { sleep } from "@utils/sleep"; +import { Logger } from "@utils/logger"; import { parseCookie } from "@utils/parseCookie"; +import { buildQueryString } from "@utils/buildQueryString"; import { Hydratable, Serializable } from "@utils/types"; +interface FetchOption { + url: string; + method?: "GET" | "POST" | "PUT" | "DELETE"; + retryCount?: number; // retry count (default: -1, infinite) + retryDelay?: number; // in ms (default: 1000) + data?: Record; + headers?: Record; +} + export class Fetcher implements Serializable, Hydratable { private readonly cookies: Record = {}; private readonly fetchImpl = nodeFetch; + private readonly logger = new Logger("Fetcher"); private getCookieString(): string { return Object.entries(this.cookies) @@ -28,20 +41,63 @@ export class Fetcher implements Serializable, Hydratable { return { ...this.cookies }; } - public async fetchJson(url: string, options: RequestInit = {}): Promise { - const response = await this.fetch(url, options); + public async fetchJson(options: FetchOption): Promise { + const response = await this.fetch(options); return response.json(); } - public async fetch(url: string, options: RequestInit = {}): Promise { - const headers = new Headers(options.headers); - headers.set("cookie", this.getCookieString()); + public async fetch({ + url, + headers, + data, + method = "GET", + retryCount = 0, + retryDelay = 1000, + }: FetchOption): Promise { + let endpoint = url; + if (method === "GET" && data) { + endpoint = `${endpoint}?${buildQueryString(data)}`; + } + + const fetchHeaders = new Headers({ + cookie: this.getCookieString(), + ...headers, + }); + + if (method !== "GET" && data) { + fetchHeaders.set("Content-Type", "application/json"); + } - const response = await this.fetchImpl(url, { - ...options, - headers, + const response = await this.fetchImpl(endpoint, { + method: method, + headers: fetchHeaders, + body: method === "GET" ? undefined : JSON.stringify(data), }); this.setCookies(response.headers.get("set-cookie")); + + if (!response.ok) { + if (retryCount === 0) { + throw new Error(`Failed to fetch ${url}: (${response.status} ${response.statusText})`); + } + + this.logger.error(`Failed to fetch ${url}: (${response.status} ${response.statusText})`); + if (retryDelay > 0) { + this.logger.error(`Retrying in ${retryDelay}ms ...`); + } else { + this.logger.error("Retrying ..."); + } + + await sleep(retryDelay); + return this.fetch({ + url, + headers, + data, + method, + retryCount: retryCount - 1, + retryDelay, + }); + } + return response; } diff --git a/src/watchers/twitter/auth.ts b/src/watchers/twitter/auth.ts index 3967128..eaba573 100644 --- a/src/watchers/twitter/auth.ts +++ b/src/watchers/twitter/auth.ts @@ -13,33 +13,31 @@ export class TwitterAuth { public doInstrumentation() { this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>( - "https://twitter.com/i/api/1.1/onboarding/task.json", - { - method: "POST", - headers: this.getHeaders(), - body: JSON.stringify({ - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginJsInstrumentationSubtask", - js_instrumentation: { - response: JSON.stringify({ - rf: { - eca553b518cf2bf0dac5687d57cc1857738793960864216fb28429c367b1074c: 96, - bf7ee80b5b9e0822c133ba94f7c8fa22efadc740ccfe7e83a8907335d7668441: 128, - aea7b2935d0508b33d370215bb57788afe530c7250f289c4e2f347579a363c98: 154, - aa1900ce941819d2ce7b3985d94c4b05781f7b9bd3b2389cd1b28f1b8156ebc8: -115, - }, - s: "CKq_55Z6_WsJSB5d7xiFWMeLaWUckgaJw1bADMTt3WNVnsZGXlu4q9EDeem5Azx-pPMUR8M2_VRpnn0411iltwDyynQKh_ONDxmO52V2TrialbGMyy1Zh-J1Xj_xeXDRI-DPpTG-xe7qTtzMZj8i60xSwZv15BV0S496tJ00pSNlfYBEk-YK85q2kO3cGQg7ZPoHDjxIJPRcintmYd9ioVMFTMYcmrnhGkpk6Qf7GqwbL1XEkAAtuqB33AD_xH-V3P3Dl2z4RQQsL-xL6-LwuEtJW4F6l91N4Fqx0jCN9AaP42CM-tMNu0lJvZw32QTGhE8VzFg1Db26CTym4q9qlAAAAYTGoKew", - }), - link: "next_link", - }, + const data = await this.fetcher.fetchJson<{ flow_token: string }>({ + url: "https://twitter.com/i/api/1.1/onboarding/task.json", + method: "POST", + headers: this.getHeaders(), + data: { + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginJsInstrumentationSubtask", + js_instrumentation: { + response: JSON.stringify({ + rf: { + eca553b518cf2bf0dac5687d57cc1857738793960864216fb28429c367b1074c: 96, + bf7ee80b5b9e0822c133ba94f7c8fa22efadc740ccfe7e83a8907335d7668441: 128, + aea7b2935d0508b33d370215bb57788afe530c7250f289c4e2f347579a363c98: 154, + aa1900ce941819d2ce7b3985d94c4b05781f7b9bd3b2389cd1b28f1b8156ebc8: -115, + }, + s: "CKq_55Z6_WsJSB5d7xiFWMeLaWUckgaJw1bADMTt3WNVnsZGXlu4q9EDeem5Azx-pPMUR8M2_VRpnn0411iltwDyynQKh_ONDxmO52V2TrialbGMyy1Zh-J1Xj_xeXDRI-DPpTG-xe7qTtzMZj8i60xSwZv15BV0S496tJ00pSNlfYBEk-YK85q2kO3cGQg7ZPoHDjxIJPRcintmYd9ioVMFTMYcmrnhGkpk6Qf7GqwbL1XEkAAtuqB33AD_xH-V3P3Dl2z4RQQsL-xL6-LwuEtJW4F6l91N4Fqx0jCN9AaP42CM-tMNu0lJvZw32QTGhE8VzFg1Db26CTym4q9qlAAAAYTGoKew", + }), + link: "next_link", }, - ], - }), + }, + ], }, - ); + }); this.flowToken = data.flow_token; }); @@ -48,27 +46,25 @@ export class TwitterAuth { } public setUserId(userId: string) { this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>( - "https://twitter.com/i/api/1.1/onboarding/task.json", - { - method: "POST", - headers: this.getHeaders(), - body: JSON.stringify({ - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginEnterUserIdentifierSSO", - settings_list: { - setting_responses: [ - { key: "user_identifier", response_data: { text_data: { result: userId } } }, - ], - link: "next_link", - }, + const data = await this.fetcher.fetchJson<{ flow_token: string }>({ + url: "https://twitter.com/i/api/1.1/onboarding/task.json", + method: "POST", + headers: this.getHeaders(), + data: { + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginEnterUserIdentifierSSO", + settings_list: { + setting_responses: [ + { key: "user_identifier", response_data: { text_data: { result: userId } } }, + ], + link: "next_link", }, - ], - }), + }, + ], }, - ); + }); this.flowToken = data.flow_token; }); @@ -77,22 +73,20 @@ export class TwitterAuth { } public setPassword(password: string) { this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>( - "https://twitter.com/i/api/1.1/onboarding/task.json", - { - method: "POST", - headers: this.getHeaders(), - body: JSON.stringify({ - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginEnterPassword", - enter_password: { password, link: "next_link" }, - }, - ], - }), + const data = await this.fetcher.fetchJson<{ flow_token: string }>({ + url: "https://twitter.com/i/api/1.1/onboarding/task.json", + method: "POST", + headers: this.getHeaders(), + data: { + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "LoginEnterPassword", + enter_password: { password, link: "next_link" }, + }, + ], }, - ); + }); this.flowToken = data.flow_token; }); @@ -101,22 +95,20 @@ export class TwitterAuth { } public doDuplicationCheck() { this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>( - "https://twitter.com/i/api/1.1/onboarding/task.json", - { - method: "POST", - headers: this.getHeaders(), - body: JSON.stringify({ - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "AccountDuplicationCheck", - check_logged_in_account: { link: "AccountDuplicationCheck_false" }, - }, - ], - }), + const data = await this.fetcher.fetchJson<{ flow_token: string }>({ + url: "https://twitter.com/i/api/1.1/onboarding/task.json", + method: "POST", + headers: this.getHeaders(), + data: { + flow_token: this.flowToken, + subtask_inputs: [ + { + subtask_id: "AccountDuplicationCheck", + check_logged_in_account: { link: "AccountDuplicationCheck_false" }, + }, + ], }, - ); + }); this.flowToken = data.flow_token; }); @@ -125,71 +117,67 @@ export class TwitterAuth { } public async action() { - const { guest_token } = await this.fetcher.fetchJson<{ guest_token: string }>( - "https://api.twitter.com/1.1/guest/activate.json", - { - method: "POST", - headers: this.getHeaders(), - }, - ); + const { guest_token } = await this.fetcher.fetchJson<{ guest_token: string }>({ + url: "https://api.twitter.com/1.1/guest/activate.json", + method: "POST", + headers: this.getHeaders(), + }); this.guestTokenHandler(guest_token); - const { flow_token } = await this.fetcher.fetchJson<{ flow_token: string }>( - "https://twitter.com/i/api/1.1/onboarding/task.json?flow_name=login", - { - method: "POST", - headers: this.getHeaders(), - body: JSON.stringify({ - input_flow_data: { - flow_context: { debug_overrides: {}, start_location: { location: "manual_link" } }, - }, - subtask_versions: { - action_list: 2, - alert_dialog: 1, - app_download_cta: 1, - check_logged_in_account: 1, - choice_selection: 3, - contacts_live_sync_permission_prompt: 0, - cta: 7, - email_verification: 2, - end_flow: 1, - enter_date: 1, - enter_email: 2, - enter_password: 5, - enter_phone: 2, - enter_recaptcha: 1, - enter_text: 5, - enter_username: 2, - generic_urt: 3, - in_app_notification: 1, - interest_picker: 3, - js_instrumentation: 1, - menu_dialog: 1, - notifications_permission_prompt: 2, - open_account: 2, - open_home_timeline: 1, - open_link: 1, - phone_verification: 4, - privacy_options: 1, - security_key: 3, - select_avatar: 4, - select_banner: 2, - settings_list: 7, - show_code: 1, - sign_up: 2, - sign_up_review: 4, - tweet_selection_urt: 1, - update_users: 1, - upload_media: 1, - user_recommendations_list: 4, - user_recommendations_urt: 1, - wait_spinner: 3, - web_modal: 1, - }, - }), + const { flow_token } = await this.fetcher.fetchJson<{ flow_token: string }>({ + url: "https://twitter.com/i/api/1.1/onboarding/task.json?flow_name=login", + method: "POST", + headers: this.getHeaders(), + data: { + input_flow_data: { + flow_context: { debug_overrides: {}, start_location: { location: "manual_link" } }, + }, + subtask_versions: { + action_list: 2, + alert_dialog: 1, + app_download_cta: 1, + check_logged_in_account: 1, + choice_selection: 3, + contacts_live_sync_permission_prompt: 0, + cta: 7, + email_verification: 2, + end_flow: 1, + enter_date: 1, + enter_email: 2, + enter_password: 5, + enter_phone: 2, + enter_recaptcha: 1, + enter_text: 5, + enter_username: 2, + generic_urt: 3, + in_app_notification: 1, + interest_picker: 3, + js_instrumentation: 1, + menu_dialog: 1, + notifications_permission_prompt: 2, + open_account: 2, + open_home_timeline: 1, + open_link: 1, + phone_verification: 4, + privacy_options: 1, + security_key: 3, + select_avatar: 4, + select_banner: 2, + settings_list: 7, + show_code: 1, + sign_up: 2, + sign_up_review: 4, + tweet_selection_urt: 1, + update_users: 1, + upload_media: 1, + user_recommendations_list: 4, + user_recommendations_urt: 1, + wait_spinner: 3, + web_modal: 1, + }, }, - ); + }); this.flowToken = flow_token; diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index 0e177b0..a60c9f8 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -52,7 +52,6 @@ export class TwitterHelper implements Serializable, Hydratable { const [, userId] = twid.replace(/"/g, "").split("="); this.currentUserId = userId; - console.log(userId); } return this.currentUserId; @@ -78,35 +77,30 @@ export class TwitterHelper implements Serializable, Hydratable { variables.cursor = cursor; } - const features = { - responsive_web_twitter_blue_verified_badge_is_enabled: true, - verified_phone_label_enabled: false, - responsive_web_graphql_timeline_navigation_enabled: true, - unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: true, - tweetypie_unmention_optimization_enabled: true, - responsive_web_uc_gql_enabled: true, - vibe_api_enabled: true, - responsive_web_edit_tweet_api_enabled: true, - graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, - standardized_nudges_misinfo: true, - tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false, - interactive_text_enabled: true, - responsive_web_text_conversations_enabled: false, - responsive_web_enhance_cards_enabled: true, - }; - - const params = new URLSearchParams({ - variables: JSON.stringify(variables), - features: JSON.stringify(features), - }); - - const data = await this.fetcher.fetchJson( - `https://twitter.com/i/api/graphql/_gXC5CopoM8fIgawvyGpIg/Followers?${params.toString()}`, - { - headers: this.getHeaders(), - method: "GET", + const data = await this.fetcher.fetchJson({ + url: "https://twitter.com/i/api/graphql/_gXC5CopoM8fIgawvyGpIg/Followers", + headers: this.getHeaders(), + method: "GET", + data: { + variables: JSON.stringify(variables), + features: JSON.stringify({ + responsive_web_twitter_blue_verified_badge_is_enabled: true, + verified_phone_label_enabled: false, + responsive_web_graphql_timeline_navigation_enabled: true, + unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: true, + tweetypie_unmention_optimization_enabled: true, + responsive_web_uc_gql_enabled: true, + vibe_api_enabled: true, + responsive_web_edit_tweet_api_enabled: true, + graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, + standardized_nudges_misinfo: true, + tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false, + interactive_text_enabled: true, + responsive_web_text_conversations_enabled: false, + responsive_web_enhance_cards_enabled: true, + }), }, - ); + }); if (process.env.NODE_ENV === "development") { await fs.writeJson("followers.json", data, { From cb4985c5ce50d40338c1cfed734e945bd1ccfeb4 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 14:01:01 +0900 Subject: [PATCH 016/140] ci: make ci collect coverages --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96b0a21..971880e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: env: FORCE_COLOR: 3 run: | - yarn test + yarn coverage - name: Codecov uses: codecov/codecov-action@v3 From 23474230a099104415e30253c21cd0d15948360d Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 14:12:24 +0900 Subject: [PATCH 017/140] refactor(core): make logger can format string with arguments --- src/app.ts | 18 +++++++++--------- src/utils/fetcher.ts | 6 +++--- src/utils/logger.spec.ts | 17 ++++++++++++++++- src/utils/logger.ts | 22 +++++++++++++++++----- src/watchers/twitter/index.ts | 7 ++++++- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/app.ts b/src/app.ts index 3c73c67..6585a4d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -93,20 +93,20 @@ export class App extends Loggable { } const watcherNames = targetWatchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); - this.logger.info(`start to watch through ${watcherNames} watchers${targetWatchers.length === 1 ? "" : "s"}.`); + this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", targetWatchers.length)]); while (true) { try { const [, elapsedTime] = await throttle(this.onCycle.bind(this), this.config.watchInterval, true); - this.logger.debug( - `last task finished in ${chalk.green(prettyMilliseconds(elapsedTime, { verbose: true }))}.`, - ); + this.logger.debug(`last task finished in ${chalk.green("{}")}.`, [ + prettyMilliseconds(elapsedTime, { verbose: true }), + ]); } catch (e) { if (!(e instanceof Error)) { throw e; } - this.logger.error(`An error occurred while processing scheduled task: ${e.message}`); + this.logger.error("An error occurred while processing scheduled task: {}", [e.message]); } } } @@ -157,7 +157,7 @@ export class App extends Loggable { allUserData.push(...userData); } - this.logger.info(`All ${allUserData.length} ${pluralize("follower", allUserData.length)} collected.`); + this.logger.info(`all {} {} collected.`, [allUserData.length, pluralize("follower", allUserData.length)]); const followingMap = await this.userLogRepository.getFollowStatusMap(); const oldUsers = await this.userRepository.find(); @@ -173,9 +173,9 @@ export class App extends Loggable { [unfollowers, UserLogType.Unfollow], ]); - this.logger.info(`all ${newLogs.length} ${pluralize("log", newLogs.length)} saved.`); + this.logger.info(`all {} {} saved`, [newLogs.length, pluralize("log", newLogs.length)]); - this.logger.info(`tracked ${newFollowers.length} new ${pluralize("follower", newFollowers.length)}.`); - this.logger.info(`tracked ${unfollowers.length} un${pluralize("follower", unfollowers.length)}.`); + this.logger.info("tracked {} new {}", [newFollowers.length, pluralize("follower", newFollowers.length)]); + this.logger.info("tracked {} un{}", [unfollowers.length, pluralize("follower", unfollowers.length)]); }; } diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index 560aac0..3674baa 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -80,11 +80,11 @@ export class Fetcher implements Serializable, Hydratable { throw new Error(`Failed to fetch ${url}: (${response.status} ${response.statusText})`); } - this.logger.error(`Failed to fetch ${url}: (${response.status} ${response.statusText})`); + this.logger.error("failed to fetch {}: ({} {})", [url, response.status, response.statusText]); if (retryDelay > 0) { - this.logger.error(`Retrying in ${retryDelay}ms ...`); + this.logger.error("retrying in {}ms ...", [retryDelay]); } else { - this.logger.error("Retrying ..."); + this.logger.error("retrying ..."); } await sleep(retryDelay); diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index 9080322..d76c66e 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -78,6 +78,21 @@ describe("Logger class", () => { expect(buffer[0].trim()).toMatch(/Test$/); expect(buffer[1].trim()).toMatch(/failed. \([0-9]*?ms\)/); - expect(buffer[2].trim().includes("Last task failed with error: Test")).toBeTruthy(); + expect(buffer[2].trim().includes("Test")).toBeTruthy(); + }); + + it("should format the log message correctly", () => { + const mockedLogContent = "test {} {}"; + const mockedArgs = ["1", "2"]; + target.info(mockedLogContent, mockedArgs); + + expect(buffer[0].trim().endsWith("test 1 2")).toBeTruthy(); + }); + + it("should leave the {} in the log message if there are no arguments", () => { + const mockedLogContent = "test {} {}"; + target.info(mockedLogContent, []); + + expect(buffer[0].trim().endsWith("test {} {}")).toBeTruthy(); }); }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index e372a95..83a63e9 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -4,7 +4,7 @@ import chalk from "chalk"; import { measureTime } from "@utils/measureTime"; import { Fn } from "@utils/types"; -type LoggerFn = (content: string, breakLine?: boolean) => void; +type LoggerFn = (content: string, args?: any[], breakLine?: boolean) => void; export type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; const LOG_LEVEL_COLOR_MAP: Record> = { @@ -53,14 +53,26 @@ export class Logger implements Record { private createLoggerFunction = (level: LogLevel): LoggerFn => { const levelString = LOG_LEVEL_COLOR_MAP[level](level.toUpperCase()); - return (content, breakLine = true) => { + return (content, args, breakLine = true) => { const tokens = chalk.green( [chalk.cyan(dayjs().format("HH:mm:ss.SSS")), chalk.yellow(this.name), levelString] .map(t => `[${t}]`) .join(""), ); - const formattedString = `${tokens} ${content}${breakLine ? "\n" : ""}`; + // replace all {} with the corresponding argument + let message = content; + if (args) { + message = content.replace(/{}/g, () => { + if (args.length === 0) { + return "{}"; + } + + return args.shift(); + }); + } + + const formattedString = `${tokens} ${message}${breakLine ? "\n" : ""}`; if (Logger.isLocked) { Logger.buffer.push(formattedString); } else { @@ -80,7 +92,7 @@ export class Logger implements Record { message, done = "done.", }: WorkOptions): Promise => { - this[level](message, false); + this[level](message, [], false); Logger.setLock(true); const measuredData = await measureTime(work); @@ -88,7 +100,7 @@ export class Logger implements Record { if ("exception" in measuredData) { process.stdout.write(`failed. ${time}\n`); - this[failedLevel](`Last task failed with error: ${measuredData.exception.message}`); + this[failedLevel](`${measuredData.exception.message}`); Logger.setLock(false); return null; } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 1b02b24..5df5271 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +import pluralize from "pluralize"; + import { BaseWatcher } from "@watchers/base"; import { TwitterHelper } from "@watchers/twitter/helper"; @@ -31,7 +33,10 @@ export class TwitterWatcher extends BaseWatcher { public async doWatch() { const allFollowers = await this.helper.getAllFollowers(); - this.logger.silly(`Successfully crawled ${allFollowers.length} followers`); + this.logger.silly("Successfully crawled {} {}", [ + allFollowers.length, + pluralize("follower", allFollowers.length), + ]); return allFollowers.map(user => ({ uniqueId: user.rest_id, From 7ce9f67dbcb552b43eef1dcfe035b571f017abf0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 14:13:12 +0900 Subject: [PATCH 018/140] test: make test suite not to collect coverages when its not explicitly set --- jest.config.ts | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 61114b0..a8f5f81 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,12 +9,13 @@ const jestConfig: JestConfigWithTsJest = { transform: { "^.+\\.(t|j)s$": "ts-jest", }, - collectCoverageFrom: ["**/*.ts", "!coverage/**"], + collectCoverageFrom: ["**/*.ts", "!coverage/**", "!utils/noop.ts", "!index.ts", "!app.ts"], coverageDirectory: "../coverage", testEnvironment: "node", roots: [""], modulePaths: [compilerOptions.baseUrl], // <-- This will be set to 'baseUrl' value moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/../" }), + collectCoverage: false, }; export = jestConfig; diff --git a/package.json b/package.json index 67973d1..06c0925 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", "test": "jest", - "coverage": "jest --coverage --watchAll" + "coverage": "jest --coverage" }, "bin": { "cage": "./bin/cage.cjs" From a140d6591d93e5e45a9c6a488ceb99b48d5f0879 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 15:04:56 +0900 Subject: [PATCH 019/140] refactor(core): remove exit handler and cleaning up routine --- src/app.ts | 72 ++++++++++++----------------------- src/watchers/base.ts | 1 - src/watchers/twitter/index.ts | 1 - 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/app.ts b/src/app.ts index 6585a4d..945e053 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,8 +15,8 @@ import { AVAILABLE_WATCHERS } from "@watchers"; import { Config } from "@utils/config"; import { throttle } from "@utils/throttle"; -import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; import { mapBy } from "@utils/mapBy"; +import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; export class App extends Loggable { private readonly followerDataSource: DataSource; @@ -42,17 +42,10 @@ export class App extends Loggable { this.userRepository = new UserRepository(this.followerDataSource.getRepository(User)); this.userLogRepository = new UserLogRepository(this.followerDataSource.getRepository(UserLog)); - - process.on("exit", this.cleanUp.bind(this)); - process.on("SIGTERM", this.cleanUp.bind(this)); - process.on("SIGINT", this.cleanUp.bind(this)); - process.on("SIGUSR1", this.cleanUp.bind(this)); - process.on("SIGUSR2", this.cleanUp.bind(this)); } public async run() { this.logger.clear(); - this.logger.info("Hello World from Cage! 🐦"); this.config = await Config.create(); if (!this.config) { @@ -61,12 +54,6 @@ export class App extends Loggable { } const targetWatchers = this.config.watchers; - await this.logger.work({ - level: "info", - message: "initialize database ... ", - work: () => this.followerDataSource.initialize(), - }); - for (const watcher of targetWatchers) { await this.logger.work({ level: "debug", @@ -84,6 +71,26 @@ export class App extends Loggable { }); } + await this.logger.work({ + level: "info", + message: "initialize database ... ", + work: () => this.followerDataSource.initialize(), + }); + + for (const watcher of targetWatchers) { + await this.logger.work({ + level: "debug", + message: `saving serialized data of \`${chalk.green(watcher.getName())}\` watcher ... `, + work: async () => { + const data = watcher.serialize(); + const filePath = `./dump/${watcher.getName().toLowerCase()}.json`; + + await fs.ensureFile(filePath); + await fs.writeJson(filePath, data, { spaces: 4 }); + }, + }); + } + for (const watcher of targetWatchers) { await this.logger.work({ level: "info", @@ -101,6 +108,10 @@ export class App extends Loggable { this.logger.debug(`last task finished in ${chalk.green("{}")}.`, [ prettyMilliseconds(elapsedTime, { verbose: true }), ]); + + if (this.cleaningUp) { + break; + } } catch (e) { if (!(e instanceof Error)) { throw e; @@ -111,39 +122,6 @@ export class App extends Loggable { } } - private async cleanUp() { - if (!this.config || this.cleaningUp) { - return; - } - - this.cleaningUp = true; - - const targetWatchers = this.config.watchers; - for (const watcher of targetWatchers) { - await this.logger.work({ - level: "info", - message: `finalize \`${chalk.green(watcher.getName())}\` watcher ... `, - work: () => watcher.finalize(), - }); - } - - for (const watcher of targetWatchers) { - await this.logger.work({ - level: "debug", - message: `saving serialized data of \`${chalk.green(watcher.getName())}\` watcher ... `, - work: async () => { - const data = watcher.serialize(); - const filePath = `./dump/${watcher.getName().toLowerCase()}.json`; - - await fs.ensureFile(filePath); - await fs.writeJson(filePath, data, { spaces: 4 }); - }, - }); - } - - this.logger.info("Bye World from Cage! 🐦"); - } - private onCycle = async () => { if (!this.config) { throw new Error("Config is not loaded."); diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 812a3ab..deec61b 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -11,7 +11,6 @@ export abstract class BaseWatcher extends Loggable implements Serializable, Hydr } public abstract initialize(context: ProviderInitializeContext): Promise; - public abstract finalize(): Promise; public abstract doWatch(): Promise; diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 5df5271..1d8384a 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -28,7 +28,6 @@ export class TwitterWatcher extends BaseWatcher { await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); } } - public async finalize(): Promise {} public async doWatch() { const allFollowers = await this.helper.getAllFollowers(); From 01bd940b9ce4bd6c50e396b80b8d080738bbaf6a Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 15:09:43 +0900 Subject: [PATCH 020/140] chore: add coverage badge on README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a6f3aba..a8c6747 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ MIT License + + Codecov +
(almost) realtime unfollower detection for any social services
From 1f3d08a4fac36e3fe3e0db96131e717f16dc3dce Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 15:13:10 +0900 Subject: [PATCH 021/140] refactor(core): rename log level from `silly` to `verbose` --- src/utils/fetcher.spec.ts | 2 +- src/utils/logger.spec.ts | 2 +- src/utils/logger.ts | 8 ++++---- src/watchers/twitter/index.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/fetcher.spec.ts b/src/utils/fetcher.spec.ts index de3d5f2..cf9a643 100644 --- a/src/utils/fetcher.spec.ts +++ b/src/utils/fetcher.spec.ts @@ -15,7 +15,7 @@ describe("Fetcher class", function () { warn: jest.fn(), error: jest.fn(), debug: jest.fn(), - silly: jest.fn(), + verbose: jest.fn(), }, }); }); diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index d76c66e..aa97135 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -4,7 +4,7 @@ import { Logger, LogLevel } from "@utils/logger"; import { sleep } from "@utils/sleep"; describe("Logger class", () => { - const TARGET_LOG_LEVELS: LogLevel[] = ["info", "warn", "error", "debug", "silly"]; + const TARGET_LOG_LEVELS: LogLevel[] = ["info", "warn", "error", "debug", "verbose"]; let target: Logger; let buffer: string[]; let logMockFn: jest.Mock; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 83a63e9..1d88230 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -5,10 +5,10 @@ import { measureTime } from "@utils/measureTime"; import { Fn } from "@utils/types"; type LoggerFn = (content: string, args?: any[], breakLine?: boolean) => void; -export type LogLevel = "silly" | "info" | "warn" | "error" | "debug"; +export type LogLevel = "verbose" | "info" | "warn" | "error" | "debug"; const LOG_LEVEL_COLOR_MAP: Record> = { - silly: chalk.blue, + verbose: chalk.blue, info: chalk.cyan, warn: chalk.yellow, error: chalk.red, @@ -40,14 +40,14 @@ export class Logger implements Record { public readonly warn: LoggerFn; public readonly error: LoggerFn; public readonly debug: LoggerFn; - public readonly silly: LoggerFn; + public readonly verbose: LoggerFn; public constructor(private readonly name: string) { this.info = this.createLoggerFunction("info"); this.warn = this.createLoggerFunction("warn"); this.error = this.createLoggerFunction("error"); this.debug = this.createLoggerFunction("debug"); - this.silly = this.createLoggerFunction("silly"); + this.verbose = this.createLoggerFunction("verbose"); } private createLoggerFunction = (level: LogLevel): LoggerFn => { diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 1d8384a..7acb9b7 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -32,7 +32,7 @@ export class TwitterWatcher extends BaseWatcher { public async doWatch() { const allFollowers = await this.helper.getAllFollowers(); - this.logger.silly("Successfully crawled {} {}", [ + this.logger.verbose("Successfully crawled {} {}", [ allFollowers.length, pluralize("follower", allFollowers.length), ]); From cd763c9211a07bfd0c977bfff2be81f31c097fc7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 16:53:32 +0900 Subject: [PATCH 022/140] refactor: reimplement how watcher state saved or loaded --- src/app.ts | 48 +++++------------------ src/utils/config.ts | 2 +- src/utils/logger.ts | 2 +- src/utils/types.ts | 10 ----- src/watchers/base.ts | 72 ++++++++++++++++++++++++++++++---- src/watchers/twitter/helper.ts | 17 +++++++- src/watchers/twitter/index.ts | 37 ++++++++--------- 7 files changed, 109 insertions(+), 79 deletions(-) diff --git a/src/app.ts b/src/app.ts index 945e053..fa46643 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,3 @@ -import * as fs from "fs-extra"; import * as path from "path"; import { DataSource } from "typeorm"; import chalk from "chalk"; @@ -16,14 +15,10 @@ import { AVAILABLE_WATCHERS } from "@watchers"; import { Config } from "@utils/config"; import { throttle } from "@utils/throttle"; import { mapBy } from "@utils/mapBy"; -import { Env, Loggable, ProviderInitializeContext } from "@utils/types"; +import { Loggable } from "@utils/types"; export class App extends Loggable { private readonly followerDataSource: DataSource; - private readonly initializeContext: Readonly = { - env: process.env as Env, - }; - private readonly userRepository: UserRepository; private readonly userLogRepository: UserLogRepository; @@ -53,52 +48,29 @@ export class App extends Loggable { return; } - const targetWatchers = this.config.watchers; - for (const watcher of targetWatchers) { - await this.logger.work({ - level: "debug", - message: `loading lastly dumped data of \`${chalk.green(watcher.getName())}\` watcher ... `, - work: async () => { - const fileName = `${watcher.getName().toLowerCase()}.json`; - const filePath = `./dump/${fileName}`; - - if (!fs.existsSync(filePath)) { - throw new Error(`Dump file \`${fileName}\` does not exist.`); - } - - watcher.hydrate(await fs.readJSON(filePath)); - }, - }); - } - await this.logger.work({ level: "info", - message: "initialize database ... ", + message: "initialize database", work: () => this.followerDataSource.initialize(), }); + const targetWatchers = this.config.watchers; for (const watcher of targetWatchers) { - await this.logger.work({ - level: "debug", - message: `saving serialized data of \`${chalk.green(watcher.getName())}\` watcher ... `, - work: async () => { - const data = watcher.serialize(); - const filePath = `./dump/${watcher.getName().toLowerCase()}.json`; - - await fs.ensureFile(filePath); - await fs.writeJson(filePath, data, { spaces: 4 }); - }, - }); + await watcher.loadState(); } for (const watcher of targetWatchers) { await this.logger.work({ level: "info", - message: `initialize \`${chalk.green(watcher.getName())}\` watcher ... `, - work: () => watcher.initialize(this.initializeContext), + message: `initialize \`${chalk.green(watcher.getName())}\` watcher`, + work: () => watcher.initialize(), }); } + for (const watcher of targetWatchers) { + await watcher.saveState(); + } + const watcherNames = targetWatchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", targetWatchers.length)]); diff --git a/src/utils/config.ts b/src/utils/config.ts index 59305a7..e05735e 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -63,7 +63,7 @@ export class Config { return Config.logger.work({ level: "info", - message: `loading config file from ${pathToken} ... `, + message: `loading config file from ${pathToken}`, work: async () => { const data: RawConfigData = await fs.readJSON(filePath); const valid = validate(data); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 1d88230..c4c52b2 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -92,7 +92,7 @@ export class Logger implements Record { message, done = "done.", }: WorkOptions): Promise => { - this[level](message, [], false); + this[level](`${message} ... `, [], false); Logger.setLock(true); const measuredData = await measureTime(work); diff --git a/src/utils/types.ts b/src/utils/types.ts index 1945a8e..59ca698 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,4 +1,3 @@ -import type { TWITTER_PROVIDER_ENV_KEYS } from "@watchers/twitter"; import { Logger } from "@utils/logger"; export type Fn = TArgs extends unknown[] @@ -10,15 +9,6 @@ export type AsyncFn = Fn>; export type Work = Fn | T>; export type Nullable = T | null | undefined; -export type KnownEnvKeys = `CAGE_${TWITTER_PROVIDER_ENV_KEYS}`; -export type Env = { - [key in KnownEnvKeys]?: string | undefined; -}; - -export interface ProviderInitializeContext { - readonly env: Readonly; -} - export interface Serializable { serialize(): Record; } diff --git a/src/watchers/base.ts b/src/watchers/base.ts index deec61b..c1a135b 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,8 +1,15 @@ -import { Hydratable, Loggable, ProviderInitializeContext, Serializable } from "@utils/types"; -import { UserData } from "@root/repositories/models/user"; +import * as path from "path"; +import * as argon2 from "argon2"; +import * as fs from "fs-extra"; -export abstract class BaseWatcher extends Loggable implements Serializable, Hydratable { - protected constructor(name: string) { +import { UserData } from "@repositories/models/user"; + +import { Loggable } from "@utils/types"; + +export abstract class BaseWatcher extends Loggable { + private readonly stateFileDirectory = path.join(process.cwd(), "./dump/"); + + protected constructor(name: string, private readonly hashData: any) { super(name); } @@ -10,10 +17,61 @@ export abstract class BaseWatcher extends Loggable implements Serializable, Hydr return this.name; } - public abstract initialize(context: ProviderInitializeContext): Promise; + public abstract initialize(): Promise; public abstract doWatch(): Promise; - public abstract serialize(): Record; - public abstract hydrate(data: Record): void; + public async saveState() { + await this.logger.work({ + message: "saving state", + level: "info", + work: async () => { + const serializedData = this.serialize(); + const fileName = `${this.getName().toLowerCase()}.json`; + const filePath = path.join(this.stateFileDirectory, fileName); + + serializedData["__hash__"] = await this.hash(); + + await fs.ensureFile(filePath); + await fs.writeJson(filePath, serializedData, { spaces: 4 }); + }, + }); + } + public async loadState() { + await this.logger.work({ + message: "loading state", + level: "info", + work: async () => { + const fileName = `${this.getName().toLowerCase()}.json`; + const filePath = path.join(this.stateFileDirectory, fileName); + + if (!fs.existsSync(filePath)) { + this.logger.warn(`saved state not found. skipping loading state.`); + return; + } + + const serializedData = await fs.readJson(filePath); + const isOutdated = !(await this.checkHash(serializedData["__hash__"])); + if (isOutdated) { + this.logger.warn(`saved state is outdated. skipping loading state.`); + return; + } + + this.hydrate(serializedData); + }, + }); + } + + protected async hash() { + const hash = await argon2.hash(JSON.stringify(this.hashData)); + return Buffer.from(hash).toString("base64"); + } + protected async checkHash(hash: string) { + // base64 to string + hash = Buffer.from(hash, "base64").toString("utf-8"); + return argon2.verify(hash, JSON.stringify(this.hashData)); + } + + protected abstract serialize(): Record; + protected abstract hydrate(data: Record): void; } diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index a60c9f8..e08b330 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -57,8 +57,21 @@ export class TwitterHelper implements Serializable, Hydratable { return this.currentUserId; } - public doAuth() { - return new TwitterAuth(this.fetcher, this.getHeaders.bind(this), token => (this.guest = token)); + public async doAuth(userId: string, password: string) { + await new TwitterAuth(this.fetcher, this.getHeaders.bind(this), token => (this.guest = token)) + .doInstrumentation() + .setUserId(userId) + .setPassword(password) + .doDuplicationCheck() + .action(); + + const { twid } = this.fetcher.getCookies(); + if (!twid) { + throw new Error("twid cookie not found"); + } + + const [, currentUserId] = twid.replace(/"/g, "").split("="); + this.currentUserId = currentUserId; } public async getFollowers(cursor: FollowerCursor | null = null): Promise { diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 7acb9b7..49b9c26 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -6,26 +6,29 @@ import { TwitterHelper } from "@watchers/twitter/helper"; import { UserData } from "@root/repositories/models/user"; -import { ProviderInitializeContext } from "@utils/types"; - -export type TWITTER_PROVIDER_ENV_KEYS = "TWITTER_USER_ID" | "TWITTER_PASSWORD"; - export class TwitterWatcher extends BaseWatcher { private readonly helper: TwitterHelper = new TwitterHelper(); + private readonly userId: string; + private readonly password: string; public constructor() { - super("Twitter"); - } - - public async initialize({ env }: ProviderInitializeContext): Promise { - if (!env.CAGE_TWITTER_USER_ID) { + if (!process.env.CAGE_TWITTER_USER_ID) { throw new Error("Environment variable 'CAGE_TWITTER_USER_ID' should be provided."); - } else if (!env.CAGE_TWITTER_PASSWORD) { + } + + if (!process.env.CAGE_TWITTER_PASSWORD) { throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); } + super("Twitter", [process.env.CAGE_TWITTER_USER_ID, process.env.CAGE_TWITTER_PASSWORD]); + + this.userId = process.env.CAGE_TWITTER_USER_ID; + this.password = process.env.CAGE_TWITTER_PASSWORD; + } + + public async initialize(): Promise { if (!this.helper.isLogged) { - await this.login(env.CAGE_TWITTER_USER_ID, env.CAGE_TWITTER_PASSWORD); + await this.login(this.userId, this.password); } } @@ -45,22 +48,16 @@ export class TwitterWatcher extends BaseWatcher { })); } - public serialize(): Record { + protected serialize(): Record { return { helper: this.helper.serialize(), }; } - public hydrate(data: Record): void { + protected hydrate(data: Record): void { this.helper.hydrate(data.helper); } private async login(userId: string, password: string) { - await this.helper - .doAuth() - .doInstrumentation() - .setUserId(userId) - .setPassword(password) - .doDuplicationCheck() - .action(); + await this.helper.doAuth(userId, password); } } From c1b350121693fc0f320dee0cae90f25775caeb85 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 16:56:09 +0900 Subject: [PATCH 023/140] test(core): fix logger printed value expectation --- src/utils/logger.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index aa97135..e89ff6a 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -60,7 +60,7 @@ describe("Logger class", () => { expect(target.work).toBeDefined(); expect(mockedWorkResult).toBe(1); - expect(buffer[0].trim()).toMatch(/Test$/); + expect(buffer[0].trim()).toMatch(/Test ...$/); expect(buffer[1].trim()).toMatch("done."); }); @@ -76,7 +76,7 @@ describe("Logger class", () => { level: "info", }); - expect(buffer[0].trim()).toMatch(/Test$/); + expect(buffer[0].trim()).toMatch(/Test ...$/); expect(buffer[1].trim()).toMatch(/failed. \([0-9]*?ms\)/); expect(buffer[2].trim().includes("Test")).toBeTruthy(); }); From 1e9208dd09eb54d5f37c574ececffb25518cdbc7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 17:33:56 +0900 Subject: [PATCH 024/140] chore: add required dependency --- package.json | 1 + yarn.lock | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 06c0925..3933f9f 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "dependencies": { "ajv": "^8.11.2", + "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", "cronstrue": "^2.20.0", diff --git a/yarn.lock b/yarn.lock index 20bd563..f6e5eb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -633,7 +633,7 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@mapbox/node-pre-gyp@^1.0.0": +"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.10": version "1.0.10" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== @@ -957,6 +957,11 @@ dependencies: "@octokit/openapi-types" "^13.11.0" +"@phc/format@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4" + integrity sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ== + "@semantic-release/commit-analyzer@^9.0.2": version "9.0.2" resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz#a78e54f9834193b55f1073fa6258eecc9a545e03" @@ -1549,6 +1554,15 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +argon2@^0.30.2: + version "0.30.2" + resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.30.2.tgz#b308a039d6d0cda5a3c47cbddd12685f033c33fa" + integrity sha512-RBbXTUsrJUQH259/72CCJxQa0hV961pV4PyZ7R1czGkArSsQP4DToCS2axmNfHywXaBNEMPWMW6rM82EArulYA== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.10" + "@phc/format" "^1.0.0" + node-addon-api "^5.0.0" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -4402,6 +4416,11 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== + node-cron@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" From 36c11ca19919f2c69df92d1d0e712a1b58ae54f2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 17:34:12 +0900 Subject: [PATCH 025/140] ci: update jest coverage command --- .github/workflows/test.yml | 2 +- jest.config.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 971880e..d851038 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: env: FORCE_COLOR: 3 run: | - yarn coverage + yarn coverage --ci --verbose --testTimeout=10000 - name: Codecov uses: codecov/codecov-action@v3 diff --git a/jest.config.ts b/jest.config.ts index a8f5f81..b1728a4 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -16,6 +16,14 @@ const jestConfig: JestConfigWithTsJest = { modulePaths: [compilerOptions.baseUrl], // <-- This will be set to 'baseUrl' value moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "/../" }), collectCoverage: false, + coverageThreshold: { + global: { + lines: 0, + branches: 0, + functions: 0, + statements: 0, + }, + }, }; export = jestConfig; From fd6c4ec341dd68f7f446865f8c7ec485e78988a4 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 21:21:28 +0900 Subject: [PATCH 026/140] feat(discord): implement discord webhook notifier --- .eslintrc.json | 10 ++- config.schema.json | 61 +++++++++++++++++ package.json | 6 +- scripts/generate-schema.ts | 15 ++++ src/app.ts | 47 ++++++++++--- src/notifiers/base.ts | 16 +++++ src/notifiers/discord.ts | 121 +++++++++++++++++++++++++++++++++ src/notifiers/index.ts | 8 +++ src/utils/config.ts | 56 +++++++-------- src/utils/config.type.ts | 8 +++ src/utils/fetcher.ts | 28 ++++---- src/utils/logger.ts | 12 ++++ src/utils/types.ts | 8 +++ src/watchers/base.ts | 6 +- src/watchers/index.ts | 7 +- src/watchers/twitter/helper.ts | 2 + src/watchers/twitter/index.ts | 6 +- tsconfig.json | 5 +- yarn.lock | 40 +++++++++-- 19 files changed, 393 insertions(+), 69 deletions(-) create mode 100644 config.schema.json create mode 100644 scripts/generate-schema.ts create mode 100644 src/notifiers/base.ts create mode 100644 src/notifiers/discord.ts create mode 100644 src/notifiers/index.ts create mode 100644 src/utils/config.type.ts diff --git a/.eslintrc.json b/.eslintrc.json index 276e885..126b69b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,6 +16,14 @@ "@typescript-eslint/interface-name-prefix": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] } } diff --git a/config.schema.json b/config.schema.json new file mode 100644 index 0000000..2583f42 --- /dev/null +++ b/config.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/ConfigData", + "definitions": { + "ConfigData": { + "type": "object", + "properties": { + "watchInterval": { + "type": "number" + }, + "watchers": { + "type": "object", + "properties": { + "twitter": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "twitter" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "notifiers": { + "type": "object", + "properties": { + "discord": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "discord" + }, + "webhookUrl": { + "type": "string" + } + }, + "required": [ + "type", + "webhookUrl" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": [ + "watchInterval", + "watchers" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 3933f9f..6e77aa5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", "test": "jest", - "coverage": "jest --coverage" + "coverage": "jest --coverage", + "schema": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./scripts/generate-schema.ts" }, "bin": { "cage": "./bin/cage.cjs" @@ -31,7 +32,7 @@ "@semantic-release/npm": "^9.0.1", "@semantic-release/release-notes-generator": "^10.0.3", "@types/fs-extra": "^9.0.13", - "@types/jest": "^29.2.3", + "@types/jest": "^29.2.4", "@types/listr": "^0.14.4", "@types/lodash": "^4.14.191", "@types/lodash.chunk": "^4.2.7", @@ -54,6 +55,7 @@ "rimraf": "^3.0.2", "semantic-release": "^19.0.5", "ts-jest": "^29.0.3", + "ts-json-schema-generator": "^1.1.2", "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.0", "typescript": "^4.9.3" diff --git a/scripts/generate-schema.ts b/scripts/generate-schema.ts new file mode 100644 index 0000000..89de79b --- /dev/null +++ b/scripts/generate-schema.ts @@ -0,0 +1,15 @@ +import * as fs from "fs-extra"; +import * as tsj from "ts-json-schema-generator"; + +const config: tsj.Config = { + path: "src/utils/config.type.ts", + tsconfig: "tsconfig.json", + type: "ConfigData", + expose: "none", +}; + +(async () => { + const schema = tsj.createGenerator(config).createSchema(config.type); + const schemaString = JSON.stringify(schema, null, 4); + await fs.writeFile("config.schema.json", schemaString); +})(); diff --git a/src/app.ts b/src/app.ts index fa46643..2085a3e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,31 +48,50 @@ export class App extends Loggable { return; } + const watchers = this.config.watchers; + const notifiers = this.config.notifiers; + + if (!watchers.length) { + this.logger.warn("no watchers are configured. exiting..."); + return; + } + await this.logger.work({ level: "info", message: "initialize database", work: () => this.followerDataSource.initialize(), }); - const targetWatchers = this.config.watchers; - for (const watcher of targetWatchers) { + for (const watcher of watchers) { + const options = this.config.watcherOptions[watcher.getName()]; await watcher.loadState(); - } - for (const watcher of targetWatchers) { await this.logger.work({ level: "info", message: `initialize \`${chalk.green(watcher.getName())}\` watcher`, - work: () => watcher.initialize(), + work: () => watcher.initialize(options), }); - } - for (const watcher of targetWatchers) { await watcher.saveState(); } - const watcherNames = targetWatchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); - this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", targetWatchers.length)]); + for (const notifier of notifiers) { + const options = this.config.notifierOptions?.[notifier.getName().toLowerCase()]; + if (!options) { + this.logger.warn(`no options are configured for \`${chalk.green("{}")}\` notifier. skipping...`, [ + notifier.getName(), + ]); + } + + await this.logger.work({ + level: "info", + message: `initialize \`${chalk.green(notifier.getName())}\` notifier`, + work: () => notifier.initialize(options), + }); + } + + const watcherNames = watchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); + this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", watchers.length)]); while (true) { try { @@ -127,5 +146,15 @@ export class App extends Loggable { this.logger.info("tracked {} new {}", [newFollowers.length, pluralize("follower", newFollowers.length)]); this.logger.info("tracked {} un{}", [unfollowers.length, pluralize("follower", unfollowers.length)]); + + const notifiers = this.config.notifiers; + if (!notifiers.length) { + this.logger.info("no notifiers are configured. skipping notification..."); + return; + } + + for (const notifier of notifiers) { + await notifier.notify(newLogs); + } }; } diff --git a/src/notifiers/base.ts b/src/notifiers/base.ts new file mode 100644 index 0000000..7d9847a --- /dev/null +++ b/src/notifiers/base.ts @@ -0,0 +1,16 @@ +import { UserLog } from "@repositories/models/user-log"; +import { Loggable } from "@utils/types"; + +export abstract class BaseNotifier extends Loggable { + protected constructor(name: string) { + super(name); + } + + public getName() { + return super.getName().replace("Notifier", ""); + } + + public abstract initialize(options: Record): Promise; + + public abstract notify(logs: UserLog[]): Promise; +} diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts new file mode 100644 index 0000000..c931476 --- /dev/null +++ b/src/notifiers/discord.ts @@ -0,0 +1,121 @@ +import pluralize from "pluralize"; + +import { UserLog, UserLogType } from "@repositories/models/user-log"; + +import { BaseNotifier } from "@notifiers/base"; + +import { Fetcher } from "@utils/fetcher"; +import { Logger } from "@utils/logger"; + +export interface DiscordNotifierOptions { + type: "discord"; + webhookUrl: string; +} + +interface DiscordWebhookData { + content: any; + embeds: { + title: string; + color: number; + fields: { + name: string; + value: string; + }[]; + author: { + name: string; + }; + timestamp: string; + }[]; + attachments: []; +} + +export class DiscordNotifier extends BaseNotifier { + private readonly fetcher = new Fetcher(); + private webhookUrl: string | null = null; + + public constructor() { + super(DiscordNotifier.name); + } + + public async initialize(options: DiscordNotifierOptions) { + this.webhookUrl = options.webhookUrl; + } + + public async notify(logs: UserLog[]) { + if (!this.webhookUrl) { + throw new Error("DiscordNotifier is not initialized"); + } + + if (logs.length <= 0) { + return; + } + + const data: DiscordWebhookData = { + content: null, + embeds: [ + { + title: Logger.format( + "Total {} {} {} found", + logs.length, + pluralize("change", logs.length), + pluralize("was", logs.length), + ), + color: 5814783, + fields: [], + author: { + name: "Cage Report", + }, + timestamp: "2022-12-29T15:00:00.000Z", + }, + ], + attachments: [], + }; + + const newFields: DiscordWebhookData["embeds"][0]["fields"] = []; + const followerLogs = logs.filter(l => l.type === UserLogType.Follow); + if (followerLogs.length > 0) { + const field = this.generateEmbedField( + followerLogs, + Logger.format("🎉 {} new {}", logs.length, pluralize("follower", logs.length)), + ); + + newFields.push(field); + } + + const unfollowerLogs = logs.filter(l => l.type === UserLogType.Unfollow); + if (unfollowerLogs.length > 0) { + const field = this.generateEmbedField( + unfollowerLogs, + Logger.format("❌ {} {}", logs.length, pluralize("unfollower", logs.length)), + ); + + newFields.push(field); + } + + data.embeds[0].fields.push(...newFields); + + await this.fetcher.fetch({ + url: this.webhookUrl, + method: "POST", + data, + }); + } + + private generateEmbedField(logs: UserLog[], title: string) { + return { + name: title, + value: logs + .map(l => l.user) + .slice(0, 10) + .map(user => { + return Logger.format( + "[{} (@{})](https://twitter.com/{})", + user.displayName, + user.userId, + user.userId, + ); + }) + .join("\n"), + }; + } +} diff --git a/src/notifiers/index.ts b/src/notifiers/index.ts new file mode 100644 index 0000000..30a571b --- /dev/null +++ b/src/notifiers/index.ts @@ -0,0 +1,8 @@ +import { BaseNotifier } from "@notifiers/base"; +import { DiscordNotifier, DiscordNotifierOptions } from "@notifiers/discord"; +import { TypeMap } from "@utils/types"; + +export type NotifierOptions = DiscordNotifierOptions; +export type NotifierOptionMap = TypeMap; + +export const AVAILABLE_NOTIFIERS: ReadonlyArray = [new DiscordNotifier()]; diff --git a/src/utils/config.ts b/src/utils/config.ts index e05735e..529cd14 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,39 +1,21 @@ +import _ from "lodash"; import * as path from "path"; import * as fs from "fs-extra"; import chalk from "chalk"; -import Ajv, { JSONSchemaType } from "ajv"; +import Ajv from "ajv"; import betterAjvErrors from "better-ajv-errors"; import { AVAILABLE_WATCHERS } from "@watchers"; +import { AVAILABLE_NOTIFIERS } from "@notifiers"; +import { ConfigData } from "@utils/config.type"; import { Logger } from "@utils/logger"; -const ajv = new Ajv({ allErrors: true }); - -interface RawConfigData { - target: string[]; - watchInterval: number; -} +import schema from "@root/../config.schema.json"; -const schema: JSONSchemaType = { - type: "object", - properties: { - target: { - type: "array", - items: { - type: "string", - enum: AVAILABLE_WATCHERS.map(w => w.getName().toLowerCase()), - }, - }, - watchInterval: { - type: "integer", - }, - }, - required: ["target", "watchInterval"], - additionalProperties: false, -}; +const ajv = new Ajv({ allErrors: true }); -const validate = ajv.compile(schema); +const validate = ajv.compile(schema); export class Config { private static readonly logger = new Logger("Config"); @@ -65,7 +47,7 @@ export class Config { level: "info", message: `loading config file from ${pathToken}`, work: async () => { - const data: RawConfigData = await fs.readJSON(filePath); + const data: ConfigData = await fs.readJSON(filePath); const valid = validate(data); if (!valid && validate.errors) { const output = betterAjvErrors(schema, data, validate.errors, { @@ -82,14 +64,28 @@ export class Config { } public get watchers() { - return AVAILABLE_WATCHERS.filter(w => this.target.includes(w.getName().toLowerCase())); + const names = Object.keys(this.rawData.watchers); + return AVAILABLE_WATCHERS.filter(w => names.includes(w.getName().toLowerCase())); } - public get target() { - return [...this.rawData.target]; + public get watcherOptions() { + return _.cloneDeep(this.rawData.watchers); } + + public get notifiers() { + if (!this.rawData.notifiers) { + return []; + } + + const names = Object.keys(this.rawData.notifiers); + return AVAILABLE_NOTIFIERS.filter(n => names.includes(n.getName().replace("Notifier", "").toLowerCase())); + } + public get notifierOptions() { + return _.cloneDeep(this.rawData.notifiers); + } + public get watchInterval() { return this.rawData.watchInterval; } - private constructor(private readonly rawData: RawConfigData) {} + private constructor(private readonly rawData: ConfigData) {} } diff --git a/src/utils/config.type.ts b/src/utils/config.type.ts new file mode 100644 index 0000000..d996090 --- /dev/null +++ b/src/utils/config.type.ts @@ -0,0 +1,8 @@ +import { WatcherOptionMap } from "@watchers"; +import { NotifierOptionMap } from "@notifiers"; + +export interface ConfigData { + watchInterval: number; + watchers: Partial; + notifiers?: Partial; +} diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index 3674baa..1fde050 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -67,20 +67,26 @@ export class Fetcher implements Serializable, Hydratable { fetchHeaders.set("Content-Type", "application/json"); } - const response = await this.fetchImpl(endpoint, { - method: method, - headers: fetchHeaders, - body: method === "GET" ? undefined : JSON.stringify(data), - }); + try { + const response = await this.fetchImpl(endpoint, { + method: method, + headers: fetchHeaders, + body: method === "GET" ? undefined : JSON.stringify(data), + }); - this.setCookies(response.headers.get("set-cookie")); + if (!response.ok) { + throw new Error(`${response.status} (${response.statusText})`); + } - if (!response.ok) { - if (retryCount === 0) { - throw new Error(`Failed to fetch ${url}: (${response.status} ${response.statusText})`); + this.setCookies(response.headers.get("set-cookie")); + + return response; + } catch (e) { + if (retryCount === 0 || !(e instanceof Error)) { + throw e; } - this.logger.error("failed to fetch {}: ({} {})", [url, response.status, response.statusText]); + this.logger.error("failed to fetch {}: {}", [url, e.message]); if (retryDelay > 0) { this.logger.error("retrying in {}ms ...", [retryDelay]); } else { @@ -97,8 +103,6 @@ export class Fetcher implements Serializable, Hydratable { retryDelay, }); } - - return response; } public serialize(): Record { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index c4c52b2..afca880 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -24,6 +24,18 @@ interface WorkOptions { } export class Logger implements Record { + public static format(content: string, ...args: any[]): string { + const formattedArgs = args?.map(arg => { + if (typeof arg === "object") { + return JSON.stringify(arg, null, 4); + } + + return arg; + }); + + return formattedArgs ? content.replace(/{}/g, () => formattedArgs.shift()) : content; + } + private static readonly buffer: string[] = []; private static isLocked = false; diff --git a/src/utils/types.ts b/src/utils/types.ts index 59ca698..b556a50 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,9 @@ import { Logger } from "@utils/logger"; +export type TypeMap = { + [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; +}; + export type Fn = TArgs extends unknown[] ? (...arg: TArgs) => TReturn : TArgs extends void @@ -23,4 +27,8 @@ export abstract class Loggable { protected constructor(protected readonly name: string) { this.logger = new Logger(name); } + + public getName(): string { + return this.name; + } } diff --git a/src/watchers/base.ts b/src/watchers/base.ts index c1a135b..f1725d2 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -13,11 +13,7 @@ export abstract class BaseWatcher extends Loggable { super(name); } - public getName(): string { - return this.name; - } - - public abstract initialize(): Promise; + public abstract initialize(options: Record): Promise; public abstract doWatch(): Promise; diff --git a/src/watchers/index.ts b/src/watchers/index.ts index f284ddf..d6d2f49 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,4 +1,9 @@ import { BaseWatcher } from "@watchers/base"; -import { TwitterWatcher } from "@watchers/twitter"; +import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; + +import { TypeMap } from "@utils/types"; + +export type WatcherOptions = TwitterWatcherOptions; +export type WatcherOptionMap = TypeMap; export const AVAILABLE_WATCHERS: ReadonlyArray = [new TwitterWatcher()]; diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index e08b330..8976998 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -94,6 +94,8 @@ export class TwitterHelper implements Serializable, Hydratable { url: "https://twitter.com/i/api/graphql/_gXC5CopoM8fIgawvyGpIg/Followers", headers: this.getHeaders(), method: "GET", + retryCount: 3, + retryDelay: 500, data: { variables: JSON.stringify(variables), features: JSON.stringify({ diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 49b9c26..c6412ba 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -6,6 +6,10 @@ import { TwitterHelper } from "@watchers/twitter/helper"; import { UserData } from "@root/repositories/models/user"; +export interface TwitterWatcherOptions { + type: "twitter"; +} + export class TwitterWatcher extends BaseWatcher { private readonly helper: TwitterHelper = new TwitterHelper(); private readonly userId: string; @@ -26,7 +30,7 @@ export class TwitterWatcher extends BaseWatcher { this.password = process.env.CAGE_TWITTER_PASSWORD; } - public async initialize(): Promise { + public async initialize(_: TwitterWatcherOptions): Promise { if (!this.helper.isLogged) { await this.login(this.userId, this.password); } diff --git a/tsconfig.json b/tsconfig.json index 867ab10..645de76 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,6 @@ "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", - "sourceMap": true, "inlineSourceMap": true, "resolveJsonModule": true, "outDir": "./dist", @@ -31,7 +30,9 @@ "@watchers/*": ["./src/watchers/*"], "@watchers": ["./src/watchers"], "@followers/*": ["./src/followers/*"], - "@repositories/*": ["./src/repositories/*"] + "@repositories/*": ["./src/repositories/*"], + "@notifiers/*": ["./src/notifiers/*"], + "@notifiers": ["./src/notifiers"] } } } diff --git a/yarn.lock b/yarn.lock index f6e5eb8..011bdee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1171,15 +1171,15 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.2.3": - version "29.2.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.3.tgz#f5fd88e43e5a9e4221ca361e23790d48fcf0a211" - integrity sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w== +"@types/jest@^29.2.4": + version "29.2.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.4.tgz#9c155c4b81c9570dbd183eb8604aa0ae80ba5a5b" + integrity sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A== dependencies: expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -2058,6 +2058,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^9.4.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" + integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -2905,7 +2910,7 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.1: +glob@^8.0.1, glob@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== @@ -5408,6 +5413,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-stable-stringify@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61" + integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA== + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -5950,6 +5960,19 @@ ts-jest@^29.0.3: semver "7.x" yargs-parser "^21.0.1" +ts-json-schema-generator@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-1.1.2.tgz#29a0c878733b6a1bb0346fce02b1ebb8b96effa3" + integrity sha512-XMnxvndJFJEYv3NBmW7Po5bGajKdK2qH8Q078eDy60srK9+nEvbT9nLCRKd2IV/RQ7a+oc5FNylvZWveqh7jeQ== + dependencies: + "@types/json-schema" "^7.0.11" + commander "^9.4.0" + glob "^8.0.3" + json5 "^2.2.1" + normalize-path "^3.0.0" + safe-stable-stringify "^2.4.0" + typescript "~4.8.3" + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -6070,6 +6093,11 @@ typescript@^4.9.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== +typescript@~4.8.3: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== + uglify-js@^3.1.4: version "3.17.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" From cefe007f194b0468e753d18dc3367cae7f86b027 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 21:23:01 +0900 Subject: [PATCH 027/140] test: fix logger printed value expectation --- src/utils/fetcher.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/fetcher.spec.ts b/src/utils/fetcher.spec.ts index cf9a643..7caf74c 100644 --- a/src/utils/fetcher.spec.ts +++ b/src/utils/fetcher.spec.ts @@ -167,7 +167,7 @@ describe("Fetcher class", function () { }); await expect(target.fetch({ url: "", retryCount: 3, retryDelay: 0 })).rejects.toThrow( - "Failed to fetch : (500 Internal Server Error)", + "500 (Internal Server Error)", ); expect(calledCount).toBe(4); }); @@ -189,7 +189,7 @@ describe("Fetcher class", function () { const [, elapsedTime] = await throttle( expect(target.fetch({ url: "", retryCount: 3, retryDelay: 500 })).rejects.toThrow( - "Failed to fetch : (500 Internal Server Error)", + "500 (Internal Server Error)", ), 0, true, From 21a2d45501bb9dfdbfc41a78c2a155ad211d75bb Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 21:40:51 +0900 Subject: [PATCH 028/140] test(core): add test case for `Logger.format` function --- src/utils/logger.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index e89ff6a..a4dadbc 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -23,6 +23,12 @@ describe("Logger class", () => { jest.spyOn(process.stdout, "write").mockImplementation(logMockFn); }); + it("should format string correctly", () => { + const formattedMessage = Logger.format("{} {} {}", "a", "b", "c"); + + expect(formattedMessage).toBe("a b c"); + }); + it("should provide methods for all log levels", () => { const mockedLogContent = "test"; for (const logLevel of TARGET_LOG_LEVELS) { From 65f6df96f279604ecab1a5e90409911281260184 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 5 Dec 2022 22:43:26 +0900 Subject: [PATCH 029/140] refactor(core): make logger can style string with new format tokens --- src/utils/logger.spec.ts | 19 +++++++++ src/utils/logger.ts | 86 +++++++++++++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index a4dadbc..3a3ddb5 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -2,6 +2,7 @@ import stripAnsi from "strip-ansi"; import { Logger, LogLevel } from "@utils/logger"; import { sleep } from "@utils/sleep"; +import chalk from "chalk"; describe("Logger class", () => { const TARGET_LOG_LEVELS: LogLevel[] = ["info", "warn", "error", "debug", "verbose"]; @@ -29,6 +30,24 @@ describe("Logger class", () => { expect(formattedMessage).toBe("a b c"); }); + it("should format object correctly", () => { + const formattedMessage = Logger.format("{}", { a: "b", c: "d" }); + + expect(formattedMessage).toBe(JSON.stringify({ a: "b", c: "d" })); + }); + + it("should not format string if no arguments are provided", () => { + const formattedMessage = Logger.format("{} {} {}"); + + expect(formattedMessage).toBe("{} {} {}"); + }); + + it("should style string with format tokens correctly", () => { + const formattedMessage = Logger.format("{bold}", "a"); + + expect(formattedMessage).toBe(chalk.bold("a")); + }); + it("should provide methods for all log levels", () => { const mockedLogContent = "test"; for (const logLevel of TARGET_LOG_LEVELS) { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index afca880..2300c41 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -23,17 +23,81 @@ interface WorkOptions { work: () => T | Promise; } +const CHALK_FORMAT_NAMES: Array = [ + "black", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "white", + "gray", + "grey", + "blackBright", + "redBright", + "greenBright", + "yellowBright", + "blueBright", + "magentaBright", + "cyanBright", + "whiteBright", + "bgBlack", + "bgRed", + "bgGreen", + "bgYellow", + "bgBlue", + "bgMagenta", + "bgCyan", + "bgWhite", + "bgGray", + "bgGrey", + "bgBlackBright", + "bgRedBright", + "bgGreenBright", + "bgYellowBright", + "bgBlueBright", + "bgMagentaBright", + "bgCyanBright", + "bgWhiteBright", + "italic", + "bold", + "underline", + "strikethrough", +]; + export class Logger implements Record { public static format(content: string, ...args: any[]): string { - const formattedArgs = args?.map(arg => { - if (typeof arg === "object") { - return JSON.stringify(arg, null, 4); - } + if (args.length === 0) { + return content; + } - return arg; - }); + const formattedArgs = args + .map(arg => { + if (typeof arg === "object") { + return JSON.stringify(arg); + } + + return `${arg}`; + }) + .map(arg => { + const result = /(\{(.*?)\})/.exec(content); + if (result && result[2]) { + const tokens = result[2].split(":"); + let content = arg; + for (const token of tokens) { + if (CHALK_FORMAT_NAMES.includes(token as any)) { + content = chalk[token](content); + } + } - return formattedArgs ? content.replace(/{}/g, () => formattedArgs.shift()) : content; + return content; + } else { + return arg; + } + }); + + return formattedArgs.reduce((content, arg) => content.replace(/\{.*?\}/, arg), content); } private static readonly buffer: string[] = []; @@ -75,13 +139,7 @@ export class Logger implements Record { // replace all {} with the corresponding argument let message = content; if (args) { - message = content.replace(/{}/g, () => { - if (args.length === 0) { - return "{}"; - } - - return args.shift(); - }); + message = Logger.format(content, ...args); } const formattedString = `${tokens} ${message}${breakLine ? "\n" : ""}`; From 5ad4bf38850cfcd2931c91ed72b099bd61bccc21 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:33:38 +0900 Subject: [PATCH 030/140] refactor(core): make logger not to log verbose level logs when verbose mode is disabled --- src/utils/logger.spec.ts | 31 +++++++++++++++++++++---------- src/utils/logger.ts | 11 +++++++++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/utils/logger.spec.ts b/src/utils/logger.spec.ts index 3a3ddb5..0ae2d4e 100644 --- a/src/utils/logger.spec.ts +++ b/src/utils/logger.spec.ts @@ -49,6 +49,8 @@ describe("Logger class", () => { }); it("should provide methods for all log levels", () => { + Logger.verbose = true; + const mockedLogContent = "test"; for (const logLevel of TARGET_LOG_LEVELS) { clearBuffer(); @@ -59,6 +61,17 @@ describe("Logger class", () => { expect(buffer[0]).toContain(logLevel.toUpperCase()); expect(buffer[0].trim().endsWith(mockedLogContent)).toBeTruthy(); } + + Logger.verbose = false; + }); + + it("should not log if verbose mode is disabled", () => { + Logger.verbose = false; + + target.verbose("test"); + expect(buffer).toHaveLength(0); + + Logger.verbose = true; }); it("should provide a method to clear the console", () => { @@ -89,21 +102,19 @@ describe("Logger class", () => { expect(buffer[1].trim()).toMatch("done."); }); - it("should print failed information when a work fails", async () => { + it("should throw an error when work failed", async () => { const mockedWork = async () => { await sleep(1000); throw new Error("Test"); }; - await target.work({ - work: mockedWork, - message: "Test", - level: "info", - }); - - expect(buffer[0].trim()).toMatch(/Test ...$/); - expect(buffer[1].trim()).toMatch(/failed. \([0-9]*?ms\)/); - expect(buffer[2].trim().includes("Test")).toBeTruthy(); + await expect( + target.work({ + work: mockedWork, + message: "Test", + level: "info", + }), + ).rejects.toThrow("Test"); }); it("should format the log message correctly", () => { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 2300c41..86aabf4 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import chalk from "chalk"; import { measureTime } from "@utils/measureTime"; +import { noop } from "@utils/noop"; import { Fn } from "@utils/types"; type LoggerFn = (content: string, args?: any[], breakLine?: boolean) => void; @@ -67,6 +68,8 @@ const CHALK_FORMAT_NAMES: Array = [ ]; export class Logger implements Record { + public static verbose = false; + public static format(content: string, ...args: any[]): string { if (args.length === 0) { return content; @@ -130,6 +133,10 @@ export class Logger implements Record { const levelString = LOG_LEVEL_COLOR_MAP[level](level.toUpperCase()); return (content, args, breakLine = true) => { + if (level === "verbose" && !Logger.verbose) { + return noop; + } + const tokens = chalk.green( [chalk.cyan(dayjs().format("HH:mm:ss.SSS")), chalk.yellow(this.name), levelString] .map(t => `[${t}]`) @@ -161,7 +168,7 @@ export class Logger implements Record { level, message, done = "done.", - }: WorkOptions): Promise => { + }: WorkOptions): Promise => { this[level](`${message} ... `, [], false); Logger.setLock(true); @@ -172,7 +179,7 @@ export class Logger implements Record { process.stdout.write(`failed. ${time}\n`); this[failedLevel](`${measuredData.exception.message}`); Logger.setLock(false); - return null; + throw measuredData.exception; } process.stdout.write(`${done} ${time}\n`); From 9c5c41eb73994bc83852ec41e20e0b25fc253d90 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:34:59 +0900 Subject: [PATCH 031/140] refactor(core): make config file path always to be absolute when given path is relative --- src/utils/config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/config.ts b/src/utils/config.ts index 529cd14..462a2e8 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -22,6 +22,10 @@ export class Config { private static readonly DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./config.json"); public static async create(filePath: string = Config.DEFAULT_CONFIG_PATH) { + if (!path.isAbsolute(filePath)) { + filePath = path.join(process.cwd(), filePath); + } + const pathToken = `\`${chalk.green(filePath)}\``; if (!fs.existsSync(filePath)) { From 866b9b6e6502fed1a8e2b26bb83ad97224fe9325 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:36:59 +0900 Subject: [PATCH 032/140] refactor(watcher): make watcher hash data can be retrieved by its own implementation --- src/watchers/base.ts | 12 +++++++----- src/watchers/twitter/index.ts | 22 +++++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/watchers/base.ts b/src/watchers/base.ts index f1725d2..430ed22 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -9,7 +9,7 @@ import { Loggable } from "@utils/types"; export abstract class BaseWatcher extends Loggable { private readonly stateFileDirectory = path.join(process.cwd(), "./dump/"); - protected constructor(name: string, private readonly hashData: any) { + protected constructor(name: string) { super(name); } @@ -58,16 +58,18 @@ export abstract class BaseWatcher extends Loggable { }); } - protected async hash() { - const hash = await argon2.hash(JSON.stringify(this.hashData)); + private async hash() { + const hash = await argon2.hash(JSON.stringify(this.getHashData())); return Buffer.from(hash).toString("base64"); } - protected async checkHash(hash: string) { + private async checkHash(hash: string) { // base64 to string hash = Buffer.from(hash, "base64").toString("utf-8"); - return argon2.verify(hash, JSON.stringify(this.hashData)); + return argon2.verify(hash, JSON.stringify(this.getHashData())); } + protected abstract getHashData(): any; + protected abstract serialize(): Record; protected abstract hydrate(data: Record): void; } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index c6412ba..85153d4 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -16,21 +16,21 @@ export class TwitterWatcher extends BaseWatcher { private readonly password: string; public constructor() { - if (!process.env.CAGE_TWITTER_USER_ID) { + super("Twitter"); + + this.userId = process.env.CAGE_TWITTER_USER_ID || ""; + this.password = process.env.CAGE_TWITTER_PASSWORD || ""; + } + + public async initialize(_: TwitterWatcherOptions): Promise { + if (!this.userId) { throw new Error("Environment variable 'CAGE_TWITTER_USER_ID' should be provided."); } - if (!process.env.CAGE_TWITTER_PASSWORD) { + if (!this.password) { throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); } - super("Twitter", [process.env.CAGE_TWITTER_USER_ID, process.env.CAGE_TWITTER_PASSWORD]); - - this.userId = process.env.CAGE_TWITTER_USER_ID; - this.password = process.env.CAGE_TWITTER_PASSWORD; - } - - public async initialize(_: TwitterWatcherOptions): Promise { if (!this.helper.isLogged) { await this.login(this.userId, this.password); } @@ -52,6 +52,10 @@ export class TwitterWatcher extends BaseWatcher { })); } + protected getHashData(): any { + return [this.userId, this.password]; + } + protected serialize(): Record { return { helper: this.helper.serialize(), From 75ee06187d6d0546bce28ab07a1768e7e0a77add Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:38:07 +0900 Subject: [PATCH 033/140] feat(core): implement basic cli feature --- README.md | 53 +++++++++++++++++++++++++++++++++ bin/cage.cjs | 2 +- package.json | 4 ++- src/app.ts | 36 +++++++++++++++++----- src/index.ts | 17 ++++++++++- yarn.lock | 84 ++++++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 176 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a8c6747..e5b4fad 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,56 @@ ## Introduction Cage is a cli application for detecting unfollowers on any social services. + +## Usage + +```bash +$ npm install -g cage-cli + +$ cage --help +``` + +## Supported Services + +### Watchers + +| Service | Support? | +| --------- | :-----------------: | +| Twitter | ⚠️ (Partially, WIP) | +| Instagram | ❌ | +| TikTok | ❌ | +| YouTube | ❌ | +| Twitch | ❌ | +| Facebook | ❌ | +| Reddit | ❌ | +| Discord | ❌ | +| GitHub | ❌ | + +### Notifiers + +| Service | Support? | +| --------------- | :------: | +| Discord WebHook | ✅ | + +## Configuration + +this application reads configuration file from `./cage.config.json` by default. + +You can use json schema file `config.schema.json` on this repository. here is example configuration file content: + +```json +{ + "watchers": { + "twitter": { + "type": "twitter" + } + }, + "watchInterval": 60000, + "notifiers": { + "discord": { + "type": "discord", + "webhookUrl": "https://discord.com/api/webhooks/..." + } + } +} +``` diff --git a/bin/cage.cjs b/bin/cage.cjs index baf21c8..661300c 100644 --- a/bin/cage.cjs +++ b/bin/cage.cjs @@ -1,3 +1,3 @@ #!/usr/bin/env node -require("../dist/index"); +require("../dist/src/index"); diff --git a/package.json b/package.json index 6e77aa5..ee1b083 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "access": "public" }, "scripts": { - "build": "tsc --project ./tsconfig.build.json", + "build": "tsc --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json", "dev": "node --watch -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", "semantic-release": "semantic-release", "lint": "eslint \"src/**/*.ts\"", @@ -57,6 +57,7 @@ "ts-jest": "^29.0.3", "ts-json-schema-generator": "^1.1.2", "ts-node": "^10.9.1", + "tsc-alias": "^1.8.1", "tsconfig-paths": "^4.1.0", "typescript": "^4.9.3" }, @@ -65,6 +66,7 @@ "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", + "commander": "^9.4.1", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", "lodash": "^4.17.21", diff --git a/src/app.ts b/src/app.ts index 2085a3e..be7b23d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,6 +15,9 @@ import { AVAILABLE_WATCHERS } from "@watchers"; import { Config } from "@utils/config"; import { throttle } from "@utils/throttle"; import { mapBy } from "@utils/mapBy"; +import { measureTime } from "@utils/measureTime"; +import { sleep } from "@utils/sleep"; +import { Logger } from "@utils/logger"; import { Loggable } from "@utils/types"; export class App extends Loggable { @@ -25,13 +28,14 @@ export class App extends Loggable { private cleaningUp = false; private config: Config | null = null; - public constructor() { + public constructor(private readonly configFilePath: string, private readonly verbose: boolean) { super("App"); + Logger.verbose = verbose; + this.followerDataSource = new DataSource({ type: "sqlite", database: path.join(process.cwd(), "./data.sqlite"), entities: [User, UserLog], - dropSchema: true, synchronize: true, }); @@ -42,7 +46,7 @@ export class App extends Loggable { public async run() { this.logger.clear(); - this.config = await Config.create(); + this.config = await Config.create(this.configFilePath); if (!this.config) { process.exit(-1); return; @@ -93,12 +97,28 @@ export class App extends Loggable { const watcherNames = watchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", watchers.length)]); + const interval = this.config.watchInterval; while (true) { try { - const [, elapsedTime] = await throttle(this.onCycle.bind(this), this.config.watchInterval, true); - this.logger.debug(`last task finished in ${chalk.green("{}")}.`, [ - prettyMilliseconds(elapsedTime, { verbose: true }), - ]); + const [, elapsedTime] = await throttle( + async () => { + const { elapsedTime: time } = await measureTime(async () => this.onCycle()); + + this.logger.info(`last task finished in ${chalk.green("{}")}.`, [ + prettyMilliseconds(time, { verbose: true }), + ]); + + this.logger.info("waiting {green} for next cycle ...", [ + prettyMilliseconds(interval - time, { verbose: true }), + ]); + }, + this.config.watchInterval, + true, + ); + + if (elapsedTime < interval) { + await sleep(interval - elapsedTime); + } if (this.cleaningUp) { break; @@ -145,7 +165,7 @@ export class App extends Loggable { this.logger.info(`all {} {} saved`, [newLogs.length, pluralize("log", newLogs.length)]); this.logger.info("tracked {} new {}", [newFollowers.length, pluralize("follower", newFollowers.length)]); - this.logger.info("tracked {} un{}", [unfollowers.length, pluralize("follower", unfollowers.length)]); + this.logger.info("tracked {} {}", [unfollowers.length, pluralize("unfollower", unfollowers.length)]); const notifiers = this.config.notifiers; if (!notifiers.length) { diff --git a/src/index.ts b/src/index.ts index f4cbdd9..1cb1acb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,18 @@ +import { Command } from "commander"; import { App } from "@root/app"; +import { noop } from "@utils/noop"; +import { version } from "../package.json"; -new App().run().then(); +const program = new Command(); + +program + .name("cage") + .description("(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒") + .option("-c, --config ", "path to the configuration file", "./config.json") + .option("--verbose", "enable verbose level logging") + .version(version, "-v, --version") + .parse(process.argv); + +const { config, verbose } = program.opts<{ config: string; verbose: boolean }>(); + +new App(config, verbose).run().then().catch(noop); diff --git a/yarn.lock b/yarn.lock index 011bdee..408c6d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1518,6 +1518,14 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + app-root-path@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" @@ -1703,7 +1711,7 @@ bin-links@^3.0.3: rimraf "^3.0.0" write-file-atomic "^4.0.0" -binary-extensions@^2.2.0: +binary-extensions@^2.0.0, binary-extensions@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== @@ -1737,7 +1745,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1913,6 +1921,21 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -2058,7 +2081,7 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^9.4.0: +commander@^9.0.0, commander@^9.4.0, commander@^9.4.1: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== @@ -2806,7 +2829,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2884,7 +2907,7 @@ git-log-parser@^1.2.0: through2 "~2.0.0" traverse "~0.6.6" -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2933,7 +2956,7 @@ globals@^13.15.0: dependencies: type-fest "^0.20.2" -globby@^11.0.0, globby@^11.0.1, globby@^11.1.0: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -3190,6 +3213,13 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-cidr@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" @@ -3219,7 +3249,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4387,6 +4417,11 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mylas@^2.1.9: + version "2.1.13" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.13.tgz#1e23b37d58fdcc76e15d8a5ed23f9ae9fc0cbdf4" + integrity sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg== + mz@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -4533,7 +4568,7 @@ normalize-package-data@^4.0.0: semver "^7.3.5" validate-npm-package-license "^3.0.4" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -4994,7 +5029,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -5024,6 +5059,13 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +plimit-lit@^1.2.6: + version "1.5.0" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.5.0.tgz#f66df8a7041de1e965c4f1c0697ab486968a92a5" + integrity sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng== + dependencies: + queue-lit "^1.5.0" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -5188,6 +5230,11 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== +queue-lit@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.5.0.tgz#8197fdafda1edd615c8a0fc14c48353626e5160a" + integrity sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5294,6 +5341,13 @@ readdir-scoped-modules@^1.1.0: graceful-fs "^4.1.2" once "^1.3.0" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -5992,6 +6046,18 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tsc-alias@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.1.tgz#2cb548a451cc99ccd2dabcd583a0ebe22f3c7dd8" + integrity sha512-d8Oc3y/XI9OvpdbdjzOWNYskEvFxqtobitHcTszmQr9iVt/tHL7pwV0WHKuCQ4uXHcHh+oDMx+uTlvMQBlpYcw== + dependencies: + chokidar "^3.5.3" + commander "^9.0.0" + globby "^11.0.4" + mylas "^2.1.9" + normalize-path "^3.0.0" + plimit-lit "^1.2.6" + tsconfig-paths@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz#f8ef7d467f08ae3a695335bf1ece088c5538d2c1" From a0522622a56168f659586fa710a58497c5ae9621 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:41:15 +0900 Subject: [PATCH 034/140] test(core): adjust expectation value for accept error ranges --- src/utils/throttle.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/throttle.spec.ts b/src/utils/throttle.spec.ts index 7543810..dcaec04 100644 --- a/src/utils/throttle.spec.ts +++ b/src/utils/throttle.spec.ts @@ -20,7 +20,7 @@ describe("throttle() Function", function () { const [result, elapsedTime] = await throttle(target, 1000, true); expect(result).toBe(1); - expect(elapsedTime).toBeGreaterThanOrEqual(1000); + expect(elapsedTime).toBeGreaterThanOrEqual(990); }); it("should return the result of the target promise with the elapsed time", async function () { @@ -28,6 +28,6 @@ describe("throttle() Function", function () { const [result, elapsedTime] = await throttle(target, 1000, true); expect(result).toBe(1); - expect(elapsedTime).toBeGreaterThanOrEqual(1000); + expect(elapsedTime).toBeGreaterThanOrEqual(990); }); }); From 9a6883daae441b8a3f00c216d653fddea596405a Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:48:40 +0900 Subject: [PATCH 035/140] chore: update semantic-release configuration --- .releaserc.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/.releaserc.json b/.releaserc.json index ecda334..6d96e3f 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -3,7 +3,42 @@ "ci": true, "plugins": [ "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalCommits", + "parserOpts": { + "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] + }, + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Internal", + "hidden": true + }, + { + "type": "refactor", + "section": "Internal", + "hidden": false + }, + { + "type": "perf", + "section": "Internal", + "hidden": false + } + ] + } + } + ], "@semantic-release/npm", "@semantic-release/github", [ From d8733e4b71554738d5700a9bcadeb6eef7cd68a9 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 02:56:07 +0900 Subject: [PATCH 036/140] ci: make `main` branch run coverage properly --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b14d3d..577d8be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: env: FORCE_COLOR: 3 run: | - yarn test + yarn coverage --ci --verbose --testTimeout=10000 - name: Codecov uses: codecov/codecov-action@v3 From 6e5e94f1e1fa8cc96919ea6e46fab52405e48c07 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 03:47:01 +0900 Subject: [PATCH 037/140] chore: update package information --- package.json | 8 +++++++- yarn.lock | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ee1b083..9f5160c 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,16 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.4.3", + "version": "0.0.0", "license": "MIT", "publishConfig": { "access": "public" }, + "author": { + "name": "Sophia", + "email": "me@sophia-dev.io", + "url": "https://github.com/async3619" + }, "scripts": { "build": "tsc --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json", "dev": "node --watch -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", @@ -69,6 +74,7 @@ "commander": "^9.4.1", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", + "fs-extra": "^11.1.0", "lodash": "^4.17.21", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", diff --git a/yarn.lock b/yarn.lock index 408c6d7..31307aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2817,6 +2817,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" + integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" From 679a0eab9d734326395f88433342f997693b1554 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 03:47:22 +0900 Subject: [PATCH 038/140] chore: make pre-releases can be published on `dev` branch --- .releaserc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.releaserc.json b/.releaserc.json index 6d96e3f..32a0894 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,5 +1,5 @@ { - "branches": ["main"], + "branches": ["main", { "name": "dev", "prerelease": true }], "ci": true, "plugins": [ "@semantic-release/commit-analyzer", From b5a154f1e9ce7afa954917844867419ed36d1dd9 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 03:50:05 +0900 Subject: [PATCH 039/140] ci: remove redundant test ci pipeline --- .github/workflows/ci.yml | 1 + .github/workflows/test.yml | 71 -------------------------------------- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 577d8be..c486dc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - dev jobs: build-and-deploy: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index d851038..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Build and Deploy - -on: - push: - branches: - - dev - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.3.1 - with: - persist-credentials: false - - - name: Cache nextjs build - uses: actions/cache@v2 - with: - path: ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]sx?') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}- - - - name: Cache node_modules - id: node-cache - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: node_modules - key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-node-modules- - - - name: Install and Build - uses: actions/setup-node@v3 - with: - node-version: "18.x" - - - name: Install yarn - run: | - npm install -g yarn - - - name: Prepare package - run: | - yarn - - - name: Lint - run: | - yarn lint - - - name: Test - env: - FORCE_COLOR: 3 - run: | - yarn coverage --ci --verbose --testTimeout=10000 - - - name: Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - name: cage - - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK }} From bb1b5d9f27485a3351d894a76d2d0b60440b5f3b Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 03:52:20 +0900 Subject: [PATCH 040/140] chore: add required dependency --- package.json | 1 + yarn.lock | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/package.json b/package.json index 9f5160c..738dbd8 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", "commander": "^9.4.1", + "conventional-changelog-conventionalcommits": "^5.0.0", "cronstrue": "^2.20.0", "dayjs": "^1.11.6", "fs-extra": "^11.1.0", diff --git a/yarn.lock b/yarn.lock index 31307aa..38c4129 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2117,6 +2117,15 @@ conventional-changelog-angular@^5.0.0: compare-func "^2.0.0" q "^1.5.1" +conventional-changelog-conventionalcommits@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz#41bdce54eb65a848a4a3ffdca93e92fa22b64a86" + integrity sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw== + dependencies: + compare-func "^2.0.0" + lodash "^4.17.15" + q "^1.5.1" + conventional-changelog-writer@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz#e0757072f045fe03d91da6343c843029e702f359" From 951fafa500263fbfe53dd02f252ffc4936ec2f8e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 5 Dec 2022 18:53:59 +0000 Subject: [PATCH 041/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 1.0.0-dev.1 (2022-12-05) ### Features * **config:** implement basic file-based configuration feature ([17286e7](https://github.com/async3619/cage/commit/17286e7aae575662c797c1555bcc1f2ba4e050d3)) * **core:** implement (un)follower detection through database ([8265e55](https://github.com/async3619/cage/commit/8265e554e0abafb965f4ee561539c17f20d149ff)) * **core:** implement basic cli feature ([75ee061](https://github.com/async3619/cage/commit/75ee06187d6d0546bce28ab07a1768e7e0a77add)) * **discord:** implement discord webhook notifier ([fd6c4ec](https://github.com/async3619/cage/commit/fd6c4ec341dd68f7f446865f8c7ec485e78988a4)) * **logger:** implement basic logger ([91c40de](https://github.com/async3619/cage/commit/91c40de6a22b240e6720680d4297d1a27c802bd8)) * **twitter:** implement basic twitter auth logic ([f4d84a7](https://github.com/async3619/cage/commit/f4d84a70800943451cdef2695057da5f1abf7ee0)) * **twitter:** implement collecting follower data feature ([db9ab2b](https://github.com/async3619/cage/commit/db9ab2b5a72969438a9d1bfd6efbdcb368810744)) ### Bug Fixes * **core:** rewrite new follower and unfollower check routine ([47d9be4](https://github.com/async3619/cage/commit/47d9be415b7a0d9f0e9eccabf9a138de216f4fbf)) ### Internal * **core:** add basic watching feature through cron ([07f98fb](https://github.com/async3619/cage/commit/07f98fb9e6d8db32564afd5125cc6b9aaf16efe1)) * **core:** make config file path always to be absolute when given path is relative ([9c5c41e](https://github.com/async3619/cage/commit/9c5c41eb73994bc83852ec41e20e0b25fc253d90)) * **core:** make fetcher can retry when request throws an error ([91e1ec4](https://github.com/async3619/cage/commit/91e1ec4c3c3e06391518300b1b001a34c79db72c)) * **core:** make logger can format string with arguments ([2347423](https://github.com/async3619/cage/commit/23474230a099104415e30253c21cd0d15948360d)) * **core:** make logger can style string with new format tokens ([65f6df9](https://github.com/async3619/cage/commit/65f6df96f279604ecab1a5e90409911281260184)) * **core:** make logger not to log verbose level logs when verbose mode is disabled ([5ad4bf3](https://github.com/async3619/cage/commit/5ad4bf38850cfcd2931c91ed72b099bd61bccc21)) * **core:** remove exit handler and cleaning up routine ([a140d65](https://github.com/async3619/cage/commit/a140d6591d93e5e45a9c6a488ceb99b48d5f0879)) * **core:** rename log level from `silly` to `verbose` ([1f3d08a](https://github.com/async3619/cage/commit/1f3d08a4fac36e3fe3e0db96131e717f16dc3dce)) * **core:** use throttle instead of waiting for main task loop ([40a6db5](https://github.com/async3619/cage/commit/40a6db5e5e93847e920b60b7f9175cad18de83e8)) * reimplement how watcher state saved or loaded ([cd763c9](https://github.com/async3619/cage/commit/cd763c9211a07bfd0c977bfff2be81f31c097fc7)) * update type `Fn` for better usage ([bb5954b](https://github.com/async3619/cage/commit/bb5954b1d06527ab15a046cef013c33a7707cefd)) * **watcher:** make watcher hash data can be retrieved by its own implementation ([866b9b6](https://github.com/async3619/cage/commit/866b9b6e6502fed1a8e2b26bb83ad97224fe9325)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 738dbd8..ad3af4b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "0.0.0", + "version": "1.0.0-dev.1", "license": "MIT", "publishConfig": { "access": "public" From c04902b63e1ba9d7cd017f873a43e1da22e738a9 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 12:10:40 +0900 Subject: [PATCH 042/140] fix(discord): fix a bug that discord notifier notifies with wrong number of data --- src/notifiers/discord.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index c931476..0268488 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -76,7 +76,7 @@ export class DiscordNotifier extends BaseNotifier { if (followerLogs.length > 0) { const field = this.generateEmbedField( followerLogs, - Logger.format("🎉 {} new {}", logs.length, pluralize("follower", logs.length)), + Logger.format("🎉 {} new {}", followerLogs.length, pluralize("follower", logs.length)), ); newFields.push(field); @@ -86,7 +86,7 @@ export class DiscordNotifier extends BaseNotifier { if (unfollowerLogs.length > 0) { const field = this.generateEmbedField( unfollowerLogs, - Logger.format("❌ {} {}", logs.length, pluralize("unfollower", logs.length)), + Logger.format("❌ {} {}", followerLogs.length, pluralize("unfollower", logs.length)), ); newFields.push(field); From b75ab6aa75f5ec355b24c273387e06d36d29c37e Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 19:24:37 +0900 Subject: [PATCH 043/140] refactor(twitter): make twitter watcher to use official api client --- config.schema.json | 64 +++++++++++ package.json | 2 + src/app.ts | 11 +- src/utils/config.ts | 39 +++++-- src/utils/types.ts | 9 +- src/watchers/base.ts | 12 ++- src/watchers/index.ts | 30 +++++- src/watchers/twitter/auth.ts | 188 --------------------------------- src/watchers/twitter/helper.ts | 185 +++++++------------------------- src/watchers/twitter/index.ts | 50 +++------ src/watchers/twitter/types.ts | 94 ++++------------- yarn.lock | 10 ++ 12 files changed, 223 insertions(+), 471 deletions(-) delete mode 100644 src/watchers/twitter/auth.ts diff --git a/config.schema.json b/config.schema.json index 2583f42..a139889 100644 --- a/config.schema.json +++ b/config.schema.json @@ -17,9 +17,73 @@ "type": { "type": "string", "const": "twitter" + }, + "auth": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "api-key" + }, + "apiKey": { + "type": "string" + }, + "apiSecret": { + "type": "string" + } + }, + "required": [ + "type", + "apiKey", + "apiSecret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bearer-token" + }, + "bearerToken": { + "type": "string" + } + }, + "required": [ + "type", + "bearerToken" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "basic" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "type", + "username", + "password" + ], + "additionalProperties": false + } + ] } }, "required": [ + "auth", "type" ], "additionalProperties": false diff --git a/package.json b/package.json index ad3af4b..39241a8 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "typescript": "^4.9.3" }, "dependencies": { + "@twitter-api-v2/plugin-rate-limit": "^1.1.0", "ajv": "^8.11.2", "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", @@ -86,6 +87,7 @@ "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", "strip-ansi": "^6.0.1", + "twitter-api-v2": "^1.12.9", "typeorm": "^0.3.10" } } diff --git a/src/app.ts b/src/app.ts index be7b23d..b56dfa3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,8 +10,6 @@ import { UserLogRepository } from "@repositories/user-log"; import { User, UserData } from "@repositories/models/user"; import { UserLog, UserLogType } from "@root/repositories/models/user-log"; -import { AVAILABLE_WATCHERS } from "@watchers"; - import { Config } from "@utils/config"; import { throttle } from "@utils/throttle"; import { mapBy } from "@utils/mapBy"; @@ -66,14 +64,13 @@ export class App extends Loggable { work: () => this.followerDataSource.initialize(), }); - for (const watcher of watchers) { - const options = this.config.watcherOptions[watcher.getName()]; + for (const [, watcher] of watchers) { await watcher.loadState(); await this.logger.work({ level: "info", message: `initialize \`${chalk.green(watcher.getName())}\` watcher`, - work: () => watcher.initialize(options), + work: () => watcher.initialize(), }); await watcher.saveState(); @@ -94,7 +91,7 @@ export class App extends Loggable { }); } - const watcherNames = watchers.map(p => `\`${chalk.green(p.getName())}\``).join(", "); + const watcherNames = watchers.map(([, p]) => `\`${chalk.green(p.getName())}\``).join(", "); this.logger.info("start to watch through {} {}.", [watcherNames, pluralize("watcher", watchers.length)]); const interval = this.config.watchInterval; @@ -140,7 +137,7 @@ export class App extends Loggable { const startedDate = new Date(); const allUserData: UserData[] = []; - for (const watcher of AVAILABLE_WATCHERS) { + for (const [, watcher] of this.config.watchers) { const userData = await watcher.doWatch(); allUserData.push(...userData); diff --git a/src/utils/config.ts b/src/utils/config.ts index 462a2e8..0ae6a0a 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -5,7 +5,7 @@ import chalk from "chalk"; import Ajv from "ajv"; import betterAjvErrors from "better-ajv-errors"; -import { AVAILABLE_WATCHERS } from "@watchers"; +import { createWatcher, WatcherMap, WatcherPair, WatcherTypes } from "@watchers"; import { AVAILABLE_NOTIFIERS } from "@notifiers"; import { ConfigData } from "@utils/config.type"; @@ -62,17 +62,35 @@ export class Config { throw new Error(`config file is invalid.\n${output}`); } - return new Config(data); + const watcherTypes = Object.keys(data.watchers) as WatcherTypes[]; + const watchers: WatcherPair[] = []; + const watcherMap: WatcherMap = {} as WatcherMap; + for (const type of watcherTypes) { + const configs = data.watchers[type]; + if (!configs) { + throw new Error(`watcher \`${type}\` is not configured.`); + } + + const watcher = createWatcher(configs); + watchers.push([type, watcher]); + watcherMap[type] = watcher as any; + } + + return new Config(data, watcherTypes, watchers, watcherMap); }, }); } - public get watchers() { - const names = Object.keys(this.rawData.watchers); - return AVAILABLE_WATCHERS.filter(w => names.includes(w.getName().toLowerCase())); + get watcherTypes(): ReadonlyArray { + return [...this._watcherTypes]; } - public get watcherOptions() { - return _.cloneDeep(this.rawData.watchers); + + get watchers(): ReadonlyArray { + return [...this._watchers]; + } + + get watcherMap(): WatcherMap { + return { ...this._watcherMap }; } public get notifiers() { @@ -91,5 +109,10 @@ export class Config { return this.rawData.watchInterval; } - private constructor(private readonly rawData: ConfigData) {} + private constructor( + private readonly rawData: ConfigData, + private readonly _watcherTypes: ReadonlyArray, + private readonly _watchers: ReadonlyArray, + private readonly _watcherMap: WatcherMap, + ) {} } diff --git a/src/utils/types.ts b/src/utils/types.ts index b556a50..2219f33 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,9 +1,12 @@ import { Logger } from "@utils/logger"; +export type CombineFn any> = (...args: Parameters) => ReturnType; export type TypeMap = { [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; }; +export type Values = T[keyof T]; + export type Fn = TArgs extends unknown[] ? (...arg: TArgs) => TReturn : TArgs extends void @@ -14,17 +17,17 @@ export type Work = Fn | T>; export type Nullable = T | null | undefined; export interface Serializable { - serialize(): Record; + serialize(): Record; } export interface Hydratable { hydrate(data: Record): void; } -export abstract class Loggable { +export abstract class Loggable { protected readonly logger: Logger; - protected constructor(protected readonly name: string) { + protected constructor(public readonly name: TName) { this.logger = new Logger(name); } diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 430ed22..6cb1660 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -4,16 +4,22 @@ import * as fs from "fs-extra"; import { UserData } from "@repositories/models/user"; +import { WatcherTypes } from "@watchers"; + import { Loggable } from "@utils/types"; -export abstract class BaseWatcher extends Loggable { +export interface BaseWatcherOptions { + type: TType; +} + +export abstract class BaseWatcher extends Loggable { private readonly stateFileDirectory = path.join(process.cwd(), "./dump/"); - protected constructor(name: string) { + protected constructor(name: TType) { super(name); } - public abstract initialize(options: Record): Promise; + public abstract initialize(): Promise; public abstract doWatch(): Promise; diff --git a/src/watchers/index.ts b/src/watchers/index.ts index d6d2f49..72256d1 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,9 +1,33 @@ -import { BaseWatcher } from "@watchers/base"; -import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; +import { TwitterWatcher } from "@watchers/twitter"; +import { TwitterWatcherOptions } from "@watchers/twitter/types"; import { TypeMap } from "@utils/types"; +import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; + +export type WatcherClasses = TwitterWatcher; +export type WatcherTypes = Lowercase; export type WatcherOptions = TwitterWatcherOptions; export type WatcherOptionMap = TypeMap; -export const AVAILABLE_WATCHERS: ReadonlyArray = [new TwitterWatcher()]; +export type WatcherMap = { + [TKey in WatcherClasses["name"] as Lowercase]: TKey extends WatcherClasses["name"] + ? Extract + : never; +}; +export type WatcherFactoryMap = { + [TKey in WatcherClasses["name"] as Lowercase]: TKey extends WatcherClasses["name"] + ? (options: WatcherOptionMap[Lowercase]) => Extract + : never; +}; +export type WatcherPair = [WatcherTypes, BaseWatcher]; + +const AVAILABLE_WATCHERS: Readonly = { + twitter: options => new TwitterWatcher(options), +}; + +export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { + const { type } = options; + + return AVAILABLE_WATCHERS[type](options); +}; diff --git a/src/watchers/twitter/auth.ts b/src/watchers/twitter/auth.ts deleted file mode 100644 index eaba573..0000000 --- a/src/watchers/twitter/auth.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Fetcher } from "@utils/fetcher"; -import { AsyncFn, Fn } from "@utils/types"; - -export class TwitterAuth { - private readonly tasks: Array> = []; - private flowToken = ""; - - public constructor( - private readonly fetcher: Fetcher, - private readonly getHeaders: Fn>, - private readonly guestTokenHandler: Fn, - ) {} - - public doInstrumentation() { - this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>({ - url: "https://twitter.com/i/api/1.1/onboarding/task.json", - method: "POST", - headers: this.getHeaders(), - data: { - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginJsInstrumentationSubtask", - js_instrumentation: { - response: JSON.stringify({ - rf: { - eca553b518cf2bf0dac5687d57cc1857738793960864216fb28429c367b1074c: 96, - bf7ee80b5b9e0822c133ba94f7c8fa22efadc740ccfe7e83a8907335d7668441: 128, - aea7b2935d0508b33d370215bb57788afe530c7250f289c4e2f347579a363c98: 154, - aa1900ce941819d2ce7b3985d94c4b05781f7b9bd3b2389cd1b28f1b8156ebc8: -115, - }, - s: "CKq_55Z6_WsJSB5d7xiFWMeLaWUckgaJw1bADMTt3WNVnsZGXlu4q9EDeem5Azx-pPMUR8M2_VRpnn0411iltwDyynQKh_ONDxmO52V2TrialbGMyy1Zh-J1Xj_xeXDRI-DPpTG-xe7qTtzMZj8i60xSwZv15BV0S496tJ00pSNlfYBEk-YK85q2kO3cGQg7ZPoHDjxIJPRcintmYd9ioVMFTMYcmrnhGkpk6Qf7GqwbL1XEkAAtuqB33AD_xH-V3P3Dl2z4RQQsL-xL6-LwuEtJW4F6l91N4Fqx0jCN9AaP42CM-tMNu0lJvZw32QTGhE8VzFg1Db26CTym4q9qlAAAAYTGoKew", - }), - link: "next_link", - }, - }, - ], - }, - }); - - this.flowToken = data.flow_token; - }); - - return this; - } - public setUserId(userId: string) { - this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>({ - url: "https://twitter.com/i/api/1.1/onboarding/task.json", - method: "POST", - headers: this.getHeaders(), - data: { - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginEnterUserIdentifierSSO", - settings_list: { - setting_responses: [ - { key: "user_identifier", response_data: { text_data: { result: userId } } }, - ], - link: "next_link", - }, - }, - ], - }, - }); - - this.flowToken = data.flow_token; - }); - - return this; - } - public setPassword(password: string) { - this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>({ - url: "https://twitter.com/i/api/1.1/onboarding/task.json", - method: "POST", - headers: this.getHeaders(), - data: { - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "LoginEnterPassword", - enter_password: { password, link: "next_link" }, - }, - ], - }, - }); - - this.flowToken = data.flow_token; - }); - - return this; - } - public doDuplicationCheck() { - this.tasks.push(async () => { - const data = await this.fetcher.fetchJson<{ flow_token: string }>({ - url: "https://twitter.com/i/api/1.1/onboarding/task.json", - method: "POST", - headers: this.getHeaders(), - data: { - flow_token: this.flowToken, - subtask_inputs: [ - { - subtask_id: "AccountDuplicationCheck", - check_logged_in_account: { link: "AccountDuplicationCheck_false" }, - }, - ], - }, - }); - - this.flowToken = data.flow_token; - }); - - return this; - } - - public async action() { - const { guest_token } = await this.fetcher.fetchJson<{ guest_token: string }>({ - url: "https://api.twitter.com/1.1/guest/activate.json", - method: "POST", - headers: this.getHeaders(), - }); - - this.guestTokenHandler(guest_token); - - const { flow_token } = await this.fetcher.fetchJson<{ flow_token: string }>({ - url: "https://twitter.com/i/api/1.1/onboarding/task.json?flow_name=login", - method: "POST", - headers: this.getHeaders(), - data: { - input_flow_data: { - flow_context: { debug_overrides: {}, start_location: { location: "manual_link" } }, - }, - subtask_versions: { - action_list: 2, - alert_dialog: 1, - app_download_cta: 1, - check_logged_in_account: 1, - choice_selection: 3, - contacts_live_sync_permission_prompt: 0, - cta: 7, - email_verification: 2, - end_flow: 1, - enter_date: 1, - enter_email: 2, - enter_password: 5, - enter_phone: 2, - enter_recaptcha: 1, - enter_text: 5, - enter_username: 2, - generic_urt: 3, - in_app_notification: 1, - interest_picker: 3, - js_instrumentation: 1, - menu_dialog: 1, - notifications_permission_prompt: 2, - open_account: 2, - open_home_timeline: 1, - open_link: 1, - phone_verification: 4, - privacy_options: 1, - security_key: 3, - select_avatar: 4, - select_banner: 2, - settings_list: 7, - show_code: 1, - sign_up: 2, - sign_up_review: 4, - tweet_selection_urt: 1, - update_users: 1, - upload_media: 1, - user_recommendations_list: 4, - user_recommendations_urt: 1, - wait_spinner: 3, - web_modal: 1, - }, - }, - }); - - this.flowToken = flow_token; - - for (const task of this.tasks) { - await task(); - } - } -} diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index 8976998..3a4d18b 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -1,170 +1,61 @@ -import * as fs from "fs-extra"; +import { ITwitterApiClientPlugin, TwitterApi, TwitterApiReadOnly } from "twitter-api-v2"; +import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; -import { TwitterAuth } from "@watchers/twitter/auth"; -import { TWITTER_AUTH_TOKEN } from "@watchers/twitter/constants"; -import { FollowerAPIResponse, FollowerUser } from "@watchers/twitter/types"; -import { findBottomCursorFromFollower, findUserDataFromFollower } from "@watchers/twitter/utils"; +import { UserData } from "@repositories/models/user"; -import { generateRandomString } from "@utils/generateRandomString"; -import { Fetcher } from "@utils/fetcher"; -import { Hydratable, Serializable } from "@utils/types"; +import { TwitterApiKeyAuth, TwitterBasicAuth, TwitterBearerTokenAuth } from "@watchers/twitter/types"; -type FollowerCursor = string; -type GetFollowersResult = [FollowerUser[], (() => Promise) | null]; +export class TwitterHelper { + private static createClient(configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth) { + const plugins: ITwitterApiClientPlugin[] = [new TwitterApiRateLimitPlugin()]; -export class TwitterHelper implements Serializable, Hydratable { - private readonly fetcher: Fetcher = new Fetcher(); - private readonly defaultCsrf = generateRandomString(32, "0123456789abcdefghijklmnopqrstuvwxyz"); + switch (configs.type) { + case "api-key": + return new TwitterApi({ appKey: configs.apiKey, appSecret: configs.apiSecret }, { plugins }).readOnly; - private authToken: string = TWITTER_AUTH_TOKEN; - private guest: string | null = null; - private currentUserId: string | null = null; + case "basic": + return new TwitterApi({ username: configs.username, password: configs.password }, { plugins }).readOnly; - public get isLogged() { - return Boolean(this.currentUserId); - } - - private getHeaders() { - const headers = { - "Content-type": "application/json", - "x-twitter-active-user": "yes", - "x-twitter-client-language": "ko", - "x-csrf-token": this.fetcher.getCookies().ct0 || this.defaultCsrf, - Referer: "https://twitter.com/i/flow/login", - }; - - if (this.authToken) { - headers["authorization"] = `Bearer ${this.authToken}`; - } - - if (this.guest) { - headers["x-guest-token"] = this.guest; - } + case "bearer-token": + return new TwitterApi(configs.bearerToken, { plugins }).readOnly; - return headers; - } - private getUserId() { - if (!this.currentUserId) { - const { twid } = this.fetcher.getCookies(); - if (!twid) { - throw new Error("twid cookie not found"); - } - - const [, userId] = twid.replace(/"/g, "").split("="); - this.currentUserId = userId; + default: + throw new Error("Invalid auth type"); } - - return this.currentUserId; } - public async doAuth(userId: string, password: string) { - await new TwitterAuth(this.fetcher, this.getHeaders.bind(this), token => (this.guest = token)) - .doInstrumentation() - .setUserId(userId) - .setPassword(password) - .doDuplicationCheck() - .action(); + private twitterClient: TwitterApiReadOnly; - const { twid } = this.fetcher.getCookies(); - if (!twid) { - throw new Error("twid cookie not found"); - } - - const [, currentUserId] = twid.replace(/"/g, "").split("="); - this.currentUserId = currentUserId; + public constructor(private readonly configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth) { + this.twitterClient = TwitterHelper.createClient(this.configs); } - public async getFollowers(cursor: FollowerCursor | null = null): Promise { - const variables: Record = { - userId: this.getUserId(), - count: 100, - includePromotedContent: false, - withSuperFollowsUserFields: true, - withDownvotePerspective: false, - withReactionsMetadata: false, - withReactionsPerspective: false, - withSuperFollowsTweetFields: true, - }; - - if (cursor) { - variables.cursor = cursor; - } - - const data = await this.fetcher.fetchJson({ - url: "https://twitter.com/i/api/graphql/_gXC5CopoM8fIgawvyGpIg/Followers", - headers: this.getHeaders(), - method: "GET", - retryCount: 3, - retryDelay: 500, - data: { - variables: JSON.stringify(variables), - features: JSON.stringify({ - responsive_web_twitter_blue_verified_badge_is_enabled: true, - verified_phone_label_enabled: false, - responsive_web_graphql_timeline_navigation_enabled: true, - unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: true, - tweetypie_unmention_optimization_enabled: true, - responsive_web_uc_gql_enabled: true, - vibe_api_enabled: true, - responsive_web_edit_tweet_api_enabled: true, - graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, - standardized_nudges_misinfo: true, - tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false, - interactive_text_enabled: true, - responsive_web_text_conversations_enabled: false, - responsive_web_enhance_cards_enabled: true, - }), - }, + public async getFollowers(): Promise[]> { + const followers = await this.twitterClient.v2.followers("1569485307049033729", { + max_results: 1000, + "user.fields": ["id", "name", "username", "profile_image_url"], }); - if (process.env.NODE_ENV === "development") { - await fs.writeJson("followers.json", data, { - spaces: 4, - }); - } - - const bottomCursor = findBottomCursorFromFollower(data); - if (!bottomCursor) { - throw new Error("Failed to find bottom cursor"); - } - - const users = findUserDataFromFollower(data); - if (!users) { - throw new Error("Failed to find users"); - } - - return [users, users.length >= 100 ? this.getFollowers.bind(this, bottomCursor) : null]; + return followers.data.map(user => ({ + uniqueId: user.id, + displayName: user.name, + userId: user.username, + })); } - public async getAllFollowers(): Promise { - const result = await this.getFollowers(); - const followers: FollowerUser[] = [...result[0]]; - let next = result[1]; - while (true) { - if (!next) { - break; - } - - const [nextFollowers, nextFunction] = await next(); - followers.push(...nextFollowers); - next = nextFunction; + public async initialize() { + if (this.configs.type === "api-key") { + await this.twitterClient.appLogin(); } - - return followers; } - public serialize(): Record { - return { - fetcher: this.fetcher.serialize(), - authToken: this.authToken, - guest: this.guest, - currentUserId: this.currentUserId, - }; + public getHashData() { + return this.configs; + } + public hydrate(configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth): void { + this.twitterClient = TwitterHelper.createClient(configs); } - public hydrate(data: Record): void { - this.fetcher.hydrate(data.fetcher as Record); - this.authToken = data.authToken as string; - this.guest = data.guest as string | null; - this.currentUserId = data.currentUserId as string | null; + public serialize(): TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth { + return this.configs; } } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 85153d4..1d4f4b7 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -2,42 +2,23 @@ import pluralize from "pluralize"; import { BaseWatcher } from "@watchers/base"; -import { TwitterHelper } from "@watchers/twitter/helper"; - -import { UserData } from "@root/repositories/models/user"; +import { TwitterWatcherOptions } from "@watchers/twitter/types"; -export interface TwitterWatcherOptions { - type: "twitter"; -} +import { UserData } from "@repositories/models/user"; +import { TwitterHelper } from "@watchers/twitter/helper"; -export class TwitterWatcher extends BaseWatcher { - private readonly helper: TwitterHelper = new TwitterHelper(); - private readonly userId: string; - private readonly password: string; +export class TwitterWatcher extends BaseWatcher<"Twitter"> { + private readonly helper: TwitterHelper; - public constructor() { + public constructor({ auth }: TwitterWatcherOptions) { super("Twitter"); - - this.userId = process.env.CAGE_TWITTER_USER_ID || ""; - this.password = process.env.CAGE_TWITTER_PASSWORD || ""; + this.helper = new TwitterHelper(auth); } - - public async initialize(_: TwitterWatcherOptions): Promise { - if (!this.userId) { - throw new Error("Environment variable 'CAGE_TWITTER_USER_ID' should be provided."); - } - - if (!this.password) { - throw new Error("Environment variable 'CAGE_TWITTER_PASSWORD' should be provided."); - } - - if (!this.helper.isLogged) { - await this.login(this.userId, this.password); - } + public async initialize(): Promise { + await this.helper.initialize(); } - public async doWatch() { - const allFollowers = await this.helper.getAllFollowers(); + const allFollowers = await this.helper.getFollowers(); this.logger.verbose("Successfully crawled {} {}", [ allFollowers.length, @@ -45,17 +26,14 @@ export class TwitterWatcher extends BaseWatcher { ]); return allFollowers.map(user => ({ - uniqueId: user.rest_id, - userId: user.legacy.screen_name, - displayName: user.legacy.name, + ...user, from: this.getName().toLowerCase(), })); } protected getHashData(): any { - return [this.userId, this.password]; + return this.helper.getHashData(); } - protected serialize(): Record { return { helper: this.helper.serialize(), @@ -64,8 +42,4 @@ export class TwitterWatcher extends BaseWatcher { protected hydrate(data: Record): void { this.helper.hydrate(data.helper); } - - private async login(userId: string, password: string) { - await this.helper.doAuth(userId, password); - } } diff --git a/src/watchers/twitter/types.ts b/src/watchers/twitter/types.ts index 11aa2c1..b2b9e80 100644 --- a/src/watchers/twitter/types.ts +++ b/src/watchers/twitter/types.ts @@ -1,78 +1,24 @@ -export interface FollowerAPIResponse { - data: { - user: { - result: { - __typename: string; - timeline: { - timeline: { - instructions: Array; - }; - }; - }; - }; - }; +import { BaseWatcherOptions } from "@watchers/base"; + +export interface TwitterApiKeyAuth { + type: "api-key"; + apiKey: string; + apiSecret: string; } -export interface AddEntryInstruction { - type: "TimelineAddEntries"; - entries: InstructionEntry[]; + +export interface TwitterBearerTokenAuth { + type: "bearer-token"; + bearerToken: string; } -export interface OtherInstruction { - type: "TimelineClearCache" | "TimelineTerminateTimeline"; + +export interface TwitterBasicAuth { + type: "basic"; + username: string; + password: string; } -export interface InstructionEntry { - entryId: string; - sortIndex: string; - content: TimelineItemContent | TimelineCursorContent; -} -export interface FollowerUser { - rest_id: string; - legacy: { - blocked_by: boolean; - blocking: boolean; - can_dm: boolean; - can_media_tag: boolean; - created_at: string; - default_profile: boolean; - default_profile_image: boolean; - description: string; - fast_followers_count: number; - favourites_count: number; - follow_request_sent: boolean; - followed_by: boolean; - followers_count: number; - following: boolean; - friends_count: number; - has_custom_timelines: boolean; - is_translator: boolean; - listed_count: number; - location: string; - media_count: number; - muting: boolean; - name: string; - normal_followers_count: number; - notifications: boolean; - possibly_sensitive: boolean; - profile_banner_url: string; - profile_image_url_https: string; - profile_interstitial_type: string; - protected: boolean; - screen_name: string; - statuses_count: number; - translator_type: string; - verified: boolean; - want_retweets: boolean; - }; -} -export interface TimelineItemContent { - entryType: "TimelineTimelineItem"; - itemContent: { - user_results: { - result: FollowerUser; - }; - }; -} -export interface TimelineCursorContent { - entryType: "TimelineTimelineCursor"; - cursorType: "Bottom" | "Top"; - value: string; + +export type TwitterAuth = TwitterApiKeyAuth | TwitterBearerTokenAuth | TwitterBasicAuth; + +export interface TwitterWatcherOptions extends BaseWatcherOptions<"twitter"> { + auth: TwitterAuth; } diff --git a/yarn.lock b/yarn.lock index 38c4129..40bb75d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,6 +1105,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== +"@twitter-api-v2/plugin-rate-limit@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@twitter-api-v2/plugin-rate-limit/-/plugin-rate-limit-1.1.0.tgz#a80bbc86f4629575c13ab2f688dafe8b7eb05058" + integrity sha512-5nUk0w0s1yzzMe+NLN5BlKzwgl9OHkF1Makb5Xje1ntCO1H1fGq0qli80WRvcrpcwKZcSckRkXd4HEksh2LZ6g== + "@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" @@ -6102,6 +6107,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +twitter-api-v2@^1.12.9: + version "1.12.9" + resolved "https://registry.yarnpkg.com/twitter-api-v2/-/twitter-api-v2-1.12.9.tgz#447fc4efb693bbb7983a6dcec2de5da7e0f27648" + integrity sha512-nmKwcUiXgoEfcOtAH/hYojy0oSxbouWvvNsHVphiHSW9x9aus2Jdg9/NRB2Gzyq5G6xl+EkjUlEWwBQjp1FOfg== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From 09b987a871496d0085014ba62d844f321284cffd Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 19:26:59 +0900 Subject: [PATCH 044/140] refactor(twitter): remove redundant legacy codes --- src/watchers/twitter/constants.ts | 2 -- src/watchers/twitter/utils.ts | 55 ------------------------------- 2 files changed, 57 deletions(-) delete mode 100644 src/watchers/twitter/constants.ts delete mode 100644 src/watchers/twitter/utils.ts diff --git a/src/watchers/twitter/constants.ts b/src/watchers/twitter/constants.ts deleted file mode 100644 index 3fac7c3..0000000 --- a/src/watchers/twitter/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const TWITTER_AUTH_TOKEN = - "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"; diff --git a/src/watchers/twitter/utils.ts b/src/watchers/twitter/utils.ts deleted file mode 100644 index ba4bf43..0000000 --- a/src/watchers/twitter/utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - AddEntryInstruction, - FollowerAPIResponse, - TimelineCursorContent, - TimelineItemContent, -} from "@watchers/twitter/types"; - -export function findAddEntryInstructionFromFollower(follower: FollowerAPIResponse): AddEntryInstruction | null { - const { instructions } = follower.data.user.result.timeline.timeline; - const addEntryInstruction = instructions.find(instruction => instruction.type === "TimelineAddEntries") as - | AddEntryInstruction - | undefined; - - if (!addEntryInstruction) { - return null; - } - - return addEntryInstruction; -} - -export function findBottomCursorFromFollower(response: FollowerAPIResponse) { - const addEntryInstruction = findAddEntryInstructionFromFollower(response); - if (!addEntryInstruction) { - return null; - } - - const entries = addEntryInstruction.entries; - const cursors = entries - .map(entry => entry.content) - .filter((content): content is TimelineCursorContent => { - return content.entryType === "TimelineTimelineCursor"; - }); - - const bottomCursor = cursors.find(cursor => cursor.cursorType === "Bottom"); - if (!bottomCursor) { - return null; - } - - return bottomCursor.value; -} - -export function findUserDataFromFollower(response: FollowerAPIResponse) { - const addEntryInstruction = findAddEntryInstructionFromFollower(response); - if (!addEntryInstruction) { - return null; - } - - const entries = addEntryInstruction.entries; - return entries - .map(entry => entry.content) - .filter((content): content is TimelineItemContent => { - return content.entryType === "TimelineTimelineItem"; - }) - .map(content => content.itemContent.user_results.result); -} From c6ee7c1b3c05db9beb5c31e499c7104fa59a45c6 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 19:34:15 +0900 Subject: [PATCH 045/140] fix(discord): fix a bug that notification having wrong unfollower count --- src/notifiers/discord.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index 0268488..f4b0681 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -86,7 +86,7 @@ export class DiscordNotifier extends BaseNotifier { if (unfollowerLogs.length > 0) { const field = this.generateEmbedField( unfollowerLogs, - Logger.format("❌ {} {}", followerLogs.length, pluralize("unfollower", logs.length)), + Logger.format("❌ {} {}", unfollowerLogs.length, pluralize("unfollower", logs.length)), ); newFields.push(field); From 9c94b85a4568514ba5e4d296d64145ab7e9b7851 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:07:11 +0900 Subject: [PATCH 046/140] feat(core): implement user detection for users who renaming `displayName` or `userId` --- src/app.ts | 8 ++++++++ src/repositories/models/user-log.ts | 2 ++ src/repositories/user-log.ts | 1 + src/utils/getDiff.ts | 20 ++++++++++++++++++++ src/utils/getFollowerDiff.ts | 23 ----------------------- src/watchers/twitter/index.ts | 1 + 6 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 src/utils/getDiff.ts delete mode 100644 src/utils/getFollowerDiff.ts diff --git a/src/app.ts b/src/app.ts index b56dfa3..9d07198 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,6 +17,7 @@ import { measureTime } from "@utils/measureTime"; import { sleep } from "@utils/sleep"; import { Logger } from "@utils/logger"; import { Loggable } from "@utils/types"; +import { getDiff } from "@utils/getDiff"; export class App extends Loggable { private readonly followerDataSource: DataSource; @@ -147,9 +148,14 @@ export class App extends Loggable { const followingMap = await this.userLogRepository.getFollowStatusMap(); const oldUsers = await this.userRepository.find(); + const newUsers = await this.userRepository.createFromData(allUserData, startedDate); const newUserMap = mapBy(newUsers, "id"); + // find user renaming their displayName or userId + const displayNameRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "displayName"); + const userIdRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "userId"); + // find users who are not followed yet. const newFollowers = newUsers.filter(p => !followingMap[p.id]); const unfollowers = oldUsers.filter(p => !newUserMap[p.id] && followingMap[p.id]); @@ -157,6 +163,8 @@ export class App extends Loggable { const newLogs = await this.userLogRepository.batchWriteLogs([ [newFollowers, UserLogType.Follow], [unfollowers, UserLogType.Unfollow], + [displayNameRenamedUsers, UserLogType.RenameDisplayName], + [userIdRenamedUsers, UserLogType.RenameUserId], ]); this.logger.info(`all {} {} saved`, [newLogs.length, pluralize("log", newLogs.length)]); diff --git a/src/repositories/models/user-log.ts b/src/repositories/models/user-log.ts index b627be7..7629b58 100644 --- a/src/repositories/models/user-log.ts +++ b/src/repositories/models/user-log.ts @@ -14,6 +14,8 @@ import { User } from "@repositories/models/user"; export enum UserLogType { Follow = "follow", Unfollow = "unfollow", + RenameDisplayName = "rename-display-name", + RenameUserId = "rename-user-id", } @Entity({ name: "follower-logs" }) diff --git a/src/repositories/user-log.ts b/src/repositories/user-log.ts index 5faf317..bc57544 100644 --- a/src/repositories/user-log.ts +++ b/src/repositories/user-log.ts @@ -49,6 +49,7 @@ export class UserLogRepository extends BaseRepository { .select("type") .addSelect("userId") .addSelect("MAX(createdAt)", "createdAt") + .where("type IN (:...types)", { types: [UserLogType.Follow, UserLogType.Unfollow] }) .groupBy("userId") .getRawMany<{ type: UserLog["type"]; userId: string; createdAt: string }>(); diff --git a/src/utils/getDiff.ts b/src/utils/getDiff.ts new file mode 100644 index 0000000..043bec2 --- /dev/null +++ b/src/utils/getDiff.ts @@ -0,0 +1,20 @@ +import { mapBy } from "@utils/mapBy"; +import { Values } from "@utils/types"; + +type KeysOnlyString> = Values<{ + [TKey in keyof TData]: TData[TKey] extends string ? TKey : never; +}>; + +export function getDiff>( + newData: TData[], + oldData: TData[], + uniqueKey: KeysOnlyString, + key: keyof TData, +): TData[] { + const oldDataMap = mapBy(oldData, uniqueKey); + + return newData.filter(item => { + const oldUser = oldDataMap[item[uniqueKey] as string]; + return oldUser && oldUser[key] !== item[key]; + }); +} diff --git a/src/utils/getFollowerDiff.ts b/src/utils/getFollowerDiff.ts deleted file mode 100644 index c13322c..0000000 --- a/src/utils/getFollowerDiff.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as _ from "lodash"; - -import { User } from "@root/repositories/models/user"; - -export function getFollowerDiff(oldFollowers: User[], newFollowers: User[]) { - const oldFollowerMap = _.chain(oldFollowers).keyBy("id").value(); - const newFollowerMap = _.chain(newFollowers).keyBy("id").value(); - - // get old & new follower ids in different variable - const oldFollowerIds = oldFollowers.map(follower => follower.id); - const newFollowerIds = newFollowers.map(follower => follower.id); - - // get new follower ids - const newFollowerIdsOnly = newFollowerIds.filter(id => !oldFollowerIds.includes(id)); - - // get removed follower ids - const removedFollowerIdsOnly = oldFollowerIds.filter(id => !newFollowerIds.includes(id)); - - return { - added: newFollowerIdsOnly.map(id => newFollowerMap[id]), - removed: removedFollowerIdsOnly.map(id => oldFollowerMap[id]), - }; -} diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 1d4f4b7..46241be 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -19,6 +19,7 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { } public async doWatch() { const allFollowers = await this.helper.getFollowers(); + console.log(allFollowers); this.logger.verbose("Successfully crawled {} {}", [ allFollowers.length, From 676d063069e7cd7c40cb5d24fdf8306b184451b0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:07:28 +0900 Subject: [PATCH 047/140] chore: remove redundant loggings --- src/watchers/twitter/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 46241be..1d4f4b7 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -19,7 +19,6 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { } public async doWatch() { const allFollowers = await this.helper.getFollowers(); - console.log(allFollowers); this.logger.verbose("Successfully crawled {} {}", [ allFollowers.length, From f8e2bb6bb47d2bcacd8f434fd9838919da850b94 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:25:41 +0900 Subject: [PATCH 048/140] feat(discord): notifier now notifies renaming user logs well --- src/notifiers/discord.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index f4b0681..7a3d67a 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -1,5 +1,6 @@ import pluralize from "pluralize"; +import { User } from "@repositories/models/user"; import { UserLog, UserLogType } from "@repositories/models/user-log"; import { BaseNotifier } from "@notifiers/base"; @@ -92,6 +93,18 @@ export class DiscordNotifier extends BaseNotifier { newFields.push(field); } + const renameLogs = logs.filter( + l => l.type === UserLogType.RenameUserId || l.type === UserLogType.RenameDisplayName, + ); + if (renameLogs.length > 0) { + const field = this.generateEmbedField( + renameLogs, + Logger.format("✏️ {} {}", renameLogs.length, pluralize("rename", logs.length)), + ); + + newFields.push(field); + } + data.embeds[0].fields.push(...newFields); await this.fetcher.fetch({ @@ -105,9 +118,19 @@ export class DiscordNotifier extends BaseNotifier { return { name: title, value: logs - .map(l => l.user) + .map<[User, UserLog]>(l => [l.user, l]) .slice(0, 10) - .map(user => { + .map(([user, log]) => { + if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { + return Logger.format( + "{} (@{}) → {}{}", + log.oldDisplayName, + log.oldUserId, + log.type === UserLogType.RenameDisplayName ? "" : "@", + log.type === UserLogType.RenameUserId ? user.userId : user.displayName, + ); + } + return Logger.format( "[{} (@{})](https://twitter.com/{})", user.displayName, From fbb574cb782043cd294d818b2ffca0b1cb10cc7b Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:26:29 +0900 Subject: [PATCH 049/140] refactor(core): now it save old `displayName` and `userId` whenever renames --- src/app.ts | 41 ++++++++++++++++++++++++----- src/repositories/models/user-log.ts | 7 +++++ src/repositories/user-log.ts | 6 ++++- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/app.ts b/src/app.ts index 9d07198..511f52c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -148,6 +148,7 @@ export class App extends Loggable { const followingMap = await this.userLogRepository.getFollowStatusMap(); const oldUsers = await this.userRepository.find(); + const oldUsersMap = mapBy(oldUsers, "id"); const newUsers = await this.userRepository.createFromData(allUserData, startedDate); const newUserMap = mapBy(newUsers, "id"); @@ -160,12 +161,40 @@ export class App extends Loggable { const newFollowers = newUsers.filter(p => !followingMap[p.id]); const unfollowers = oldUsers.filter(p => !newUserMap[p.id] && followingMap[p.id]); - const newLogs = await this.userLogRepository.batchWriteLogs([ - [newFollowers, UserLogType.Follow], - [unfollowers, UserLogType.Unfollow], - [displayNameRenamedUsers, UserLogType.RenameDisplayName], - [userIdRenamedUsers, UserLogType.RenameUserId], - ]); + const renameLogs: UserLog[] = []; + if (displayNameRenamedUsers.length || userIdRenamedUsers.length) { + const displayName = displayNameRenamedUsers.map(p => { + const oldUser = oldUsersMap[p.id]; + const userLog = this.userLogRepository.create(); + userLog.type = UserLogType.RenameDisplayName; + userLog.user = p; + userLog.oldDisplayName = oldUser?.displayName; + userLog.oldUserId = oldUser?.userId; + + return userLog; + }); + + const userId = userIdRenamedUsers.map(p => { + const oldUser = oldUsersMap[p.id]; + const userLog = this.userLogRepository.create(); + userLog.type = UserLogType.RenameUserId; + userLog.user = p; + userLog.oldDisplayName = oldUser?.displayName; + userLog.oldUserId = oldUser?.userId; + + return userLog; + }); + + renameLogs.push(...displayName, ...userId); + } + + const newLogs = await this.userLogRepository.batchWriteLogs( + [ + [newFollowers, UserLogType.Follow], + [unfollowers, UserLogType.Unfollow], + ], + renameLogs, + ); this.logger.info(`all {} {} saved`, [newLogs.length, pluralize("log", newLogs.length)]); diff --git a/src/repositories/models/user-log.ts b/src/repositories/models/user-log.ts index 7629b58..b92dfe2 100644 --- a/src/repositories/models/user-log.ts +++ b/src/repositories/models/user-log.ts @@ -10,6 +10,7 @@ import { } from "typeorm"; import { User } from "@repositories/models/user"; +import { Nullable } from "@utils/types"; export enum UserLogType { Follow = "follow", @@ -26,6 +27,12 @@ export class UserLog extends BaseEntity { @Column({ type: "varchar", length: 255 }) public type!: UserLogType; + @Column({ type: "varchar", length: 255, nullable: true }) + public oldUserId!: Nullable; + + @Column({ type: "varchar", length: 255, nullable: true }) + public oldDisplayName!: Nullable; + @CreateDateColumn() public createdAt!: Date; diff --git a/src/repositories/user-log.ts b/src/repositories/user-log.ts index bc57544..f500f82 100644 --- a/src/repositories/user-log.ts +++ b/src/repositories/user-log.ts @@ -30,7 +30,7 @@ export class UserLogRepository extends BaseRepository { return this.saveItems(userLogs); } - public async batchWriteLogs(items: [User[], UserLog["type"]][]) { + public async batchWriteLogs(items: Array<[User[], UserLog["type"]]>, rawLogs?: UserLog[]) { const userLogs = items.flatMap(([users, type]) => users.map(user => { const userLog = this.create(); @@ -41,6 +41,10 @@ export class UserLogRepository extends BaseRepository { }), ); + if (rawLogs) { + userLogs.push(...rawLogs); + } + return this.saveItems(userLogs); } From 6bca5a2a6fd62c78bf673076c8a7984f6eebd185 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:28:13 +0900 Subject: [PATCH 050/140] refactor(core): rename user log repository table name --- src/repositories/models/user-log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repositories/models/user-log.ts b/src/repositories/models/user-log.ts index b92dfe2..bd73aef 100644 --- a/src/repositories/models/user-log.ts +++ b/src/repositories/models/user-log.ts @@ -19,7 +19,7 @@ export enum UserLogType { RenameUserId = "rename-user-id", } -@Entity({ name: "follower-logs" }) +@Entity({ name: "user-logs" }) export class UserLog extends BaseEntity { @PrimaryGeneratedColumn() public id!: number; From 18728afef044cb662a4bb1d7b9e3b0edfe324e78 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:42:06 +0900 Subject: [PATCH 051/140] =?UTF-8?q?docs:=20add=20npm=20package=20version?= =?UTF-8?q?=20into=20README.md=20=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e5b4fad..91f7d1b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@
+ + npm + MIT License From 104d5f77a1e1a64568f300ea58dc2bb2c43acbf5 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 20:42:54 +0900 Subject: [PATCH 052/140] docs: add link to npm badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91f7d1b..bcdbdc5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@
- + npm From 61a2b91b0f463d7d17f263cf209309e1f6e24ccf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 6 Dec 2022 11:56:52 +0000 Subject: [PATCH 053/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.2](https://github.com/async3619/cage/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2022-12-06) ### Features * **core:** implement user detection for users who renaming `displayName` or `userId` ([9c94b85](https://github.com/async3619/cage/commit/9c94b85a4568514ba5e4d296d64145ab7e9b7851)) * **discord:** notifier now notifies renaming user logs well ([f8e2bb6](https://github.com/async3619/cage/commit/f8e2bb6bb47d2bcacd8f434fd9838919da850b94)) ### Bug Fixes * **discord:** fix a bug that discord notifier notifies with wrong number of data ([c04902b](https://github.com/async3619/cage/commit/c04902b63e1ba9d7cd017f873a43e1da22e738a9)) * **discord:** fix a bug that notification having wrong unfollower count ([c6ee7c1](https://github.com/async3619/cage/commit/c6ee7c1b3c05db9beb5c31e499c7104fa59a45c6)) ### Internal * **core:** now it save old `displayName` and `userId` whenever renames ([fbb574c](https://github.com/async3619/cage/commit/fbb574cb782043cd294d818b2ffca0b1cb10cc7b)) * **core:** rename user log repository table name ([6bca5a2](https://github.com/async3619/cage/commit/6bca5a2a6fd62c78bf673076c8a7984f6eebd185)) * **twitter:** make twitter watcher to use official api client ([b75ab6a](https://github.com/async3619/cage/commit/b75ab6aa75f5ec355b24c273387e06d36d29c37e)) * **twitter:** remove redundant legacy codes ([09b987a](https://github.com/async3619/cage/commit/09b987a871496d0085014ba62d844f321284cffd)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39241a8..2484212 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.1", + "version": "1.0.0-dev.2", "license": "MIT", "publishConfig": { "access": "public" From 202b12dee664319fc5d30fb5e33c360a70b52ee4 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 21:18:38 +0900 Subject: [PATCH 054/140] refactor(cli): add short-hand option flag for `--verbose` --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1cb1acb..8dd76a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,8 +9,8 @@ program .name("cage") .description("(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒") .option("-c, --config ", "path to the configuration file", "./config.json") - .option("--verbose", "enable verbose level logging") - .version(version, "-v, --version") + .option("-v, --verbose", "enable verbose level logging") + .version(version) .parse(process.argv); const { config, verbose } = program.opts<{ config: string; verbose: boolean }>(); From f3dbc0fe29aa1bd01f2f7a3ba7aae0eed9719023 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 21:18:56 +0900 Subject: [PATCH 055/140] docs: add help text for usage purpose --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bcdbdc5..44240c7 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,16 @@ Cage is a cli application for detecting unfollowers on any social services. $ npm install -g cage-cli $ cage --help + +Usage: cage [options] + +(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒 + +Options: + -c, --config path to the configuration file (default: "./config.json") + -v, --verbose enable verbose level logging + -V, --version output the version number + -h, --help display help for command ``` ## Supported Services From 1f9b966d3de6fed85272f238556c1e36ca5dda64 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 21:23:46 +0900 Subject: [PATCH 056/140] docs: add some description for notifiers and watchers --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44240c7..01c4630 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,14 @@ Options: -h, --help display help for command ``` -## Supported Services +## Watchers and Notifiers ### Watchers +`Watchers` are independent feature that has the ability to watch to check users who follow your account per service. + +Here is supported watchers (TBD for ❌ services): + | Service | Support? | | --------- | :-----------------: | | Twitter | ⚠️ (Partially, WIP) | @@ -65,6 +69,10 @@ Options: ### Notifiers +When we detect unfollowers, new followers, or any other events, Cage will notify you via `Notifiers`. + +Here is supported notifiers (TBD for ❌ services): + | Service | Support? | | --------------- | :------: | | Discord WebHook | ✅ | @@ -73,7 +81,7 @@ Options: this application reads configuration file from `./cage.config.json` by default. -You can use json schema file `config.schema.json` on this repository. here is example configuration file content: +you can use json schema file `config.schema.json` on this repository. here is example configuration file content: ```json { From 4f39959d8376002ce2a886f1920363ab23b6e083 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 21:32:02 +0900 Subject: [PATCH 057/140] docs: add some notifiers on supporting table --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01c4630..cd99f3b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Options: `Watchers` are independent feature that has the ability to watch to check users who follow your account per service. -Here is supported watchers (TBD for ❌ services): +#### Supported Watchers | Service | Support? | | --------- | :-----------------: | @@ -71,11 +71,15 @@ Here is supported watchers (TBD for ❌ services): When we detect unfollowers, new followers, or any other events, Cage will notify you via `Notifiers`. -Here is supported notifiers (TBD for ❌ services): +#### Supported Notifiers | Service | Support? | | --------------- | :------: | | Discord WebHook | ✅ | +| Slack WebHook | ❌ | +| Telegram Bot | ❌ | +| Email | ❌ | +| SMS | ❌ | ## Configuration From 061e8a922147866a22ee60bbbc36dbc96bcdeff2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 6 Dec 2022 23:20:23 +0900 Subject: [PATCH 058/140] fix(core): update default configuration to fit to the newest config schema --- src/utils/config.const.ts | 14 ++++++++++++++ src/utils/config.ts | 19 ++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 src/utils/config.const.ts diff --git a/src/utils/config.const.ts b/src/utils/config.const.ts new file mode 100644 index 0000000..ed851a5 --- /dev/null +++ b/src/utils/config.const.ts @@ -0,0 +1,14 @@ +import Ajv from "ajv"; + +import { ConfigData } from "@utils/config.type"; + +import schema from "@root/../config.schema.json"; + +const ajv = new Ajv({ allErrors: true }); +export const validate = ajv.compile(schema); + +export const DEFAULT_CONFIG: ConfigData = { + watchInterval: 60000, + watchers: {}, + notifiers: {}, +}; diff --git a/src/utils/config.ts b/src/utils/config.ts index 0ae6a0a..cb52f30 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -2,21 +2,17 @@ import _ from "lodash"; import * as path from "path"; import * as fs from "fs-extra"; import chalk from "chalk"; -import Ajv from "ajv"; import betterAjvErrors from "better-ajv-errors"; import { createWatcher, WatcherMap, WatcherPair, WatcherTypes } from "@watchers"; import { AVAILABLE_NOTIFIERS } from "@notifiers"; +import { DEFAULT_CONFIG, validate } from "@utils/config.const"; import { ConfigData } from "@utils/config.type"; import { Logger } from "@utils/logger"; import schema from "@root/../config.schema.json"; -const ajv = new Ajv({ allErrors: true }); - -const validate = ajv.compile(schema); - export class Config { private static readonly logger = new Logger("Config"); private static readonly DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./config.json"); @@ -33,16 +29,9 @@ export class Config { `config file ${pathToken} does not exist. we will make a new default config file for you.`, ); - await fs.writeJSON( - filePath, - { - targetProviders: ["twitter"], - watchInterval: 60000, - }, - { - spaces: 4, - }, - ); + await fs.writeJSON(filePath, DEFAULT_CONFIG, { + spaces: 4, + }); Config.logger.warn(`config file ${pathToken} has been created successfully.`); } From bb8a6613e8a6febc0adbb4539dac39eba10e28e7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 09:59:26 +0900 Subject: [PATCH 059/140] refactor(core): remove console clearing on startup --- src/app.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 511f52c..95fe971 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,8 +43,6 @@ export class App extends Loggable { } public async run() { - this.logger.clear(); - this.config = await Config.create(this.configFilePath); if (!this.config) { process.exit(-1); From aaade8d2cdc8ce54e6df149b44a68151db5f5864 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 09:59:38 +0900 Subject: [PATCH 060/140] build: add docker image deployment --- .dockerignore | 6 ++ .github/workflows/docker-deploy.yml | 96 +++++++++++++++++++++++++++++ Dockerfile | 55 +++++++++++++++++ config.schema.json | 2 +- 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-deploy.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..db0de9a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +/node_modules +/.idea +/.git +/bin +/scripts +/.github diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml new file mode 100644 index 0000000..a252c0b --- /dev/null +++ b/.github/workflows/docker-deploy.yml @@ -0,0 +1,96 @@ +name: Docker Image Deploy + +on: + push: + branches: + - main + - dev + tags: + - "*" + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.1 + with: + persist-credentials: false + + - name: Cache nextjs build + uses: actions/cache@v2 + with: + path: ${{ github.workspace }}/.next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]sx?') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}- + + - name: Cache node_modules + id: node-cache + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: node_modules + key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node-modules- + + - name: Install and Build + uses: actions/setup-node@v3 + with: + node-version: "18.x" + + - name: Install yarn + run: | + npm install -g yarn + + - name: Prepare package + run: | + yarn + + - name: Lint + run: | + yarn lint + + - name: Test + env: + FORCE_COLOR: 3 + run: | + yarn coverage --ci --verbose --testTimeout=10000 + + - name: Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: cage + + - name: Build + run: | + yarn build + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build, tag, and push image to Docker Hub + env: + IMAGE_TAG: ${{ github.sha }} + DOCKER_BUILDKIT: 1 + GITHUB_OWNER: ${{ github.repository_owner }} + GITHUB_REPO: ${{ github.event.repository.name }} + run: | + docker build -t $GITHUB_OWNER/$GITHUB_REPO:$IMAGE_TAG . + docker build -t $GITHUB_OWNER/$GITHUB_REPO:latest . + docker push $GITHUB_OWNER/$GITHUB_REPO:$IMAGE_TAG + docker push $GITHUB_OWNER/$GITHUB_REPO:latest + + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..288708d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +FROM node:18-alpine as builder + +RUN apk add --update --no-cache curl git openssh openssl + +USER node +WORKDIR /home/node + +COPY --chown=node:node package*.json ./ +COPY --chown=node:node yarn.lock ./ +RUN yarn install --frozen-lockfile + +COPY --chown=node:node . . + +ARG NODE_ENV=production +ARG APP_ENV=production + +ENV NODE_ENV ${NODE_ENV} + +RUN ["yarn", "build"] + +FROM node:18-alpine as prod-deps + +USER node +WORKDIR /home/node + +# copy from build image +COPY --from=builder /home/node/yarn.lock ./yarn.lock +COPY --from=builder /home/node/package.json ./package.json +RUN yarn install --frozen-lockfile --prod + +ARG NODE_ENV=production +ARG APP_ENV=production + +ENV NODE_ENV ${NODE_ENV} + +CMD [ "node", "dist/src/index" ] + +FROM node:18-alpine as production + +USER node +WORKDIR /home/node + +# copy from build image +COPY --from=prod-deps /home/node/node_modules ./node_modules +COPY --from=builder /home/node/dist ./dist +COPY --from=builder /home/node/yarn.lock ./yarn.lock +COPY --from=builder /home/node/package.json ./package.json +RUN yarn install --frozen-lockfile --prod + +ARG NODE_ENV=production +ARG APP_ENV=production + +ENV NODE_ENV ${NODE_ENV} + +CMD [ "node", "dist/src/index" ] diff --git a/config.schema.json b/config.schema.json index a139889..f4ebb7c 100644 --- a/config.schema.json +++ b/config.schema.json @@ -122,4 +122,4 @@ "additionalProperties": false } } -} \ No newline at end of file +} From 082bac662fca49e586b5f40d947d32e05521b580 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 10:00:24 +0900 Subject: [PATCH 061/140] ci: make docker deploy only works on tag pushing --- .github/workflows/docker-deploy.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index a252c0b..6bfe74f 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -2,9 +2,6 @@ name: Docker Image Deploy on: push: - branches: - - main - - dev tags: - "*" From af5189834fce677b204c661ed6f65c4c5e95728c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 7 Dec 2022 01:32:47 +0000 Subject: [PATCH 062/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.3](https://github.com/async3619/cage/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2022-12-07) ### Bug Fixes * **core:** update default configuration to fit to the newest config schema ([061e8a9](https://github.com/async3619/cage/commit/061e8a922147866a22ee60bbbc36dbc96bcdeff2)) ### Internal * **cli:** add short-hand option flag for `--verbose` ([202b12d](https://github.com/async3619/cage/commit/202b12dee664319fc5d30fb5e33c360a70b52ee4)) * **core:** remove console clearing on startup ([bb8a661](https://github.com/async3619/cage/commit/bb8a6613e8a6febc0adbb4539dac39eba10e28e7)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2484212..1291044 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.2", + "version": "1.0.0-dev.3", "license": "MIT", "publishConfig": { "access": "public" From ffaffd2ebc1e8f69a10ab300ceb6c929efacb2d7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 10:39:02 +0900 Subject: [PATCH 063/140] ci: make docker image tagged with proper tag name --- .github/workflows/docker-deploy.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 6bfe74f..78a1adb 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -75,16 +75,18 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set Environment Variable for Tagging + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Build, tag, and push image to Docker Hub env: - IMAGE_TAG: ${{ github.sha }} DOCKER_BUILDKIT: 1 GITHUB_OWNER: ${{ github.repository_owner }} GITHUB_REPO: ${{ github.event.repository.name }} run: | - docker build -t $GITHUB_OWNER/$GITHUB_REPO:$IMAGE_TAG . + docker build -t $GITHUB_OWNER/$GITHUB_REPO:$RELEASE_VERSION . docker build -t $GITHUB_OWNER/$GITHUB_REPO:latest . - docker push $GITHUB_OWNER/$GITHUB_REPO:$IMAGE_TAG + docker push $GITHUB_OWNER/$GITHUB_REPO:$RELEASE_VERSION docker push $GITHUB_OWNER/$GITHUB_REPO:latest - uses: sarisia/actions-status-discord@v1 From 5f00088cf98074309126cede12565534acc40fa2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 10:49:22 +0900 Subject: [PATCH 064/140] docs: add docker related usage --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index cd99f3b..6cd533b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,24 @@ Options: -h, --help display help for command ``` +or you can just deploy with `docker` if you want: + +```bash +$ docker run -d --name cage async3619/cage -v /path/to/config.json:/home/node/config.json +``` + +of course, you can also use `docker-compose`: + +```yaml +version: "3.9" + +services: + cage: + image: async3619/cage + volumes: + - /path/to/config.json:/home/node/config.json +``` + ## Watchers and Notifiers ### Watchers From 95d58add2ba79de1ab0ed87d76341a92b940abef Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 10:54:18 +0900 Subject: [PATCH 065/140] docs: make npm badge show correct versioning of a tag --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cd533b..a233151 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@
- npm + npm (tag) MIT License From a07e78c91600732e593d305c2871a0b15457147a Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 11:03:25 +0900 Subject: [PATCH 066/140] fix(discord): now discord notifier notifies with correct timestamp --- src/notifiers/discord.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index 7a3d67a..2707d5e 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -7,6 +7,7 @@ import { BaseNotifier } from "@notifiers/base"; import { Fetcher } from "@utils/fetcher"; import { Logger } from "@utils/logger"; +import dayjs from "dayjs"; export interface DiscordNotifierOptions { type: "discord"; @@ -66,7 +67,8 @@ export class DiscordNotifier extends BaseNotifier { author: { name: "Cage Report", }, - timestamp: "2022-12-29T15:00:00.000Z", + // use dayjs to generate timestamp with Z format + timestamp: dayjs().format(), }, ], attachments: [], From 51028122f28672aa4116e9edf4254edaad9a0883 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 11:06:36 +0900 Subject: [PATCH 067/140] =?UTF-8?q?docs:=20change=20project=20mascot=20emo?= =?UTF-8?q?ji=20=F0=9F=96=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a233151..1d4a18f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@


- 🐦 + 🦜
Cage From bd4095c82f58397e9cc428322f063b96f7f57d9c Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 12:03:42 +0900 Subject: [PATCH 068/140] fix(core): fix a bug that logger could not applying styles properly --- src/utils/logger.ts | 47 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 86aabf4..fecaeb3 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -75,32 +75,33 @@ export class Logger implements Record { return content; } - const formattedArgs = args - .map(arg => { - if (typeof arg === "object") { - return JSON.stringify(arg); - } - - return `${arg}`; - }) - .map(arg => { - const result = /(\{(.*?)\})/.exec(content); - if (result && result[2]) { - const tokens = result[2].split(":"); - let content = arg; - for (const token of tokens) { - if (CHALK_FORMAT_NAMES.includes(token as any)) { - content = chalk[token](content); - } + const replacements = args.map(arg => { + if (typeof arg === "object") { + return JSON.stringify(arg); + } + + return `${arg}`; + }); + + const matches = content.matchAll(/(\{(.*?)\})/g); + if (!matches) { + return content; + } + + for (const [, token, style] of matches) { + let item = replacements.shift() || "{}"; + if (style) { + style.split(",").forEach(style => { + if (CHALK_FORMAT_NAMES.includes(style as any)) { + item = chalk[style](item); } + }); + } - return content; - } else { - return arg; - } - }); + content = content.replace(token, item); + } - return formattedArgs.reduce((content, arg) => content.replace(/\{.*?\}/, arg), content); + return content; } private static readonly buffer: string[] = []; From b988a984b3f0a9ee177526ba0112585294c40ad0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 12:20:13 +0900 Subject: [PATCH 069/140] feat(core): implement update notifying feature --- package.json | 5 +- src/index.ts | 54 +++++-- yarn.lock | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 464 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 1291044..2b2f864 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@types/prompts": "^2.0.14", "@types/replace-ext": "^2.0.0", "@types/set-cookie-parser": "^2.4.2", + "@types/update-notifier": "^6.0.1", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/parser": "^5.38.0", @@ -71,6 +72,7 @@ "ajv": "^8.11.2", "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", + "boxen": "^5.1.2", "chalk": "^4.1.2", "commander": "^9.4.1", "conventional-changelog-conventionalcommits": "^5.0.0", @@ -88,6 +90,7 @@ "sqlite3": "^5.1.2", "strip-ansi": "^6.0.1", "twitter-api-v2": "^1.12.9", - "typeorm": "^0.3.10" + "typeorm": "^0.3.10", + "update-notifier": "^5.1.0" } } diff --git a/src/index.ts b/src/index.ts index 8dd76a2..64e8900 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,48 @@ +import updateNotifier from "update-notifier"; import { Command } from "commander"; +import boxen from "boxen"; + import { App } from "@root/app"; -import { noop } from "@utils/noop"; -import { version } from "../package.json"; +import { Logger } from "@utils/logger"; + +import packageJson from "../package.json"; + +(async () => { + const program = new Command(); + + program + .name("cage") + .description("(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒") + .option("-c, --config ", "path to the configuration file", "./config.json") + .option("-v, --verbose", "enable verbose level logging") + .version(packageJson.version) + .parse(process.argv); -const program = new Command(); + const { config, verbose } = program.opts<{ config: string; verbose: boolean }>(); + const { latest, current } = await updateNotifier({ + pkg: packageJson, + distTag: packageJson.version.includes("dev") ? "dev" : "latest", + }).fetchInfo(); -program - .name("cage") - .description("(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒") - .option("-c, --config ", "path to the configuration file", "./config.json") - .option("-v, --verbose", "enable verbose level logging") - .version(version) - .parse(process.argv); + if (latest !== current) { + const contents = Logger.format( + "{white}", + [ + Logger.format(`Update available {yellow} → {green}`, current, latest), + Logger.format(`Run {cyan} to update`, `npm i -g ${packageJson.name}@${latest}`), + ].join("\n"), + ); -const { config, verbose } = program.opts<{ config: string; verbose: boolean }>(); + console.log( + Logger.format( + "{cyan}", + boxen(contents, { + padding: 1, + margin: 1, + }), + ), + ); + } -new App(config, verbose).run().then().catch(noop); + await new App(config, verbose).run(); +})(); diff --git a/yarn.lock b/yarn.lock index 40bb75d..7f2c850 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1056,6 +1056,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.44.tgz#0a0aa3bf4a155a678418527342a3ee84bd8caa5c" integrity sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1075,6 +1080,13 @@ resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -1143,6 +1155,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/configstore@*": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/configstore/-/configstore-6.0.0.tgz#a49bb7d86a6cd1b0bb486fb336eec6966530aa13" + integrity sha512-GUvNiia85zTDDIx0iPrtF3pI8dwrQkfuokEqxqPDE55qxH0U5SZz4awVZjiJLWN2ZZRkXCUqgsMUbygXY+kytA== + "@types/fs-extra@^9.0.13": version "9.0.13" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" @@ -1291,6 +1308,14 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/update-notifier@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-6.0.1.tgz#b1891f56be7ef4f124058bb6ca6934a7da524c78" + integrity sha512-J6x9qtPDKgJGLdTjiswhhiL5QJoZv7eQb44t90mWb4Cf3tnuDRMvg8je9PoxDJZiGog0bfoFVcVHBq4RcItRZg== + dependencies: + "@types/configstore" "*" + boxen "^7.0.0" + "@types/uuid@^8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" @@ -1467,6 +1492,13 @@ ajv@^8.11.2: require-from-string "^2.0.2" uri-js "^4.2.2" +ansi-align@^3.0.0, ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1486,6 +1518,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1505,6 +1542,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -1735,6 +1777,34 @@ bottleneck@^2.18.1: resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== +boxen@^5.0.0, boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + +boxen@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32" + integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg== + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.0" + chalk "^5.0.1" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1862,6 +1932,19 @@ cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3: tar "^6.1.11" unique-filename "^2.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1886,6 +1969,11 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camelcase@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.0.tgz#fd112621b212126741f998d614cbc2a8623fd174" + integrity sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ== + caniuse-lite@^1.0.30001400: version "1.0.30001418" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6" @@ -1921,6 +2009,11 @@ chalk@^5.0.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.1.0.tgz#c4b4a62bfb6df0eeeb5dbc52e6a9ecaff14b9976" integrity sha512-56zD4khRTBoIyzUYAFgDDaPhUMN/fC/rySe6aZGqbj/VWiU2eI3l6ZLOtYGFZAV5v02mwPjtpzlrOveJiz5eZQ== +chalk@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.1.2.tgz#d957f370038b75ac572471e83be4c5ca9f8e8c45" + integrity sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -1951,6 +2044,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + ci-info@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.4.0.tgz#b28484fd436cbc267900364f096c9dc185efb251" @@ -1973,6 +2071,16 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + cli-columns@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" @@ -2020,6 +2128,13 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -2109,6 +2224,18 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -2270,6 +2397,13 @@ decamelize@^1.1.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -2297,6 +2431,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + del@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" @@ -2388,7 +2527,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dot-prop@^5.1.0: +dot-prop@^5.1.0, dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== @@ -2407,6 +2546,16 @@ duplexer2@~0.1.0: dependencies: readable-stream "^2.0.2" +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + electron-to-chromium@^1.4.251: version "1.4.276" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520" @@ -2422,6 +2571,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -2467,6 +2621,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2906,6 +3065,13 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -2967,6 +3133,13 @@ glob@^8.0.1, glob@^8.0.3: minimatch "^5.0.1" once "^1.3.0" +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -2991,6 +3164,23 @@ globby@^11.0.0, globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -3033,6 +3223,11 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3074,7 +3269,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@^4.1.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -3154,6 +3349,11 @@ import-from@^4.0.0: resolved "https://registry.yarnpkg.com/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2" integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ== +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -3190,6 +3390,11 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + ini@^3.0.0, ini@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ini/-/ini-3.0.1.tgz#c76ec81007875bc44d544ff7a11a55d12294102d" @@ -3243,6 +3448,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-cidr@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" @@ -3279,11 +3491,24 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -3326,6 +3551,16 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -3837,6 +4072,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -3906,6 +4146,13 @@ just-diff@^5.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -3916,6 +4163,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + leven@^3.1.0, "leven@^3.1.0 < 4": version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -4124,6 +4378,16 @@ lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -4283,6 +4547,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -4596,6 +4865,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + normalize-url@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -4835,6 +5109,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -4929,6 +5208,16 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + pacote@^13.0.3, pacote@^13.6.1, pacote@^13.6.2: version "13.6.2" resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" @@ -5107,6 +5396,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -5215,6 +5509,13 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + puppeteer-core@19.3.0: version "19.3.0" resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.3.0.tgz#deba854f3dd3f74a04db200274827a67e200f39e" @@ -5403,6 +5704,13 @@ registry-auth-token@^4.0.0: dependencies: rc "1.2.8" +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5444,6 +5752,13 @@ resolve@^1.10.0, resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -5563,7 +5878,7 @@ semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.3.2, semver@^ dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -5598,7 +5913,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5771,7 +6086,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5780,6 +6095,15 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5801,6 +6125,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -5996,6 +6327,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -6159,6 +6495,18 @@ type-fest@^1.0.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type-fest@^2.13.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typeorm@^0.3.10: version "0.3.10" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.10.tgz#aa2857fd4b078c912ca693b7eee01b6535704458" @@ -6258,6 +6606,26 @@ update-browserslist-db@^1.0.9: escalade "^3.1.1" picocolors "^1.0.0" +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -6270,6 +6638,13 @@ url-join@^4.0.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6355,6 +6730,20 @@ wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -6374,11 +6763,30 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" @@ -6392,6 +6800,11 @@ ws@8.10.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xml2js@^0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" From aaa3ad3940eff039e4a5fd460b79848a32671bd8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 7 Dec 2022 03:22:21 +0000 Subject: [PATCH 070/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.4](https://github.com/async3619/cage/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2022-12-07) ### Features * **core:** implement update notifying feature ([b988a98](https://github.com/async3619/cage/commit/b988a984b3f0a9ee177526ba0112585294c40ad0)) ### Bug Fixes * **core:** fix a bug that logger could not applying styles properly ([bd4095c](https://github.com/async3619/cage/commit/bd4095c82f58397e9cc428322f063b96f7f57d9c)) * **discord:** now discord notifier notifies with correct timestamp ([a07e78c](https://github.com/async3619/cage/commit/a07e78c91600732e593d305c2871a0b15457147a)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b2f864..257f9ad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.3", + "version": "1.0.0-dev.4", "license": "MIT", "publishConfig": { "access": "public" From 7c6b0dea52d6d7bbe5d3230cf20b0817c27add77 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 20:57:49 +0900 Subject: [PATCH 071/140] feat(github): implement github watcher using GraphQL API --- .gitignore | 4 +- .graphqlconfig | 12 + codegen.ts | 14 + config.schema.json | 19 +- package.json | 11 +- schemas/github.graphqls | 55134 +++++++++++++++++++++++++++++++ src/utils/types.ts | 2 +- src/watchers/github/index.ts | 117 + src/watchers/github/queries.ts | 37 + src/watchers/github/types.ts | 5 + src/watchers/index.ts | 8 +- yarn.lock | 2075 +- 12 files changed, 57387 insertions(+), 51 deletions(-) create mode 100644 .graphqlconfig create mode 100644 codegen.ts create mode 100644 schemas/github.graphqls create mode 100644 src/watchers/github/index.ts create mode 100644 src/watchers/github/queries.ts create mode 100644 src/watchers/github/types.ts diff --git a/.gitignore b/.gitignore index 5fd4ee6..9140da6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ /followers.json /dump /data.sqlite -/config.json \ No newline at end of file +/config.json +/src/queries.ts +/src/queries.data.ts diff --git a/.graphqlconfig b/.graphqlconfig new file mode 100644 index 0000000..8025a3c --- /dev/null +++ b/.graphqlconfig @@ -0,0 +1,12 @@ +{ + "name": "Untitled GraphQL Schema", + "schemaPath": "./schemas/github.graphqls", + "extensions": { + "endpoints": { + "Default GraphQL Endpoint": { + "url": "https://api.github.com/graphql", + "introspect": false + } + } + } +} diff --git a/codegen.ts b/codegen.ts new file mode 100644 index 0000000..9df8350 --- /dev/null +++ b/codegen.ts @@ -0,0 +1,14 @@ +import type { CodegenConfig } from "@graphql-codegen/cli"; + +const config: CodegenConfig = { + overwrite: true, + schema: "./queries/github/schema.graphqls", + documents: ["./src/**/queries.ts"], + generates: { + "src/queries.data.ts": { + plugins: ["typescript", "typescript-operations"], + }, + }, +}; + +export default config; diff --git a/config.schema.json b/config.schema.json index f4ebb7c..fa510ce 100644 --- a/config.schema.json +++ b/config.schema.json @@ -87,6 +87,23 @@ "type" ], "additionalProperties": false + }, + "github": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "github" + }, + "authToken": { + "type": "string" + } + }, + "required": [ + "authToken", + "type" + ], + "additionalProperties": false } }, "additionalProperties": false @@ -122,4 +139,4 @@ "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 257f9ad..b5685c0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "prepublishOnly": "npm run build", "test": "jest", "coverage": "jest --coverage", - "schema": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./scripts/generate-schema.ts" + "schema": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./scripts/generate-schema.ts", + "codegen": "graphql-codegen --config codegen.ts" }, "bin": { "cage": "./bin/cage.cjs" @@ -31,6 +32,9 @@ "package.json" ], "devDependencies": { + "@graphql-codegen/cli": "2.15.0", + "@graphql-codegen/typescript": "2.8.3", + "@graphql-codegen/typescript-operations": "^2.5.8", "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^8.0.6", @@ -68,7 +72,9 @@ "typescript": "^4.9.3" }, "dependencies": { + "@octokit/rest": "^19.0.5", "@twitter-api-v2/plugin-rate-limit": "^1.1.0", + "@urql/core": "^3.0.5", "ajv": "^8.11.2", "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", @@ -79,6 +85,8 @@ "cronstrue": "^2.20.0", "dayjs": "^1.11.6", "fs-extra": "^11.1.0", + "graphql": "^16.6.0", + "graphql-tag": "^2.12.6", "lodash": "^4.17.21", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", @@ -89,6 +97,7 @@ "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", "strip-ansi": "^6.0.1", + "ts-essentials": "^9.3.0", "twitter-api-v2": "^1.12.9", "typeorm": "^0.3.10", "update-notifier": "^5.1.0" diff --git a/schemas/github.graphqls b/schemas/github.graphqls new file mode 100644 index 0000000..3792d13 --- /dev/null +++ b/schemas/github.graphqls @@ -0,0 +1,55134 @@ +directive @requiredCapabilities( + requiredCapabilities: [String!] +) on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +""" +Marks an element of a GraphQL schema as only available via a preview header +""" +directive @preview( + """ + The identifier of the API preview that toggles this field. + """ + toggledBy: String! +) on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +""" +Defines what type of global IDs are accepted for a mutation argument of type ID. +""" +directive @possibleTypes( + """ + Abstract type of accepted global ID + """ + abstractType: String + + """ + Accepted types of global IDs. + """ + concreteTypes: [String!]! +) on INPUT_FIELD_DEFINITION + +""" +Autogenerated input type of AbortQueuedMigrations +""" +input AbortQueuedMigrationsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that is running the migrations. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of AbortQueuedMigrations +""" +type AbortQueuedMigrationsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Autogenerated input type of AcceptEnterpriseAdministratorInvitation +""" +input AcceptEnterpriseAdministratorInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the invitation being accepted + """ + invitationId: ID! @possibleTypes(concreteTypes: ["EnterpriseAdministratorInvitation"]) +} + +""" +Autogenerated return type of AcceptEnterpriseAdministratorInvitation +""" +type AcceptEnterpriseAdministratorInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was accepted. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of accepting an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of AcceptTopicSuggestion +""" +input AcceptTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the suggested topic. + """ + name: String! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of AcceptTopicSuggestion +""" +type AcceptTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The accepted topic. + """ + topic: Topic +} + +""" +Represents an object which can take actions on GitHub. Typically a User or Bot. +""" +interface Actor { + """ + A URL pointing to the actor's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this actor. + """ + resourcePath: URI! + + """ + The HTTP URL for this actor. + """ + url: URI! +} + +""" +Location information for an actor +""" +type ActorLocation { + """ + City + """ + city: String + + """ + Country name + """ + country: String + + """ + Country code + """ + countryCode: String + + """ + Region name + """ + region: String + + """ + Region or state code + """ + regionCode: String +} + +""" +The actor's type. +""" +enum ActorType { + """ + Indicates a team actor. + """ + TEAM + + """ + Indicates a user actor. + """ + USER +} + +""" +Autogenerated input type of AddAssigneesToAssignable +""" +input AddAssigneesToAssignableInput { + """ + The id of the assignable object to add assignees to. + """ + assignableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Assignable") + + """ + The id of users to add as assignees. + """ + assigneeIds: [ID!]! @possibleTypes(concreteTypes: ["User"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of AddAssigneesToAssignable +""" +type AddAssigneesToAssignablePayload { + """ + The item that was assigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of AddComment +""" +input AddCommentInput { + """ + The contents of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") +} + +""" +Autogenerated return type of AddComment +""" +type AddCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the subject's comment connection. + """ + commentEdge: IssueCommentEdge + + """ + The subject + """ + subject: Node + + """ + The edge from the subject's timeline connection. + """ + timelineEdge: IssueTimelineItemEdge +} + +""" +Autogenerated input type of AddDiscussionComment +""" +input AddDiscussionCommentInput { + """ + The contents of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to comment on. + """ + discussionId: ID! @possibleTypes(concreteTypes: ["Discussion"]) + + """ + The Node ID of the discussion comment within this discussion to reply to. + """ + replyToId: ID @possibleTypes(concreteTypes: ["DiscussionComment"]) +} + +""" +Autogenerated return type of AddDiscussionComment +""" +type AddDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created discussion comment. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of AddDiscussionPollVote +""" +input AddDiscussionPollVoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion poll option to vote for. + """ + pollOptionId: ID! @possibleTypes(concreteTypes: ["DiscussionPollOption"]) +} + +""" +Autogenerated return type of AddDiscussionPollVote +""" +type AddDiscussionPollVotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The poll option that a vote was added to. + """ + pollOption: DiscussionPollOption +} + +""" +Autogenerated input type of AddEnterpriseOrganizationMember +""" +input AddEnterpriseOrganizationMemberInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise which owns the organization. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The ID of the organization the users will be added to. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The role to assign the users in the organization + """ + role: OrganizationMemberRole + + """ + The IDs of the enterprise members to add. + """ + userIds: [ID!]! +} + +""" +Autogenerated return type of AddEnterpriseOrganizationMember +""" +type AddEnterpriseOrganizationMemberPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The users who were added to the organization. + """ + users: [User!] +} + +""" +Autogenerated input type of AddEnterpriseSupportEntitlement +""" +input AddEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a member who will receive the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of AddEnterpriseSupportEntitlement +""" +type AddEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of adding the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of AddLabelsToLabelable +""" +input AddLabelsToLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ids of the labels to add. + """ + labelIds: [ID!]! @possibleTypes(concreteTypes: ["Label"]) + + """ + The id of the labelable object to add labels to. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Discussion", "Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of AddLabelsToLabelable +""" +type AddLabelsToLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was labeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of AddProjectCard +""" +input AddProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The content of the card. Must be a member of the ProjectCardItem union + """ + contentId: ID @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "ProjectCardItem") + + """ + The note on the card. + """ + note: String + + """ + The Node ID of the ProjectColumn. + """ + projectColumnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of AddProjectCard +""" +type AddProjectCardPayload { + """ + The edge from the ProjectColumn's card connection. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectColumn + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of AddProjectColumn +""" +input AddProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the column. + """ + name: String! + + """ + The Node ID of the project. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) +} + +""" +Autogenerated return type of AddProjectColumn +""" +type AddProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the project's column connection. + """ + columnEdge: ProjectColumnEdge + + """ + The project + """ + project: Project +} + +""" +Autogenerated input type of AddProjectDraftIssue +""" +input AddProjectDraftIssueInput { + """ + The IDs of the assignees of the draft issue. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `assigneeIds` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body of the draft issue. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `body` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to add the draft issue to. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `projectId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + projectId: ID @possibleTypes(concreteTypes: ["ProjectNext"]) + + """ + The title of the draft issue. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `title` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + title: String +} + +""" +Autogenerated return type of AddProjectDraftIssue +""" +type AddProjectDraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue added to the project. + """ + projectNextItem: ProjectNextItem + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Autogenerated input type of AddProjectNextItem +""" +input AddProjectNextItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The content id of the item (Issue or PullRequest). This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `contentId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + contentId: ID + @possibleTypes(concreteTypes: ["DraftIssue", "Issue", "PullRequest"], abstractType: "ProjectNextItemContent") + + """ + The ID of the Project to add the item to. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `projectId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + projectId: ID @possibleTypes(concreteTypes: ["ProjectNext"]) +} + +""" +Autogenerated return type of AddProjectNextItem +""" +type AddProjectNextItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item added to the project. + """ + projectNextItem: ProjectNextItem + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Autogenerated input type of AddProjectV2DraftIssue +""" +input AddProjectV2DraftIssueInput { + """ + The IDs of the assignees of the draft issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body of the draft issue. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to add the draft issue to. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The title of the draft issue. + """ + title: String! +} + +""" +Autogenerated return type of AddProjectV2DraftIssue +""" +type AddProjectV2DraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue added to the project. + """ + projectItem: ProjectV2Item +} + +""" +Autogenerated input type of AddProjectV2ItemById +""" +input AddProjectV2ItemByIdInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the Issue or Pull Request to add. + """ + contentId: ID! + @possibleTypes(concreteTypes: ["DraftIssue", "Issue", "PullRequest"], abstractType: "ProjectV2ItemContent") + + """ + The ID of the Project to add the item to. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of AddProjectV2ItemById +""" +type AddProjectV2ItemByIdPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item added to the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of AddPullRequestReviewComment +""" +input AddPullRequestReviewCommentInput { + """ + The text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The SHA of the commit to comment on. + """ + commitOID: GitObjectID + + """ + The comment id to reply to. + """ + inReplyTo: ID @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) + + """ + The relative path of the file to comment on. + """ + path: String + + """ + The line index in the diff to comment on. + """ + position: Int + + """ + The node ID of the pull request reviewing + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node ID of the review to modify. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of AddPullRequestReviewComment +""" +type AddPullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created comment. + """ + comment: PullRequestReviewComment + + """ + The edge from the review's comment connection. + """ + commentEdge: PullRequestReviewCommentEdge +} + +""" +Autogenerated input type of AddPullRequestReview +""" +input AddPullRequestReviewInput { + """ + The contents of the review body comment. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The review line comments. + """ + comments: [DraftPullRequestReviewComment] + + """ + The commit OID the review pertains to. + """ + commitOID: GitObjectID + + """ + The event to perform on the pull request review. + """ + event: PullRequestReviewEvent + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The review line comment threads. + """ + threads: [DraftPullRequestReviewThread] +} + +""" +Autogenerated return type of AddPullRequestReview +""" +type AddPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created pull request review. + """ + pullRequestReview: PullRequestReview + + """ + The edge from the pull request's review connection. + """ + reviewEdge: PullRequestReviewEdge +} + +""" +Autogenerated input type of AddPullRequestReviewThread +""" +input AddPullRequestReviewThreadInput { + """ + Body of the thread's first comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The line of the blob to which the thread refers. The end of the line range for multi-line comments. + """ + line: Int! + + """ + Path to the file being commented on. + """ + path: String! + + """ + The node ID of the pull request reviewing + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node ID of the review to modify. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT +} + +""" +Autogenerated return type of AddPullRequestReviewThread +""" +type AddPullRequestReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created thread. + """ + thread: PullRequestReviewThread +} + +""" +Autogenerated input type of AddReaction +""" +input AddReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the emoji to react with. + """ + content: ReactionContent! + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + @possibleTypes( + concreteTypes: [ + "CommitComment" + "Discussion" + "DiscussionComment" + "Issue" + "IssueComment" + "PullRequest" + "PullRequestReview" + "PullRequestReviewComment" + "Release" + "TeamDiscussion" + "TeamDiscussionComment" + ] + abstractType: "Reactable" + ) +} + +""" +Autogenerated return type of AddReaction +""" +type AddReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of AddStar +""" +input AddStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to star. + """ + starrableId: ID! @possibleTypes(concreteTypes: ["Gist", "Repository", "Topic"], abstractType: "Starrable") +} + +""" +Autogenerated return type of AddStar +""" +type AddStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Autogenerated input type of AddUpvote +""" +input AddUpvoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion or comment to upvote. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["Discussion", "DiscussionComment"], abstractType: "Votable") +} + +""" +Autogenerated return type of AddUpvote +""" +type AddUpvotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The votable subject. + """ + subject: Votable +} + +""" +Autogenerated input type of AddVerifiableDomain +""" +input AddVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The URL of the domain + """ + domain: URI! + + """ + The ID of the owner to add the domain to + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Enterprise", "Organization"], abstractType: "VerifiableDomainOwner") +} + +""" +Autogenerated return type of AddVerifiableDomain +""" +type AddVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was added. + """ + domain: VerifiableDomain +} + +""" +Represents a 'added_to_project' event on a given issue or pull request. +""" +type AddedToProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +A GitHub App. +""" +type App implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the app. + """ + description: String + id: ID! + + """ + The IP addresses of the app. + """ + ipAllowListEntries( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for IP allow list entries returned. + """ + orderBy: IpAllowListEntryOrder = {field: ALLOW_LIST_VALUE, direction: ASC} + ): IpAllowListEntryConnection! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + A URL pointing to the app's logo. + """ + logoUrl( + """ + The size of the resulting image. + """ + size: Int + ): URI! + + """ + The name of the app. + """ + name: String! + + """ + A slug based on the name of the app for use in URLs. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to the app's homepage. + """ + url: URI! +} + +""" +Autogenerated input type of ApproveDeployments +""" +input ApproveDeploymentsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Optional comment for approving deployments + """ + comment: String = "" + + """ + The ids of environments to reject deployments + """ + environmentIds: [ID!]! + + """ + The node ID of the workflow run containing the pending deployments. + """ + workflowRunId: ID! @possibleTypes(concreteTypes: ["WorkflowRun"]) +} + +""" +Autogenerated return type of ApproveDeployments +""" +type ApproveDeploymentsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The affected deployments. + """ + deployments: [Deployment!] +} + +""" +Autogenerated input type of ApproveVerifiableDomain +""" +input ApproveVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to approve. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of ApproveVerifiableDomain +""" +type ApproveVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was approved. + """ + domain: VerifiableDomain +} + +""" +Autogenerated input type of ArchiveProjectV2Item +""" +input ArchiveProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the ProjectV2Item to archive. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project to archive the item from. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of ArchiveProjectV2Item +""" +type ArchiveProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item archived from the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of ArchiveRepository +""" +input ArchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to mark as archived. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of ArchiveRepository +""" +type ArchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was marked as archived. + """ + repository: Repository +} + +""" +An object that can have users assigned to it. +""" +interface Assignable { + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! +} + +""" +Represents an 'assigned' event on any assignable object. +""" +type AssignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was assigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the user who was assigned. + """ + user: User + @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Types that can be assigned to issues. +""" +union Assignee = Bot | Mannequin | Organization | User + +""" +An entry in the audit log. +""" +interface AuditEntry { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types that can initiate an audit log event. +""" +union AuditEntryActor = Bot | Organization | User + +""" +Ordering options for Audit Log connections. +""" +input AuditLogOrder { + """ + The ordering direction. + """ + direction: OrderDirection + + """ + The field to order Audit Logs by. + """ + field: AuditLogOrderField +} + +""" +Properties by which Audit Log connections can be ordered. +""" +enum AuditLogOrderField { + """ + Order audit log entries by timestamp + """ + CREATED_AT +} + +""" +Represents a 'auto_merge_disabled' event on a given pull request. +""" +type AutoMergeDisabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who disabled auto-merge for this Pull Request + """ + disabler: User + id: ID! + + """ + PullRequest referenced by event + """ + pullRequest: PullRequest + + """ + The reason auto-merge was disabled + """ + reason: String + + """ + The reason_code relating to why auto-merge was disabled + """ + reasonCode: String +} + +""" +Represents a 'auto_merge_enabled' event on a given pull request. +""" +type AutoMergeEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents an auto-merge request for a pull request +""" +type AutoMergeRequest { + """ + The email address of the author of this auto-merge request. + """ + authorEmail: String + + """ + The commit message of the auto-merge request. If a merge queue is required by + the base branch, this value will be set by the merge queue when merging. + """ + commitBody: String + + """ + The commit title of the auto-merge request. If a merge queue is required by + the base branch, this value will be set by the merge queue when merging + """ + commitHeadline: String + + """ + When was this auto-merge request was enabled. + """ + enabledAt: DateTime + + """ + The actor who created the auto-merge request. + """ + enabledBy: Actor + + """ + The merge method of the auto-merge request. If a merge queue is required by + the base branch, this value will be set by the merge queue when merging. + """ + mergeMethod: PullRequestMergeMethod! + + """ + The pull request that this auto-merge request is set against. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'auto_rebase_enabled' event on a given pull request. +""" +type AutoRebaseEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (rebase) for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'auto_squash_enabled' event on a given pull request. +""" +type AutoSquashEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (squash) for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'automatic_base_change_failed' event on a given pull request. +""" +type AutomaticBaseChangeFailedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'automatic_base_change_succeeded' event on a given pull request. +""" +type AutomaticBaseChangeSucceededEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +A (potentially binary) string encoded using base64. +""" +scalar Base64String + +""" +Represents a 'base_ref_changed' event on a given issue or pull request. +""" +type BaseRefChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the name of the base ref for the pull request after it was changed. + """ + currentRefName: String! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Identifies the name of the base ref for the pull request before it was changed. + """ + previousRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'base_ref_deleted' event on a given pull request. +""" +type BaseRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the name of the Ref associated with the `base_ref_deleted` event. + """ + baseRefName: String + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'base_ref_force_pushed' event on a given pull request. +""" +type BaseRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'base_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'base_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'base_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents a Git blame. +""" +type Blame { + """ + The list of ranges from a Git blame. + """ + ranges: [BlameRange!]! +} + +""" +Represents a range of information from a Git blame. +""" +type BlameRange { + """ + Identifies the recency of the change, from 1 (new) to 10 (old). This is + calculated as a 2-quantile and determines the length of distance between the + median age of all the changes in the file and the recency of the current + range's change. + """ + age: Int! + + """ + Identifies the line author + """ + commit: Commit! + + """ + The ending line for the range + """ + endingLine: Int! + + """ + The starting line for the range + """ + startingLine: Int! +} + +""" +Represents a Git blob. +""" +type Blob implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + Byte size of Blob object + """ + byteSize: Int! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + Indicates whether the Blob is binary or text. Returns null if unable to determine the encoding. + """ + isBinary: Boolean + + """ + Indicates whether the contents is truncated + """ + isTruncated: Boolean! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + UTF8 text data or null if the Blob is binary + """ + text: String +} + +""" +A special type of user which takes actions on behalf of GitHub Apps. +""" +type Bot implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this bot + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this bot + """ + url: URI! +} + +""" +Types which can be actors for `BranchActorAllowance` objects. +""" +union BranchActorAllowanceActor = App | Team | User + +""" +A branch protection rule. +""" +type BranchProtectionRule implements Node { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean! + + """ + A list of conflicts matching branches protection rule and other branch protection rules + """ + branchProtectionRuleConflicts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BranchProtectionRuleConflictConnection! + + """ + A list of actors able to force push for this branch protection rule. + """ + bypassForcePushAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BypassForcePushAllowanceConnection! + + """ + A list of actors able to bypass PRs for this branch protection rule. + """ + bypassPullRequestAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BypassPullRequestAllowanceConnection! + + """ + The actor who created this branch protection rule. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean! + id: ID! + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean! + + """ + Whether users can pull changes from upstream when the branch is locked. Set to + `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean! + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean! + + """ + Repository refs that are protected by this rule + """ + matchingRefs( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters refs with query on name + """ + query: String + ): RefConnection! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + A list push allowances for this branch protection rule. + """ + pushAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PushAllowanceConnection! + + """ + The repository associated with this branch protection rule. + """ + repository: Repository + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean! + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + List of required status checks that must pass for commits to be accepted to matching branches. + """ + requiredStatusChecks: [RequiredStatusCheckDescription!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean! + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean! + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean! + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean! + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean! + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean! + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean! + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean! + + """ + A list review dismissal allowances for this branch protection rule. + """ + reviewDismissalAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReviewDismissalAllowanceConnection! +} + +""" +A conflict between two branch protection rules. +""" +type BranchProtectionRuleConflict { + """ + Identifies the branch protection rule. + """ + branchProtectionRule: BranchProtectionRule + + """ + Identifies the conflicting branch protection rule. + """ + conflictingBranchProtectionRule: BranchProtectionRule + + """ + Identifies the branch ref that has conflicting rules + """ + ref: Ref +} + +""" +The connection type for BranchProtectionRuleConflict. +""" +type BranchProtectionRuleConflictConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleConflictEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRuleConflict] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleConflictEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRuleConflict +} + +""" +The connection type for BranchProtectionRule. +""" +type BranchProtectionRuleConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRule +} + +""" +A user, team, or app who has the ability to bypass a force push requirement on a protected branch. +""" +type BypassForcePushAllowance implements Node { + """ + The actor that can force push. + """ + actor: BranchActorAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +The connection type for BypassForcePushAllowance. +""" +type BypassForcePushAllowanceConnection { + """ + A list of edges. + """ + edges: [BypassForcePushAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [BypassForcePushAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BypassForcePushAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BypassForcePushAllowance +} + +""" +A user, team, or app who has the ability to bypass a pull request requirement on a protected branch. +""" +type BypassPullRequestAllowance implements Node { + """ + The actor that can bypass. + """ + actor: BranchActorAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +The connection type for BypassPullRequestAllowance. +""" +type BypassPullRequestAllowanceConnection { + """ + A list of edges. + """ + edges: [BypassPullRequestAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [BypassPullRequestAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BypassPullRequestAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BypassPullRequestAllowance +} + +""" +The Common Vulnerability Scoring System +""" +type CVSS { + """ + The CVSS score associated with this advisory + """ + score: Float! + + """ + The CVSS vector string associated with this advisory + """ + vectorString: String +} + +""" +A common weakness enumeration +""" +type CWE implements Node { + """ + The id of the CWE + """ + cweId: String! + + """ + A detailed description of this CWE + """ + description: String! + id: ID! + + """ + The name of this CWE + """ + name: String! +} + +""" +The connection type for CWE. +""" +type CWEConnection { + """ + A list of edges. + """ + edges: [CWEEdge] + + """ + A list of nodes. + """ + nodes: [CWE] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CWEEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CWE +} + +""" +Autogenerated input type of CancelEnterpriseAdminInvitation +""" +input CancelEnterpriseAdminInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pending enterprise administrator invitation. + """ + invitationId: ID! @possibleTypes(concreteTypes: ["EnterpriseAdministratorInvitation"]) +} + +""" +Autogenerated return type of CancelEnterpriseAdminInvitation +""" +type CancelEnterpriseAdminInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was canceled. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of canceling an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of CancelSponsorship +""" +input CancelSponsorshipInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user or organization who is acting as the sponsor, paying for + the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsor") + + """ + The username of the user or organization who is acting as the sponsor, paying + for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsorable") + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String +} + +""" +Autogenerated return type of CancelSponsorship +""" +type CancelSponsorshipPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The tier that was being used at the time of cancellation. + """ + sponsorsTier: SponsorsTier +} + +""" +Autogenerated input type of ChangeUserStatus +""" +input ChangeUserStatusInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The emoji to represent your status. Can either be a native Unicode emoji or an emoji name with colons, e.g., :grinning:. + """ + emoji: String + + """ + If set, the user status will not be shown after this date. + """ + expiresAt: DateTime + + """ + Whether this status should indicate you are not fully available on GitHub, e.g., you are away. + """ + limitedAvailability: Boolean = false + + """ + A short description of your current status. + """ + message: String + + """ + The ID of the organization whose members will be allowed to see the status. If + omitted, the status will be publicly visible. + """ + organizationId: ID @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of ChangeUserStatus +""" +type ChangeUserStatusPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Your updated status. + """ + status: UserStatus +} + +""" +A single check annotation. +""" +type CheckAnnotation { + """ + The annotation's severity level. + """ + annotationLevel: CheckAnnotationLevel + + """ + The path to the file that this annotation was made on. + """ + blobUrl: URI! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The position of this annotation. + """ + location: CheckAnnotationSpan! + + """ + The annotation's message. + """ + message: String! + + """ + The path that this annotation was made on. + """ + path: String! + + """ + Additional information about the annotation. + """ + rawDetails: String + + """ + The annotation's title + """ + title: String +} + +""" +The connection type for CheckAnnotation. +""" +type CheckAnnotationConnection { + """ + A list of edges. + """ + edges: [CheckAnnotationEdge] + + """ + A list of nodes. + """ + nodes: [CheckAnnotation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationData { + """ + Represents an annotation's information level + """ + annotationLevel: CheckAnnotationLevel! + + """ + The location of the annotation + """ + location: CheckAnnotationRange! + + """ + A short description of the feedback for these lines of code. + """ + message: String! + + """ + The path of the file to add an annotation to. + """ + path: String! + + """ + Details about this annotation. + """ + rawDetails: String + + """ + The title that represents the annotation. + """ + title: String +} + +""" +An edge in a connection. +""" +type CheckAnnotationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckAnnotation +} + +""" +Represents an annotation's information level. +""" +enum CheckAnnotationLevel { + """ + An annotation indicating an inescapable error. + """ + FAILURE + + """ + An annotation indicating some information. + """ + NOTICE + + """ + An annotation indicating an ignorable error. + """ + WARNING +} + +""" +A character position in a check annotation. +""" +type CheckAnnotationPosition { + """ + Column number (1 indexed). + """ + column: Int + + """ + Line number (1 indexed). + """ + line: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationRange { + """ + The ending column of the range. + """ + endColumn: Int + + """ + The ending line of the range. + """ + endLine: Int! + + """ + The starting column of the range. + """ + startColumn: Int + + """ + The starting line of the range. + """ + startLine: Int! +} + +""" +An inclusive pair of positions for a check annotation. +""" +type CheckAnnotationSpan { + """ + End position (inclusive). + """ + end: CheckAnnotationPosition! + + """ + Start position (inclusive). + """ + start: CheckAnnotationPosition! +} + +""" +The possible states for a check suite or run conclusion. +""" +enum CheckConclusionState { + """ + The check suite or run requires action. + """ + ACTION_REQUIRED + + """ + The check suite or run has been cancelled. + """ + CANCELLED + + """ + The check suite or run has failed. + """ + FAILURE + + """ + The check suite or run was neutral. + """ + NEUTRAL + + """ + The check suite or run was skipped. + """ + SKIPPED + + """ + The check suite or run was marked stale by GitHub. Only GitHub can use this conclusion. + """ + STALE + + """ + The check suite or run has failed at startup. + """ + STARTUP_FAILURE + + """ + The check suite or run has succeeded. + """ + SUCCESS + + """ + The check suite or run has timed out. + """ + TIMED_OUT +} + +""" +A check run. +""" +type CheckRun implements Node & RequirableByPullRequest & UniformResourceLocatable { + """ + The check run's annotations + """ + annotations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckAnnotationConnection + + """ + The check suite that this run is a part of. + """ + checkSuite: CheckSuite! + + """ + Identifies the date and time when the check run was completed. + """ + completedAt: DateTime + + """ + The conclusion of the check run. + """ + conclusion: CheckConclusionState + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The corresponding deployment for this job, if any + """ + deployment: Deployment + + """ + The URL from which to find full details of the check run on the integrator's site. + """ + detailsUrl: URI + + """ + A reference for the check run on the integrator's system. + """ + externalId: String + id: ID! + + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired( + """ + The id of the pull request this is required for + """ + pullRequestId: ID + + """ + The number of the pull request this is required for + """ + pullRequestNumber: Int + ): Boolean! + + """ + The name of the check for this check run. + """ + name: String! + + """ + Information about a pending deployment, if any, in this check run + """ + pendingDeploymentRequest: DeploymentRequest + + """ + The permalink to the check run summary. + """ + permalink: URI! + + """ + The repository associated with this check run. + """ + repository: Repository! + + """ + The HTTP path for this check run. + """ + resourcePath: URI! + + """ + Identifies the date and time when the check run was started. + """ + startedAt: DateTime + + """ + The current status of the check run. + """ + status: CheckStatusState! + + """ + The check run's steps + """ + steps( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Step number + """ + number: Int + ): CheckStepConnection + + """ + A string representing the check run's summary + """ + summary: String + + """ + A string representing the check run's text + """ + text: String + + """ + A string representing the check run + """ + title: String + + """ + The HTTP URL for this check run. + """ + url: URI! +} + +""" +Possible further actions the integrator can perform. +""" +input CheckRunAction { + """ + A short explanation of what this action would do. + """ + description: String! + + """ + A reference for the action on the integrator's system. + """ + identifier: String! + + """ + The text to be displayed on a button in the web UI. + """ + label: String! +} + +""" +The connection type for CheckRun. +""" +type CheckRunConnection { + """ + A list of edges. + """ + edges: [CheckRunEdge] + + """ + A list of nodes. + """ + nodes: [CheckRun] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckRunEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckRun +} + +""" +The filters that are available when fetching check runs. +""" +input CheckRunFilter { + """ + Filters the check runs created by this application ID. + """ + appId: Int + + """ + Filters the check runs by this name. + """ + checkName: String + + """ + Filters the check runs by this type. + """ + checkType: CheckRunType + + """ + Filters the check runs by these conclusions. + """ + conclusions: [CheckConclusionState!] + + """ + Filters the check runs by this status. Superceded by statuses. + """ + status: CheckStatusState + + """ + Filters the check runs by this status. Overrides status. + """ + statuses: [CheckStatusState!] +} + +""" +Descriptive details about the check run. +""" +input CheckRunOutput { + """ + The annotations that are made as part of the check run. + """ + annotations: [CheckAnnotationData!] + + """ + Images attached to the check run output displayed in the GitHub pull request UI. + """ + images: [CheckRunOutputImage!] + + """ + The summary of the check run (supports Commonmark). + """ + summary: String! + + """ + The details of the check run (supports Commonmark). + """ + text: String + + """ + A title to provide for this check run. + """ + title: String! +} + +""" +Images attached to the check run output displayed in the GitHub pull request UI. +""" +input CheckRunOutputImage { + """ + The alternative text for the image. + """ + alt: String! + + """ + A short image description. + """ + caption: String + + """ + The full URL of the image. + """ + imageUrl: URI! +} + +""" +The possible states of a check run in a status rollup. +""" +enum CheckRunState { + """ + The check run requires action. + """ + ACTION_REQUIRED + + """ + The check run has been cancelled. + """ + CANCELLED + + """ + The check run has been completed. + """ + COMPLETED + + """ + The check run has failed. + """ + FAILURE + + """ + The check run is in progress. + """ + IN_PROGRESS + + """ + The check run was neutral. + """ + NEUTRAL + + """ + The check run is in pending state. + """ + PENDING + + """ + The check run has been queued. + """ + QUEUED + + """ + The check run was skipped. + """ + SKIPPED + + """ + The check run was marked stale by GitHub. Only GitHub can use this conclusion. + """ + STALE + + """ + The check run has failed at startup. + """ + STARTUP_FAILURE + + """ + The check run has succeeded. + """ + SUCCESS + + """ + The check run has timed out. + """ + TIMED_OUT + + """ + The check run is in waiting state. + """ + WAITING +} + +""" +Represents a count of the state of a check run. +""" +type CheckRunStateCount { + """ + The number of check runs with this state. + """ + count: Int! + + """ + The state of a check run. + """ + state: CheckRunState! +} + +""" +The possible types of check runs. +""" +enum CheckRunType { + """ + Every check run available. + """ + ALL + + """ + The latest check run. + """ + LATEST +} + +""" +The possible states for a check suite or run status. +""" +enum CheckStatusState { + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run is in pending state. + """ + PENDING + + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run has been requested. + """ + REQUESTED + + """ + The check suite or run is in waiting state. + """ + WAITING +} + +""" +A single check step. +""" +type CheckStep { + """ + Identifies the date and time when the check step was completed. + """ + completedAt: DateTime + + """ + The conclusion of the check step. + """ + conclusion: CheckConclusionState + + """ + A reference for the check step on the integrator's system. + """ + externalId: String + + """ + The step's name. + """ + name: String! + + """ + The index of the step in the list of steps of the parent check run. + """ + number: Int! + + """ + Number of seconds to completion. + """ + secondsToCompletion: Int + + """ + Identifies the date and time when the check step was started. + """ + startedAt: DateTime + + """ + The current status of the check step. + """ + status: CheckStatusState! +} + +""" +The connection type for CheckStep. +""" +type CheckStepConnection { + """ + A list of edges. + """ + edges: [CheckStepEdge] + + """ + A list of nodes. + """ + nodes: [CheckStep] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckStepEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckStep +} + +""" +A check suite. +""" +type CheckSuite implements Node { + """ + The GitHub App which created this check suite. + """ + app: App + + """ + The name of the branch for this check suite. + """ + branch: Ref + + """ + The check runs associated with a check suite. + """ + checkRuns( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filters the check runs by this type. + """ + filterBy: CheckRunFilter + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckRunConnection + + """ + The commit for this check suite + """ + commit: Commit! + + """ + The conclusion of this check suite. + """ + conclusion: CheckConclusionState + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who triggered the check suite. + """ + creator: User + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + A list of open pull requests matching the check suite. + """ + matchingPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection + + """ + The push that triggered this check suite. + """ + push: Push + + """ + The repository associated with this check suite. + """ + repository: Repository! + + """ + The HTTP path for this check suite + """ + resourcePath: URI! + + """ + The status of this check suite. + """ + status: CheckStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this check suite + """ + url: URI! + + """ + The workflow run associated with this check suite. + """ + workflowRun: WorkflowRun +} + +""" +The auto-trigger preferences that are available for check suites. +""" +input CheckSuiteAutoTriggerPreference { + """ + The node ID of the application that owns the check suite. + """ + appId: ID! + + """ + Set to `true` to enable automatic creation of CheckSuite events upon pushes to the repository. + """ + setting: Boolean! +} + +""" +The connection type for CheckSuite. +""" +type CheckSuiteConnection { + """ + A list of edges. + """ + edges: [CheckSuiteEdge] + + """ + A list of nodes. + """ + nodes: [CheckSuite] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckSuiteEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckSuite +} + +""" +The filters that are available when fetching check suites. +""" +input CheckSuiteFilter { + """ + Filters the check suites created by this application ID. + """ + appId: Int + + """ + Filters the check suites by this name. + """ + checkName: String +} + +""" +Autogenerated input type of ClearLabelsFromLabelable +""" +input ClearLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the labelable object to clear the labels from. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Discussion", "Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of ClearLabelsFromLabelable +""" +type ClearLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlabeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of ClearProjectV2ItemFieldValue +""" +input ClearProjectV2ItemFieldValueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the field to be cleared. + """ + fieldId: ID! + @possibleTypes( + concreteTypes: ["ProjectV2Field", "ProjectV2IterationField", "ProjectV2SingleSelectField"] + abstractType: "ProjectV2FieldConfiguration" + ) + + """ + The ID of the item to be cleared. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of ClearProjectV2ItemFieldValue +""" +type ClearProjectV2ItemFieldValuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated item. + """ + projectV2Item: ProjectV2Item +} + +""" +Autogenerated input type of CloneProject +""" +input CloneProjectInput { + """ + The description of the project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not to clone the source project's workflows. + """ + includeWorkflows: Boolean! + + """ + The name of the project. + """ + name: String! + + """ + The visibility of the project, defaults to false (private). + """ + public: Boolean + + """ + The source project to clone. + """ + sourceId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The owner ID to create the project under. + """ + targetOwnerId: ID! @possibleTypes(concreteTypes: ["Organization", "Repository", "User"], abstractType: "ProjectOwner") +} + +""" +Autogenerated return type of CloneProject +""" +type CloneProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the JobStatus for populating cloned fields. + """ + jobStatusId: String + + """ + The new cloned project. + """ + project: Project +} + +""" +Autogenerated input type of CloneTemplateRepository +""" +input CloneTemplateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A short description of the new repository. + """ + description: String + + """ + Whether to copy all branches from the template to the new repository. Defaults + to copying only the default branch of the template. + """ + includeAllBranches: Boolean = false + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "RepositoryOwner") + + """ + The Node ID of the template repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Autogenerated return type of CloneTemplateRepository +""" +type CloneTemplateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +An object that can be closed +""" +interface Closable { + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime +} + +""" +Autogenerated input type of CloseIssue +""" +input CloseIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be closed. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + The reason the issue is to be closed. + """ + stateReason: IssueClosedStateReason +} + +""" +Autogenerated return type of CloseIssue +""" +type CloseIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was closed. + """ + issue: Issue +} + +""" +Autogenerated input type of ClosePullRequest +""" +input ClosePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be closed. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of ClosePullRequest +""" +type ClosePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was closed. + """ + pullRequest: PullRequest +} + +""" +Represents a 'closed' event on any `Closable`. +""" +type ClosedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was closed. + """ + closable: Closable! + + """ + Object which triggered the creation of this event. + """ + closer: Closer + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The HTTP path for this closed event. + """ + resourcePath: URI! + + """ + The reason the issue state was changed to closed. + """ + stateReason: IssueStateReason + + """ + The HTTP URL for this closed event. + """ + url: URI! +} + +""" +The object which triggered a `ClosedEvent`. +""" +union Closer = Commit | PullRequest + +""" +The Code of Conduct for a repository +""" +type CodeOfConduct implements Node { + """ + The body of the Code of Conduct + """ + body: String + id: ID! + + """ + The key for the Code of Conduct + """ + key: String! + + """ + The formal name of the Code of Conduct + """ + name: String! + + """ + The HTTP path for this Code of Conduct + """ + resourcePath: URI + + """ + The HTTP URL for this Code of Conduct + """ + url: URI +} + +""" +Collaborators affiliation level with a subject. +""" +enum CollaboratorAffiliation { + """ + All collaborators the authenticated user can see. + """ + ALL + + """ + All collaborators with permissions to an organization-owned subject, regardless of organization membership status. + """ + DIRECT + + """ + All outside collaborators of an organization-owned subject. + """ + OUTSIDE +} + +""" +Represents a comment. +""" +interface Comment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A comment author association with repository. +""" +enum CommentAuthorAssociation { + """ + Author has been invited to collaborate on the repository. + """ + COLLABORATOR + + """ + Author has previously committed to the repository. + """ + CONTRIBUTOR + + """ + Author has not previously committed to GitHub. + """ + FIRST_TIMER + + """ + Author has not previously committed to the repository. + """ + FIRST_TIME_CONTRIBUTOR + + """ + Author is a placeholder for an unclaimed user. + """ + MANNEQUIN + + """ + Author is a member of the organization that owns the repository. + """ + MEMBER + + """ + Author has no association with the repository. + """ + NONE + + """ + Author is the owner of the repository. + """ + OWNER +} + +""" +The possible errors that will prevent a user from updating a comment. +""" +enum CommentCannotUpdateReason { + """ + Unable to create comment because repository is archived. + """ + ARCHIVED + + """ + You cannot update this comment + """ + DENIED + + """ + You must be the author or have write access to this repository to update this comment. + """ + INSUFFICIENT_ACCESS + + """ + Unable to create comment because issue is locked. + """ + LOCKED + + """ + You must be logged in to update this comment. + """ + LOGIN_REQUIRED + + """ + Repository is under maintenance. + """ + MAINTENANCE + + """ + At least one email address must be verified to update this comment. + """ + VERIFIED_EMAIL_REQUIRED +} + +""" +Represents a 'comment_deleted' event on a given issue or pull request. +""" +type CommentDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user who authored the deleted comment. + """ + deletedCommentAuthor: Actor + id: ID! +} + +""" +Represents a Git commit. +""" +type Commit implements GitObject & Node & Subscribable & UniformResourceLocatable { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The number of additions in this commit. + """ + additions: Int! + + """ + The merged Pull Request that introduced the commit to the repository. If the + commit is not present in the default branch, additionally returns open Pull + Requests associated with the commit + """ + associatedPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests. + """ + orderBy: PullRequestOrder = {field: CREATED_AT, direction: ASC} + ): PullRequestConnection + + """ + Authorship details of the commit. + """ + author: GitActor + + """ + Check if the committer and the author match. + """ + authoredByCommitter: Boolean! + + """ + The datetime when this commit was authored. + """ + authoredDate: DateTime! + + """ + The list of authors for this commit based on the git author and the Co-authored-by + message trailer. The git author will always be first. + """ + authors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GitActorConnection! + + """ + Fetches `git blame` information. + """ + blame( + """ + The file whose Git blame information you want. + """ + path: String! + ): Blame! + + """ + We recommend using the `changedFielsIfAvailable` field instead of + `changedFiles`, as `changedFiles` will cause your request to return an error + if GitHub is unable to calculate the number of changed files. + """ + changedFiles: Int! + @deprecated( + reason: "`changedFiles` will be removed. Use `changedFilesIfAvailable` instead. Removal on 2023-01-01 UTC." + ) + + """ + The number of changed files in this commit. If GitHub is unable to calculate + the number of changed files (for example due to a timeout), this will return + `null`. We recommend using this field instead of `changedFiles`. + """ + changedFilesIfAvailable: Int + + """ + The check suites associated with a commit. + """ + checkSuites( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filters the check suites by this type. + """ + filterBy: CheckSuiteFilter + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckSuiteConnection + + """ + Comments made on the commit. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The datetime when this commit was committed. + """ + committedDate: DateTime! + + """ + Check if committed via GitHub web UI. + """ + committedViaWeb: Boolean! + + """ + Committer details of the commit. + """ + committer: GitActor + + """ + The number of deletions in this commit. + """ + deletions: Int! + + """ + The deployments associated with a commit. + """ + deployments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Environments to list deployments for + """ + environments: [String!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for deployments returned from the connection. + """ + orderBy: DeploymentOrder = {field: CREATED_AT, direction: ASC} + ): DeploymentConnection + + """ + The tree entry representing the file located at the given path. + """ + file( + """ + The path for the file + """ + path: String! + ): TreeEntry + + """ + The linear commit history starting from (and including) this commit, in the same order as `git log`. + """ + history( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + If non-null, filters history to only show commits with matching authorship. + """ + author: CommitAuthor + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + If non-null, filters history to only show commits touching files under this path. + """ + path: String + + """ + Allows specifying a beginning time or date for fetching commits. + """ + since: GitTimestamp + + """ + Allows specifying an ending time or date for fetching commits. + """ + until: GitTimestamp + ): CommitHistoryConnection! + id: ID! + + """ + The Git commit message + """ + message: String! + + """ + The Git commit message body + """ + messageBody: String! + + """ + The commit message body rendered to HTML. + """ + messageBodyHTML: HTML! + + """ + The Git commit message headline + """ + messageHeadline: String! + + """ + The commit message headline rendered to HTML. + """ + messageHeadlineHTML: HTML! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The organization this commit was made on behalf of. + """ + onBehalfOf: Organization + + """ + The parents of a commit. + """ + parents( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitConnection! + + """ + The datetime when this commit was pushed. + """ + pushedDate: DateTime + + """ + The Repository this commit belongs to + """ + repository: Repository! + + """ + The HTTP path for this commit + """ + resourcePath: URI! + + """ + Commit signing information, if present. + """ + signature: GitSignature + + """ + Status information for this commit + """ + status: Status + + """ + Check and Status rollup information for this commit. + """ + statusCheckRollup: StatusCheckRollup + + """ + Returns a list of all submodules in this repository as of this Commit parsed from the .gitmodules file. + """ + submodules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): SubmoduleConnection! + + """ + Returns a URL to download a tarball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + tarballUrl: URI! + + """ + Commit's root Tree + """ + tree: Tree! + + """ + The HTTP path for the tree of this commit + """ + treeResourcePath: URI! + + """ + The HTTP URL for the tree of this commit + """ + treeUrl: URI! + + """ + The HTTP URL for this commit + """ + url: URI! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Returns a URL to download a zipball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + zipballUrl: URI! +} + +""" +Specifies an author for filtering Git commits. +""" +input CommitAuthor { + """ + Email addresses to filter by. Commits authored by any of the specified email addresses will be returned. + """ + emails: [String!] + + """ + ID of a User to filter by. If non-null, only commits authored by this user + will be returned. This field takes precedence over emails. + """ + id: ID +} + +""" +Represents a comment on a given Commit. +""" +type CommitComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment, if the commit exists. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies the file path associated with the comment. + """ + path: String + + """ + Identifies the line position associated with the comment. + """ + position: Int + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this commit comment. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this commit comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for CommitComment. +""" +type CommitCommentConnection { + """ + A list of edges. + """ + edges: [CommitCommentEdge] + + """ + A list of nodes. + """ + nodes: [CommitComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CommitCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CommitComment +} + +""" +A thread of comments on a commit. +""" +type CommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for Commit. +""" +type CommitConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Ordering options for commit contribution connections. +""" +input CommitContributionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field by which to order commit contributions. + """ + field: CommitContributionOrderField! +} + +""" +Properties by which commit contribution connections can be ordered. +""" +enum CommitContributionOrderField { + """ + Order commit contributions by how many commits they represent. + """ + COMMIT_COUNT + + """ + Order commit contributions by when they were made. + """ + OCCURRED_AT +} + +""" +This aggregates commits made by a user within one repository. +""" +type CommitContributionsByRepository { + """ + The commit contributions, each representing a day. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for commit contributions returned from the connection. + """ + orderBy: CommitContributionOrder = {field: OCCURRED_AT, direction: DESC} + ): CreatedCommitContributionConnection! + + """ + The repository in which the commits were made. + """ + repository: Repository! + + """ + The HTTP path for the user's commits to the repository in this time range. + """ + resourcePath: URI! + + """ + The HTTP URL for the user's commits to the repository in this time range. + """ + url: URI! +} + +""" +An edge in a connection. +""" +type CommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Commit +} + +""" +The connection type for Commit. +""" +type CommitHistoryConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A message to include with a new commit +""" +input CommitMessage { + """ + The body of the message. + """ + body: String + + """ + The headline of the message. + """ + headline: String! +} + +""" +A git ref for a commit to be appended to. + +The ref must be a branch, i.e. its fully qualified name must start +with `refs/heads/` (although the input is not required to be fully +qualified). + +The Ref may be specified by its global node ID or by the +`repositoryNameWithOwner` and `branchName`. + +### Examples + +Specify a branch using a global node ID: + + { "id": "MDM6UmVmMTpyZWZzL2hlYWRzL21haW4=" } + +Specify a branch using `repositoryNameWithOwner` and `branchName`: + + { + "repositoryNameWithOwner": "github/graphql-client", + "branchName": "main" + } +""" +input CommittableBranch { + """ + The unqualified name of the branch to append the commit to. + """ + branchName: String + + """ + The Node ID of the Ref to be updated. + """ + id: ID + + """ + The nameWithOwner of the repository to commit to. + """ + repositoryNameWithOwner: String +} + +""" +Represents a comparison between two commit revisions. +""" +type Comparison implements Node { + """ + The number of commits ahead of the base branch. + """ + aheadBy: Int! + + """ + The base revision of this comparison. + """ + baseTarget: GitObject! + + """ + The number of commits behind the base branch. + """ + behindBy: Int! + + """ + The commits which compose this comparison. + """ + commits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ComparisonCommitConnection! + + """ + The head revision of this comparison. + """ + headTarget: GitObject! + id: ID! + + """ + The status of this comparison. + """ + status: ComparisonStatus! +} + +""" +The connection type for Commit. +""" +type ComparisonCommitConnection { + """ + The total count of authors and co-authors across all commits. + """ + authorCount: Int! + + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The status of a git comparison between two refs. +""" +enum ComparisonStatus { + """ + The head ref is ahead of the base ref. + """ + AHEAD + + """ + The head ref is behind the base ref. + """ + BEHIND + + """ + The head ref is both ahead and behind of the base ref, indicating git history has diverged. + """ + DIVERGED + + """ + The head ref and base ref are identical. + """ + IDENTICAL +} + +""" +Represents a 'connected' event on a given issue or pull request. +""" +type ConnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was connected. + """ + subject: ReferencedSubject! +} + +""" +Represents a contribution a user made on GitHub, such as opening an issue. +""" +interface Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A calendar of contributions made on GitHub by a user. +""" +type ContributionCalendar { + """ + A list of hex color codes used in this calendar. The darker the color, the more contributions it represents. + """ + colors: [String!]! + + """ + Determine if the color set was chosen because it's currently Halloween. + """ + isHalloween: Boolean! + + """ + A list of the months of contributions in this calendar. + """ + months: [ContributionCalendarMonth!]! + + """ + The count of total contributions in the calendar. + """ + totalContributions: Int! + + """ + A list of the weeks of contributions in this calendar. + """ + weeks: [ContributionCalendarWeek!]! +} + +""" +Represents a single day of contributions on GitHub by a user. +""" +type ContributionCalendarDay { + """ + The hex color code that represents how many contributions were made on this day compared to others in the calendar. + """ + color: String! + + """ + How many contributions were made by the user on this day. + """ + contributionCount: Int! + + """ + Indication of contributions, relative to other days. Can be used to indicate + which color to represent this day on a calendar. + """ + contributionLevel: ContributionLevel! + + """ + The day this square represents. + """ + date: Date! + + """ + A number representing which day of the week this square represents, e.g., 1 is Monday. + """ + weekday: Int! +} + +""" +A month of contributions in a user's contribution graph. +""" +type ContributionCalendarMonth { + """ + The date of the first day of this month. + """ + firstDay: Date! + + """ + The name of the month. + """ + name: String! + + """ + How many weeks started in this month. + """ + totalWeeks: Int! + + """ + The year the month occurred in. + """ + year: Int! +} + +""" +A week of contributions in a user's contribution graph. +""" +type ContributionCalendarWeek { + """ + The days of contributions in this week. + """ + contributionDays: [ContributionCalendarDay!]! + + """ + The date of the earliest square in this week. + """ + firstDay: Date! +} + +""" +Varying levels of contributions from none to many. +""" +enum ContributionLevel { + """ + Lowest 25% of days of contributions. + """ + FIRST_QUARTILE + + """ + Highest 25% of days of contributions. More contributions than the third quartile. + """ + FOURTH_QUARTILE + + """ + No contributions occurred. + """ + NONE + + """ + Second lowest 25% of days of contributions. More contributions than the first quartile. + """ + SECOND_QUARTILE + + """ + Second highest 25% of days of contributions. More contributions than second quartile, less than the fourth quartile. + """ + THIRD_QUARTILE +} + +""" +Ordering options for contribution connections. +""" +input ContributionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +A contributions collection aggregates contributions such as opened issues and commits created by a user. +""" +type ContributionsCollection { + """ + Commit contributions made by the user, grouped by repository. + """ + commitContributionsByRepository( + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [CommitContributionsByRepository!]! + + """ + A calendar of this user's contributions on GitHub. + """ + contributionCalendar: ContributionCalendar! + + """ + The years the user has been making contributions with the most recent year first. + """ + contributionYears: [Int!]! + + """ + Determine if this collection's time span ends in the current month. + """ + doesEndInCurrentMonth: Boolean! + + """ + The date of the first restricted contribution the user made in this time + period. Can only be non-null when the user has enabled private contribution counts. + """ + earliestRestrictedContributionDate: Date + + """ + The ending date and time of this collection. + """ + endedAt: DateTime! + + """ + The first issue the user opened on GitHub. This will be null if that issue was + opened outside the collection's time range and ignoreTimeRange is false. If + the issue is not visible but the user has opted to show private contributions, + a RestrictedContribution will be returned. + """ + firstIssueContribution: CreatedIssueOrRestrictedContribution + + """ + The first pull request the user opened on GitHub. This will be null if that + pull request was opened outside the collection's time range and + ignoreTimeRange is not true. If the pull request is not visible but the user + has opted to show private contributions, a RestrictedContribution will be returned. + """ + firstPullRequestContribution: CreatedPullRequestOrRestrictedContribution + + """ + The first repository the user created on GitHub. This will be null if that + first repository was created outside the collection's time range and + ignoreTimeRange is false. If the repository is not visible, then a + RestrictedContribution is returned. + """ + firstRepositoryContribution: CreatedRepositoryOrRestrictedContribution + + """ + Does the user have any more activity in the timeline that occurred prior to the collection's time range? + """ + hasActivityInThePast: Boolean! + + """ + Determine if there are any contributions in this collection. + """ + hasAnyContributions: Boolean! + + """ + Determine if the user made any contributions in this time frame whose details + are not visible because they were made in a private repository. Can only be + true if the user enabled private contribution counts. + """ + hasAnyRestrictedContributions: Boolean! + + """ + Whether or not the collector's time span is all within the same day. + """ + isSingleDay: Boolean! + + """ + A list of issues the user opened. + """ + issueContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first issue ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from the result. + """ + excludePopular: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedIssueContributionConnection! + + """ + Issue contributions made by the user, grouped by repository. + """ + issueContributionsByRepository( + """ + Should the user's first issue ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from the result. + """ + excludePopular: Boolean = false + + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [IssueContributionsByRepository!]! + + """ + When the user signed up for GitHub. This will be null if that sign up date + falls outside the collection's time range and ignoreTimeRange is false. + """ + joinedGitHubContribution: JoinedGitHubContribution + + """ + The date of the most recent restricted contribution the user made in this time + period. Can only be non-null when the user has enabled private contribution counts. + """ + latestRestrictedContributionDate: Date + + """ + When this collection's time range does not include any activity from the user, use this + to get a different collection from an earlier time range that does have activity. + """ + mostRecentCollectionWithActivity: ContributionsCollection + + """ + Returns a different contributions collection from an earlier time range than this one + that does not have any contributions. + """ + mostRecentCollectionWithoutActivity: ContributionsCollection + + """ + The issue the user opened on GitHub that received the most comments in the specified + time frame. + """ + popularIssueContribution: CreatedIssueContribution + + """ + The pull request the user opened on GitHub that received the most comments in the + specified time frame. + """ + popularPullRequestContribution: CreatedPullRequestContribution + + """ + Pull request contributions made by the user. + """ + pullRequestContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first pull request ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from the result. + """ + excludePopular: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestContributionConnection! + + """ + Pull request contributions made by the user, grouped by repository. + """ + pullRequestContributionsByRepository( + """ + Should the user's first pull request ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from the result. + """ + excludePopular: Boolean = false + + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [PullRequestContributionsByRepository!]! + + """ + Pull request review contributions made by the user. Returns the most recently + submitted review for each PR reviewed by the user. + """ + pullRequestReviewContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestReviewContributionConnection! + + """ + Pull request review contributions made by the user, grouped by repository. + """ + pullRequestReviewContributionsByRepository( + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [PullRequestReviewContributionsByRepository!]! + + """ + A list of repositories owned by the user that the user created in this time range. + """ + repositoryContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first repository ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedRepositoryContributionConnection! + + """ + A count of contributions made by the user that the viewer cannot access. Only + non-zero when the user has chosen to share their private contribution counts. + """ + restrictedContributionsCount: Int! + + """ + The beginning date and time of this collection. + """ + startedAt: DateTime! + + """ + How many commits were made by the user in this time span. + """ + totalCommitContributions: Int! + + """ + How many issues the user opened. + """ + totalIssueContributions( + """ + Should the user's first issue ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many pull requests the user opened. + """ + totalPullRequestContributions( + """ + Should the user's first pull request ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many pull request reviews the user left. + """ + totalPullRequestReviewContributions: Int! + + """ + How many different repositories the user committed to. + """ + totalRepositoriesWithContributedCommits: Int! + + """ + How many different repositories the user opened issues in. + """ + totalRepositoriesWithContributedIssues( + """ + Should the user's first issue ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many different repositories the user left pull request reviews in. + """ + totalRepositoriesWithContributedPullRequestReviews: Int! + + """ + How many different repositories the user opened pull requests in. + """ + totalRepositoriesWithContributedPullRequests( + """ + Should the user's first pull request ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many repositories the user created. + """ + totalRepositoryContributions( + """ + Should the user's first repository ever be excluded from this count. + """ + excludeFirst: Boolean = false + ): Int! + + """ + The user who made the contributions in this collection. + """ + user: User! +} + +""" +Autogenerated input type of ConvertProjectCardNoteToIssue +""" +input ConvertProjectCardNoteToIssueInput { + """ + The body of the newly created issue. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectCard ID to convert. + """ + projectCardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + The ID of the repository to create the issue in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title of the newly created issue. Defaults to the card's note text. + """ + title: String +} + +""" +Autogenerated return type of ConvertProjectCardNoteToIssue +""" +type ConvertProjectCardNoteToIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Autogenerated input type of ConvertPullRequestToDraft +""" +input ConvertPullRequestToDraftInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to convert to draft + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of ConvertPullRequestToDraft +""" +type ConvertPullRequestToDraftPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is now a draft. + """ + pullRequest: PullRequest +} + +""" +Represents a 'convert_to_draft' event on a given pull request. +""" +type ConvertToDraftEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this convert to draft event. + """ + resourcePath: URI! + + """ + The HTTP URL for this convert to draft event. + """ + url: URI! +} + +""" +Represents a 'converted_note_to_issue' event on a given issue or pull request. +""" +type ConvertedNoteToIssueEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +Represents a 'converted_to_discussion' event on a given issue. +""" +type ConvertedToDiscussionEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The discussion that the issue was converted into. + """ + discussion: Discussion + id: ID! +} + +""" +Autogenerated input type of CreateBranchProtectionRule +""" +input CreateBranchProtectionRuleInput { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean + + """ + A list of User, Team, or App IDs allowed to bypass force push targeting matching branches. + """ + bypassForcePushActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass pull requests targeting matching branches. + """ + bypassPullRequestActorIds: [ID!] + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean + + """ + Whether users can pull changes from upstream when the branch is locked. Set to + `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String! + + """ + A list of User, Team, or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + The global relay id of the repository in which a new branch protection rule should be created in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + The list of required status checks + """ + requiredStatusChecks: [RequiredStatusCheckInput!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User, Team, or App IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] +} + +""" +Autogenerated return type of CreateBranchProtectionRule +""" +type CreateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckRun +""" +input CreateCheckRunInput { + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! + + """ + The name of the check. + """ + name: String! + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The current status. + """ + status: RequestableCheckStatusState +} + +""" +Autogenerated return type of CreateCheckRun +""" +type CreateCheckRunPayload { + """ + The newly created check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckSuite +""" +input CreateCheckSuiteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateCheckSuite +""" +type CreateCheckSuitePayload { + """ + The newly created check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCommitOnBranch +""" +input CreateCommitOnBranchInput { + """ + The Ref to be updated. Must be a branch. + """ + branch: CommittableBranch! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The git commit oid expected at the head of the branch prior to the commit + """ + expectedHeadOid: GitObjectID! + + """ + A description of changes to files in this commit. + """ + fileChanges: FileChanges + + """ + The commit message the be included with the commit. + """ + message: CommitMessage! +} + +""" +Autogenerated return type of CreateCommitOnBranch +""" +type CreateCommitOnBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new commit. + """ + commit: Commit + + """ + The ref which has been updated to point to the new commit. + """ + ref: Ref +} + +""" +Autogenerated input type of CreateDeployment +""" +input CreateDeploymentInput @preview(toggledBy: "flash-preview") { + """ + Attempt to automatically merge the default branch into the requested ref, defaults to true. + """ + autoMerge: Boolean = true + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Short description of the deployment. + """ + description: String = "" + + """ + Name for the target deployment environment. + """ + environment: String = "production" + + """ + JSON payload with extra information about the deployment. + """ + payload: String = "{}" + + """ + The node ID of the ref to be deployed. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The status contexts to verify against commit status checks. To bypass required + contexts, pass an empty array. Defaults to all unique contexts. + """ + requiredContexts: [String!] + + """ + Specifies a task to execute. + """ + task: String = "deploy" +} + +""" +Autogenerated return type of CreateDeployment +""" +type CreateDeploymentPayload @preview(toggledBy: "flash-preview") { + """ + True if the default branch has been auto-merged into the deployment ref. + """ + autoMerged: Boolean + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment. + """ + deployment: Deployment +} + +""" +Autogenerated input type of CreateDeploymentStatus +""" +input CreateDeploymentStatusInput @preview(toggledBy: "flash-preview") { + """ + Adds a new inactive status to all non-transient, non-production environment + deployments with the same repository and environment name as the created + status's deployment. + """ + autoInactive: Boolean = true + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the deployment. + """ + deploymentId: ID! @possibleTypes(concreteTypes: ["Deployment"]) + + """ + A short description of the status. Maximum length of 140 characters. + """ + description: String = "" + + """ + If provided, updates the environment of the deploy. Otherwise, does not modify the environment. + """ + environment: String + + """ + Sets the URL for accessing your environment. + """ + environmentUrl: String = "" + + """ + The log URL to associate with this status. This URL should contain + output to keep the user updated while the task is running or serve as + historical information for what happened in the deployment. + """ + logUrl: String = "" + + """ + The state of the deployment. + """ + state: DeploymentStatusState! +} + +""" +Autogenerated return type of CreateDeploymentStatus +""" +type CreateDeploymentStatusPayload @preview(toggledBy: "flash-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment status. + """ + deploymentStatus: DeploymentStatus +} + +""" +Autogenerated input type of CreateDiscussion +""" +input CreateDiscussionInput { + """ + The body of the discussion. + """ + body: String! + + """ + The id of the discussion category to associate with this discussion. + """ + categoryId: ID! @possibleTypes(concreteTypes: ["DiscussionCategory"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the repository on which to create the discussion. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title of the discussion. + """ + title: String! +} + +""" +Autogenerated return type of CreateDiscussion +""" +type CreateDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was just created. + """ + discussion: Discussion +} + +""" +Autogenerated input type of CreateEnterpriseOrganization +""" +input CreateEnterpriseOrganizationInput { + """ + The logins for the administrators of the new organization. + """ + adminLogins: [String!]! + + """ + The email used for sending billing receipts. + """ + billingEmail: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise owning the new organization. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the new organization. + """ + login: String! + + """ + The profile name of the new organization. + """ + profileName: String! +} + +""" +Autogenerated return type of CreateEnterpriseOrganization +""" +type CreateEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise that owns the created organization. + """ + enterprise: Enterprise + + """ + The organization that was created. + """ + organization: Organization +} + +""" +Autogenerated input type of CreateEnvironment +""" +input CreateEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the environment. + """ + name: String! + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateEnvironment +""" +type CreateEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new or existing environment. + """ + environment: Environment +} + +""" +Autogenerated input type of CreateIpAllowListEntry +""" +input CreateIpAllowListEntryInput { + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! + + """ + An optional name for the IP allow list entry. + """ + name: String + + """ + The ID of the owner for which to create the new IP allow list entry. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["App", "Enterprise", "Organization"], abstractType: "IpAllowListOwner") +} + +""" +Autogenerated return type of CreateIpAllowListEntry +""" +type CreateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was created. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of CreateIssue +""" +input CreateIssueInput { + """ + The Node ID for the user assignee for this issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body for the issue description. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of an issue template in the repository, assigns labels and assignees from the template to the issue + """ + issueTemplate: String + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] @possibleTypes(concreteTypes: ["Project"]) + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title for the issue. + """ + title: String! +} + +""" +Autogenerated return type of CreateIssue +""" +type CreateIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new issue. + """ + issue: Issue +} + +""" +Autogenerated input type of CreateLabel +""" +input CreateLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A 6 character hex code, without the leading #, identifying the color of the label. + """ + color: String! + + """ + A brief description of the label, such as its purpose. + """ + description: String + + """ + The name of the label. + """ + name: String! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateLabel +""" +type CreateLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new label. + """ + label: Label +} + +""" +Autogenerated input type of CreateLinkedBranch +""" +input CreateLinkedBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to link to. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + The name of the new branch. Defaults to issue number and title. + """ + name: String + + """ + The commit SHA to base the new branch on. + """ + oid: GitObjectID! + + """ + ID of the repository to create the branch in. Defaults to the issue repository. + """ + repositoryId: ID @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateLinkedBranch +""" +type CreateLinkedBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new branch issue reference. + """ + linkedBranch: LinkedBranch +} + +""" +Autogenerated input type of CreateMigrationSource +""" +input CreateMigrationSourceInput { + """ + The Octoshift migration source access token. + """ + accessToken: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The GitHub personal access token of the user importing to the target repository. + """ + githubPat: String + + """ + The Octoshift migration source name. + """ + name: String! + + """ + The ID of the organization that will own the Octoshift migration source. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The Octoshift migration source type. + """ + type: MigrationSourceType! + + """ + The Octoshift migration source URL. + """ + url: String! +} + +""" +Autogenerated return type of CreateMigrationSource +""" +type CreateMigrationSourcePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created Octoshift migration source. + """ + migrationSource: MigrationSource +} + +""" +Autogenerated input type of CreateProject +""" +input CreateProjectInput { + """ + The description of project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project. + """ + name: String! + + """ + The owner ID to create the project under. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization", "Repository", "User"], abstractType: "ProjectOwner") + + """ + A list of repository IDs to create as linked repositories for the project + """ + repositoryIds: [ID!] @possibleTypes(concreteTypes: ["Repository"]) + + """ + The name of the GitHub-provided template. + """ + template: ProjectTemplate +} + +""" +Autogenerated return type of CreateProject +""" +type CreateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new project. + """ + project: Project +} + +""" +Autogenerated input type of CreateProjectV2 +""" +input CreateProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner ID to create the project under. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "OrganizationOrUser") + + """ + The repository to link the project to. + """ + repositoryId: ID @possibleTypes(concreteTypes: ["Repository"]) + + """ + The team to link the project to. The team will be granted read permissions. + """ + teamId: ID @possibleTypes(concreteTypes: ["Team"]) + + """ + The title of the project. + """ + title: String! +} + +""" +Autogenerated return type of CreateProjectV2 +""" +type CreateProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of CreatePullRequest +""" +input CreatePullRequestInput { + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. You cannot update the base branch on a pull request to point + to another repository. + """ + baseRefName: String! + + """ + The contents of the pull request. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Indicates whether this pull request should be a draft. + """ + draft: Boolean = false + + """ + The name of the branch where your changes are implemented. For cross-repository pull requests + in the same network, namespace `head_ref_name` with a user like this: `username:branch`. + """ + headRefName: String! + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean = true + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title of the pull request. + """ + title: String! +} + +""" +Autogenerated return type of CreatePullRequest +""" +type CreatePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of CreateRef +""" +input CreateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The fully qualified name of the new Ref (ie: `refs/heads/my_new_branch`). + """ + name: String! + + """ + The GitObjectID that the new Ref shall target. Must point to a commit. + """ + oid: GitObjectID! + + """ + The Node ID of the Repository to create the Ref in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateRef +""" +type CreateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created ref. + """ + ref: Ref +} + +""" +Autogenerated input type of CreateRepository +""" +input CreateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A short description of the new repository. + """ + description: String + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean = true + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean = false + + """ + The URL for a web page about this repository. + """ + homepageUrl: URI + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "RepositoryOwner") + + """ + When an organization is specified as the owner, this ID identifies the team + that should be granted access to the new repository. + """ + teamId: ID @possibleTypes(concreteTypes: ["Team"]) + + """ + Whether this repository should be marked as a template such that anyone who + can access it can create new repositories with the same files and directory structure. + """ + template: Boolean = false + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Autogenerated return type of CreateRepository +""" +type CreateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +Autogenerated input type of CreateSponsorsTier +""" +input CreateSponsorsTierInput { + """ + The value of the new tier in US dollars. Valid values: 1-12000. + """ + amount: Int! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A description of what this tier is, what perks sponsors might receive, what a sponsorship at this tier means for you, etc. + """ + description: String! + + """ + Whether sponsorships using this tier should happen monthly/yearly or just once. + """ + isRecurring: Boolean = true + + """ + Whether to make the tier available immediately for sponsors to choose. + Defaults to creating a draft tier that will not be publicly visible. + """ + publish: Boolean = false + + """ + Optional ID of the private repository that sponsors at this tier should gain + read-only access to. Must be owned by an organization. + """ + repositoryId: ID @possibleTypes(concreteTypes: ["Repository"]) + + """ + Optional name of the private repository that sponsors at this tier should gain + read-only access to. Must be owned by an organization. Necessary if + repositoryOwnerLogin is given. Will be ignored if repositoryId is given. + """ + repositoryName: String + + """ + Optional login of the organization owner of the private repository that + sponsors at this tier should gain read-only access to. Necessary if + repositoryName is given. Will be ignored if repositoryId is given. + """ + repositoryOwnerLogin: String + + """ + The ID of the user or organization who owns the GitHub Sponsors profile. + Defaults to the current user if omitted and sponsorableLogin is not given. + """ + sponsorableId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsorable") + + """ + The username of the user or organization who owns the GitHub Sponsors profile. + Defaults to the current user if omitted and sponsorableId is not given. + """ + sponsorableLogin: String + + """ + Optional message new sponsors at this tier will receive. + """ + welcomeMessage: String +} + +""" +Autogenerated return type of CreateSponsorsTier +""" +type CreateSponsorsTierPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new tier. + """ + sponsorsTier: SponsorsTier +} + +""" +Autogenerated input type of CreateSponsorship +""" +input CreateSponsorshipInput { + """ + The amount to pay to the sponsorable in US dollars. Required if a tierId is not specified. Valid values: 1-12000. + """ + amount: Int + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether the sponsorship should happen monthly/yearly or just this one time. Required if a tierId is not specified. + """ + isRecurring: Boolean + + """ + Specify whether others should be able to see that the sponsor is sponsoring + the sponsorable. Public visibility still does not reveal which tier is used. + """ + privacyLevel: SponsorshipPrivacy = PUBLIC + + """ + Whether the sponsor should receive email updates from the sponsorable. + """ + receiveEmails: Boolean = true + + """ + The ID of the user or organization who is acting as the sponsor, paying for + the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsor") + + """ + The username of the user or organization who is acting as the sponsor, paying + for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsorable") + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String + + """ + The ID of one of sponsorable's existing tiers to sponsor at. Required if amount is not specified. + """ + tierId: ID @possibleTypes(concreteTypes: ["SponsorsTier"]) +} + +""" +Autogenerated return type of CreateSponsorship +""" +type CreateSponsorshipPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The sponsorship that was started. + """ + sponsorship: Sponsorship +} + +""" +Autogenerated input type of CreateTeamDiscussionComment +""" +input CreateTeamDiscussionCommentInput { + """ + The content of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the discussion to which the comment belongs. + """ + discussionId: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) +} + +""" +Autogenerated return type of CreateTeamDiscussionComment +""" +type CreateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new comment. + """ + teamDiscussionComment: TeamDiscussionComment +} + +""" +Autogenerated input type of CreateTeamDiscussion +""" +input CreateTeamDiscussionInput { + """ + The content of the discussion. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + If true, restricts the visibility of this discussion to team members and + organization admins. If false or not specified, allows any organization member + to view this discussion. + """ + private: Boolean + + """ + The ID of the team to which the discussion belongs. + """ + teamId: ID! @possibleTypes(concreteTypes: ["Team"]) + + """ + The title of the discussion. + """ + title: String! +} + +""" +Autogenerated return type of CreateTeamDiscussion +""" +type CreateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new discussion. + """ + teamDiscussion: TeamDiscussion +} + +""" +Represents the contribution a user made by committing to a repository. +""" +type CreatedCommitContribution implements Contribution { + """ + How many commits were made on this day to this repository by the user. + """ + commitCount: Int! + + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository the user made a commit in. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedCommitContribution. +""" +type CreatedCommitContributionConnection { + """ + A list of edges. + """ + edges: [CreatedCommitContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedCommitContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of commits across days and repositories in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedCommitContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedCommitContribution +} + +""" +Represents the contribution a user made on GitHub by opening an issue. +""" +type CreatedIssueContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + The issue that was opened. + """ + issue: Issue! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedIssueContribution. +""" +type CreatedIssueContributionConnection { + """ + A list of edges. + """ + edges: [CreatedIssueContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedIssueContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedIssueContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedIssueContribution +} + +""" +Represents either a issue the viewer can access or a restricted contribution. +""" +union CreatedIssueOrRestrictedContribution = CreatedIssueContribution | RestrictedContribution + +""" +Represents the contribution a user made on GitHub by opening a pull request. +""" +type CreatedPullRequestContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request that was opened. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestContribution. +""" +type CreatedPullRequestContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestContribution +} + +""" +Represents either a pull request the viewer can access or a restricted contribution. +""" +union CreatedPullRequestOrRestrictedContribution = CreatedPullRequestContribution | RestrictedContribution + +""" +Represents the contribution a user made by leaving a review on a pull request. +""" +type CreatedPullRequestReviewContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request the user reviewed. + """ + pullRequest: PullRequest! + + """ + The review the user left on the pull request. + """ + pullRequestReview: PullRequestReview! + + """ + The repository containing the pull request that the user reviewed. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestReviewContribution. +""" +type CreatedPullRequestReviewContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestReviewContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestReviewContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestReviewContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestReviewContribution +} + +""" +Represents the contribution a user made on GitHub by creating a repository. +""" +type CreatedRepositoryContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository that was created. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedRepositoryContribution. +""" +type CreatedRepositoryContributionConnection { + """ + A list of edges. + """ + edges: [CreatedRepositoryContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedRepositoryContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedRepositoryContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedRepositoryContribution +} + +""" +Represents either a repository the viewer can access or a restricted contribution. +""" +union CreatedRepositoryOrRestrictedContribution = CreatedRepositoryContribution | RestrictedContribution + +""" +Represents a mention made by one issue or pull request to another. +""" +type CrossReferencedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Identifies when the reference was made. + """ + referencedAt: DateTime! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request to which the reference was made. + """ + target: ReferencedSubject! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + Checks if the target will be closed when the source is merged. + """ + willCloseTarget: Boolean! +} + +""" +An ISO-8601 encoded date string. +""" +scalar Date + +""" +An ISO-8601 encoded UTC date string. +""" +scalar DateTime + +""" +Autogenerated input type of DeclineTopicSuggestion +""" +input DeclineTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the suggested topic. + """ + name: String! + + """ + The reason why the suggested topic is declined. + """ + reason: TopicSuggestionDeclineReason! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of DeclineTopicSuggestion +""" +type DeclineTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The declined topic. + """ + topic: Topic +} + +""" +The possible base permissions for repositories. +""" +enum DefaultRepositoryPermissionField { + """ + Can read, write, and administrate repos by default + """ + ADMIN + + """ + No access + """ + NONE + + """ + Can read repos by default + """ + READ + + """ + Can read and write repos by default + """ + WRITE +} + +""" +Entities that can be deleted. +""" +interface Deletable { + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! +} + +""" +Autogenerated input type of DeleteBranchProtectionRule +""" +input DeleteBranchProtectionRuleInput { + """ + The global relay id of the branch protection rule to be deleted. + """ + branchProtectionRuleId: ID! @possibleTypes(concreteTypes: ["BranchProtectionRule"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of DeleteBranchProtectionRule +""" +type DeleteBranchProtectionRulePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteDeployment +""" +input DeleteDeploymentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the deployment to be deleted. + """ + id: ID! @possibleTypes(concreteTypes: ["Deployment"]) +} + +""" +Autogenerated return type of DeleteDeployment +""" +type DeleteDeploymentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteDiscussionComment +""" +input DeleteDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node id of the discussion comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["DiscussionComment"]) +} + +""" +Autogenerated return type of DeleteDiscussionComment +""" +type DeleteDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion comment that was just deleted. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of DeleteDiscussion +""" +input DeleteDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the discussion to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["Discussion"]) +} + +""" +Autogenerated return type of DeleteDiscussion +""" +type DeleteDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was just deleted. + """ + discussion: Discussion +} + +""" +Autogenerated input type of DeleteEnvironment +""" +input DeleteEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the environment to be deleted. + """ + id: ID! @possibleTypes(concreteTypes: ["Environment"]) +} + +""" +Autogenerated return type of DeleteEnvironment +""" +type DeleteEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIpAllowListEntry +""" +input DeleteIpAllowListEntryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to delete. + """ + ipAllowListEntryId: ID! @possibleTypes(concreteTypes: ["IpAllowListEntry"]) +} + +""" +Autogenerated return type of DeleteIpAllowListEntry +""" +type DeleteIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was deleted. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of DeleteIssueComment +""" +input DeleteIssueCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["IssueComment"]) +} + +""" +Autogenerated return type of DeleteIssueComment +""" +type DeleteIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIssue +""" +input DeleteIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to delete. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of DeleteIssue +""" +type DeleteIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the issue belonged to + """ + repository: Repository +} + +""" +Autogenerated input type of DeleteLabel +""" +input DeleteLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the label to be deleted. + """ + id: ID! @possibleTypes(concreteTypes: ["Label"]) +} + +""" +Autogenerated return type of DeleteLabel +""" +type DeleteLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteLinkedBranch +""" +input DeleteLinkedBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the linked branch + """ + linkedBranchId: ID! @possibleTypes(concreteTypes: ["LinkedBranch"]) +} + +""" +Autogenerated return type of DeleteLinkedBranch +""" +type DeleteLinkedBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue the linked branch was unlinked from. + """ + issue: Issue +} + +""" +Autogenerated input type of DeletePackageVersion +""" +input DeletePackageVersionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the package version to be deleted. + """ + packageVersionId: ID! @possibleTypes(concreteTypes: ["PackageVersion"]) +} + +""" +Autogenerated return type of DeletePackageVersion +""" +type DeletePackageVersionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not the operation succeeded. + """ + success: Boolean +} + +""" +Autogenerated input type of DeleteProjectCard +""" +input DeleteProjectCardInput { + """ + The id of the card to delete. + """ + cardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of DeleteProjectCard +""" +type DeleteProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The column the deleted card was in. + """ + column: ProjectColumn + + """ + The deleted card ID. + """ + deletedCardId: ID +} + +""" +Autogenerated input type of DeleteProjectColumn +""" +input DeleteProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to delete. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of DeleteProjectColumn +""" +type DeleteProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted column ID. + """ + deletedColumnId: ID + + """ + The project the deleted column was in. + """ + project: Project +} + +""" +Autogenerated input type of DeleteProject +""" +input DeleteProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Project ID to update. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) +} + +""" +Autogenerated input type of DeleteProjectNextItem +""" +input DeleteProjectNextItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the item to be removed. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `itemId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + itemId: ID @possibleTypes(concreteTypes: ["ProjectNextItem"]) + + """ + The ID of the Project from which the item should be removed. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `projectId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + projectId: ID @possibleTypes(concreteTypes: ["ProjectNext"]) +} + +""" +Autogenerated return type of DeleteProjectNextItem +""" +type DeleteProjectNextItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the deleted item. + """ + deletedItemId: ID + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Autogenerated return type of DeleteProject +""" +type DeleteProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository or organization the project was removed from. + """ + owner: ProjectOwner +} + +""" +Autogenerated input type of DeleteProjectV2Item +""" +input DeleteProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the item to be removed. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project from which the item should be removed. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of DeleteProjectV2Item +""" +type DeleteProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the deleted item. + """ + deletedItemId: ID +} + +""" +Autogenerated input type of DeletePullRequestReviewComment +""" +input DeletePullRequestReviewCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) +} + +""" +Autogenerated return type of DeletePullRequestReviewComment +""" +type DeletePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request review the deleted comment belonged to. + """ + pullRequestReview: PullRequestReview + + """ + The deleted pull request review comment. + """ + pullRequestReviewComment: PullRequestReviewComment +} + +""" +Autogenerated input type of DeletePullRequestReview +""" +input DeletePullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to delete. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of DeletePullRequestReview +""" +type DeletePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of DeleteRef +""" +input DeleteRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Ref to be deleted. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) +} + +""" +Autogenerated return type of DeleteRef +""" +type DeleteRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussionComment +""" +input DeleteTeamDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussionComment"]) +} + +""" +Autogenerated return type of DeleteTeamDiscussionComment +""" +type DeleteTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussion +""" +input DeleteTeamDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion ID to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) +} + +""" +Autogenerated return type of DeleteTeamDiscussion +""" +type DeleteTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteVerifiableDomain +""" +input DeleteVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of DeleteVerifiableDomain +""" +type DeleteVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owning account from which the domain was deleted. + """ + owner: VerifiableDomainOwner +} + +""" +Represents a 'demilestoned' event on a given issue or pull request. +""" +type DemilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the milestone title associated with the 'demilestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +A Dependabot Update for a dependency in a repository +""" +type DependabotUpdate implements RepositoryNode { + """ + The error from a dependency update + """ + error: DependabotUpdateError + + """ + The associated pull request + """ + pullRequest: PullRequest + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +An error produced from a Dependabot Update +""" +type DependabotUpdateError { + """ + The body of the error + """ + body: String! + + """ + The error code + """ + errorType: String! + + """ + The title of the error + """ + title: String! +} + +""" +A dependency manifest entry +""" +type DependencyGraphDependency @preview(toggledBy: "hawkgirl-preview") { + """ + Does the dependency itself have dependencies? + """ + hasDependencies: Boolean! + + """ + The original name of the package, as it appears in the manifest. + """ + packageLabel: String! + @deprecated( + reason: "`packageLabel` will be removed. Use normalized `packageName` field instead. Removal on 2022-10-01 UTC." + ) + + """ + The dependency package manager + """ + packageManager: String + + """ + The name of the package in the canonical form used by the package manager. + This may differ from the original textual form (see packageLabel), for example + in a package manager that uses case-insensitive comparisons. + """ + packageName: String! + + """ + The repository containing the package + """ + repository: Repository + + """ + The dependency version requirements + """ + requirements: String! +} + +""" +The connection type for DependencyGraphDependency. +""" +type DependencyGraphDependencyConnection @preview(toggledBy: "hawkgirl-preview") { + """ + A list of edges. + """ + edges: [DependencyGraphDependencyEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphDependency] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphDependencyEdge @preview(toggledBy: "hawkgirl-preview") { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphDependency +} + +""" +The possible ecosystems of a dependency graph package. +""" +enum DependencyGraphEcosystem { + """ + GitHub Actions + """ + ACTIONS + + """ + PHP packages hosted at packagist.org + """ + COMPOSER + + """ + Go modules + """ + GO + + """ + Java artifacts hosted at the Maven central repository + """ + MAVEN + + """ + JavaScript packages hosted at npmjs.com + """ + NPM + + """ + .NET packages hosted at the NuGet Gallery + """ + NUGET + + """ + Python packages hosted at PyPI.org + """ + PIP + + """ + Dart packages hosted at pub.dev + """ + PUB + + """ + Ruby gems hosted at RubyGems.org + """ + RUBYGEMS + + """ + Rust crates + """ + RUST +} + +""" +Dependency manifest for a repository +""" +type DependencyGraphManifest implements Node @preview(toggledBy: "hawkgirl-preview") { + """ + Path to view the manifest file blob + """ + blobPath: String! + + """ + A list of manifest dependencies + """ + dependencies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DependencyGraphDependencyConnection + + """ + The number of dependencies listed in the manifest + """ + dependenciesCount: Int + + """ + Is the manifest too big to parse? + """ + exceedsMaxSize: Boolean! + + """ + Fully qualified manifest filename + """ + filename: String! + id: ID! + + """ + Were we able to parse the manifest? + """ + parseable: Boolean! + + """ + The repository containing the manifest + """ + repository: Repository! +} + +""" +The connection type for DependencyGraphManifest. +""" +type DependencyGraphManifestConnection @preview(toggledBy: "hawkgirl-preview") { + """ + A list of edges. + """ + edges: [DependencyGraphManifestEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphManifest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphManifestEdge @preview(toggledBy: "hawkgirl-preview") { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphManifest +} + +""" +A repository deploy key. +""" +type DeployKey implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The deploy key. + """ + key: String! + + """ + Whether or not the deploy key is read only. + """ + readOnly: Boolean! + + """ + The deploy key title. + """ + title: String! + + """ + Whether or not the deploy key has been verified. + """ + verified: Boolean! +} + +""" +The connection type for DeployKey. +""" +type DeployKeyConnection { + """ + A list of edges. + """ + edges: [DeployKeyEdge] + + """ + A list of nodes. + """ + nodes: [DeployKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeployKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeployKey +} + +""" +Represents a 'deployed' event on a given pull request. +""" +type DeployedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment associated with the 'deployed' event. + """ + deployment: Deployment! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The ref associated with the 'deployed' event. + """ + ref: Ref +} + +""" +Represents triggered deployment instance. +""" +type Deployment implements Node { + """ + Identifies the commit sha of the deployment. + """ + commit: Commit + + """ + Identifies the oid of the deployment commit, even if the commit has been deleted. + """ + commitOid: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment description. + """ + description: String + + """ + The latest environment to which this deployment was made. + """ + environment: String + id: ID! + + """ + The latest environment to which this deployment was made. + """ + latestEnvironment: String + + """ + The latest status of this deployment. + """ + latestStatus: DeploymentStatus + + """ + The original environment to which this deployment was made. + """ + originalEnvironment: String + + """ + Extra information that a deployment system might need. + """ + payload: String + + """ + Identifies the Ref of the deployment, if the deployment was created by ref. + """ + ref: Ref + + """ + Identifies the repository associated with the deployment. + """ + repository: Repository! + + """ + The current state of the deployment. + """ + state: DeploymentState + + """ + A list of statuses associated with the deployment. + """ + statuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentStatusConnection + + """ + The deployment task. + """ + task: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for Deployment. +""" +type DeploymentConnection { + """ + A list of edges. + """ + edges: [DeploymentEdge] + + """ + A list of nodes. + """ + nodes: [Deployment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Deployment +} + +""" +Represents a 'deployment_environment_changed' event on a given pull request. +""" +type DeploymentEnvironmentChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The deployment status that updated the deployment environment. + """ + deploymentStatus: DeploymentStatus! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Ordering options for deployment connections +""" +input DeploymentOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order deployments by. + """ + field: DeploymentOrderField! +} + +""" +Properties by which deployment connections can be ordered. +""" +enum DeploymentOrderField { + """ + Order collection by creation time + """ + CREATED_AT +} + +""" +A protection rule. +""" +type DeploymentProtectionRule { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The teams or users that can review the deployment + """ + reviewers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentReviewerConnection! + + """ + The timeout in minutes for this protection rule. + """ + timeout: Int! + + """ + The type of protection rule. + """ + type: DeploymentProtectionRuleType! +} + +""" +The connection type for DeploymentProtectionRule. +""" +type DeploymentProtectionRuleConnection { + """ + A list of edges. + """ + edges: [DeploymentProtectionRuleEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentProtectionRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentProtectionRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentProtectionRule +} + +""" +The possible protection rule types. +""" +enum DeploymentProtectionRuleType { + """ + Required reviewers + """ + REQUIRED_REVIEWERS + + """ + Wait timer + """ + WAIT_TIMER +} + +""" +A request to deploy a workflow run to an environment. +""" +type DeploymentRequest { + """ + Whether or not the current user can approve the deployment + """ + currentUserCanApprove: Boolean! + + """ + The target environment of the deployment + """ + environment: Environment! + + """ + The teams or users that can review the deployment + """ + reviewers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentReviewerConnection! + + """ + The wait timer in minutes configured in the environment + """ + waitTimer: Int! + + """ + The wait timer in minutes configured in the environment + """ + waitTimerStartedAt: DateTime +} + +""" +The connection type for DeploymentRequest. +""" +type DeploymentRequestConnection { + """ + A list of edges. + """ + edges: [DeploymentRequestEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentRequest +} + +""" +A deployment review. +""" +type DeploymentReview implements Node { + """ + The comment the user left. + """ + comment: String! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The environments approved or rejected + """ + environments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): EnvironmentConnection! + id: ID! + + """ + The decision of the user. + """ + state: DeploymentReviewState! + + """ + The user that reviewed the deployment. + """ + user: User! +} + +""" +The connection type for DeploymentReview. +""" +type DeploymentReviewConnection { + """ + A list of edges. + """ + edges: [DeploymentReviewEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentReview] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentReviewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentReview +} + +""" +The possible states for a deployment review. +""" +enum DeploymentReviewState { + """ + The deployment was approved. + """ + APPROVED + + """ + The deployment was rejected. + """ + REJECTED +} + +""" +Users and teams. +""" +union DeploymentReviewer = Team | User + +""" +The connection type for DeploymentReviewer. +""" +type DeploymentReviewerConnection { + """ + A list of edges. + """ + edges: [DeploymentReviewerEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentReviewer] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentReviewerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentReviewer +} + +""" +The possible states in which a deployment can be. +""" +enum DeploymentState { + """ + The pending deployment was not updated after 30 minutes. + """ + ABANDONED + + """ + The deployment is currently active. + """ + ACTIVE + + """ + An inactive transient deployment. + """ + DESTROYED + + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is pending. + """ + PENDING + + """ + The deployment has queued + """ + QUEUED + + """ + The deployment is waiting. + """ + WAITING +} + +""" +Describes the status of a given deployment attempt. +""" +type DeploymentStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the deployment associated with status. + """ + deployment: Deployment! + + """ + Identifies the description of the deployment. + """ + description: String + + """ + Identifies the environment of the deployment at the time of this deployment status + """ + environment: String @preview(toggledBy: "flash-preview") + + """ + Identifies the environment URL of the deployment. + """ + environmentUrl: URI + id: ID! + + """ + Identifies the log URL of the deployment. + """ + logUrl: URI + + """ + Identifies the current state of the deployment. + """ + state: DeploymentStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for DeploymentStatus. +""" +type DeploymentStatusConnection { + """ + A list of edges. + """ + edges: [DeploymentStatusEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentStatus +} + +""" +The possible states for a deployment status. +""" +enum DeploymentStatusState { + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is pending. + """ + PENDING + + """ + The deployment is queued + """ + QUEUED + + """ + The deployment was successful. + """ + SUCCESS + + """ + The deployment is waiting. + """ + WAITING +} + +""" +The possible sides of a diff. +""" +enum DiffSide { + """ + The left side of the diff. + """ + LEFT + + """ + The right side of the diff. + """ + RIGHT +} + +""" +Autogenerated input type of DisablePullRequestAutoMerge +""" +input DisablePullRequestAutoMergeInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to disable auto merge on. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of DisablePullRequestAutoMerge +""" +type DisablePullRequestAutoMergePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request auto merge was disabled on. + """ + pullRequest: PullRequest +} + +""" +Represents a 'disconnected' event on a given issue or pull request. +""" +type DisconnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request from which the issue was disconnected. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was disconnected. + """ + subject: ReferencedSubject! +} + +""" +A discussion in a repository. +""" +type Discussion implements Comment & Deletable & Labelable & Lockable & Node & Reactable & RepositoryNode & Subscribable & Updatable & Votable { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + The comment chosen as this discussion's answer, if any. + """ + answer: DiscussionComment + + """ + The time when a user chose this discussion's answer, if answered. + """ + answerChosenAt: DateTime + + """ + The user who chose this discussion's answer, if answered. + """ + answerChosenBy: Actor + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The main text of the discussion post. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + The category for this discussion. + """ + category: DiscussionCategory! + + """ + The replies to the discussion. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DiscussionCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + `true` if the object is locked + """ + locked: Boolean! + + """ + The number identifying this discussion within the repository. + """ + number: Int! + + """ + The poll associated with this discussion, if one exists. + """ + poll: DiscussionPoll + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The path for this discussion. + """ + resourcePath: URI! + + """ + The title of this discussion. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + The URL for this discussion. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A category for discussions in a repository. +""" +type DiscussionCategory implements Node & RepositoryNode { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + A description of this category. + """ + description: String + + """ + An emoji representing this category. + """ + emoji: String! + + """ + This category's emoji rendered as HTML. + """ + emojiHTML: HTML! + id: ID! + + """ + Whether or not discussions in this category support choosing an answer with the markDiscussionCommentAsAnswer mutation. + """ + isAnswerable: Boolean! + + """ + The name of this category. + """ + name: String! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The slug of this category. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for DiscussionCategory. +""" +type DiscussionCategoryConnection { + """ + A list of edges. + """ + edges: [DiscussionCategoryEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionCategory] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionCategoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionCategory +} + +""" +A comment on a discussion. +""" +type DiscussionComment implements Comment & Deletable & Minimizable & Node & Reactable & Updatable & UpdatableComment & Votable { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The time when this replied-to comment was deleted + """ + deletedAt: DateTime + + """ + The discussion this comment was created in + """ + discussion: Discussion + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Has this comment been chosen as the answer of its discussion? + """ + isAnswer: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The threaded replies to this comment. + """ + replies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DiscussionCommentConnection! + + """ + The discussion comment this comment is a reply to + """ + replyTo: DiscussionComment + + """ + The path for this discussion comment. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + The URL for this discussion comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can the current user mark this comment as an answer? + """ + viewerCanMarkAsAnswer: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Can the current user unmark this comment as an answer? + """ + viewerCanUnmarkAsAnswer: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! +} + +""" +The connection type for DiscussionComment. +""" +type DiscussionCommentConnection { + """ + A list of edges. + """ + edges: [DiscussionCommentEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionComment +} + +""" +The connection type for Discussion. +""" +type DiscussionConnection { + """ + A list of edges. + """ + edges: [DiscussionEdge] + + """ + A list of nodes. + """ + nodes: [Discussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Discussion +} + +""" +Ways in which lists of discussions can be ordered upon return. +""" +input DiscussionOrder { + """ + The direction in which to order discussions by the specified field. + """ + direction: OrderDirection! + + """ + The field by which to order discussions. + """ + field: DiscussionOrderField! +} + +""" +Properties by which discussion connections can be ordered. +""" +enum DiscussionOrderField { + """ + Order discussions by creation time. + """ + CREATED_AT + + """ + Order discussions by most recent modification time. + """ + UPDATED_AT +} + +""" +A poll for a discussion. +""" +type DiscussionPoll implements Node { + """ + The discussion that this poll belongs to. + """ + discussion: Discussion + id: ID! + + """ + The options for this poll. + """ + options( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the options for the discussion poll. + """ + orderBy: DiscussionPollOptionOrder = {field: AUTHORED_ORDER, direction: ASC} + ): DiscussionPollOptionConnection + + """ + The question that is being asked by this poll. + """ + question: String! + + """ + The total number of votes that have been cast for this poll. + """ + totalVoteCount: Int! + + """ + Indicates if the viewer has permission to vote in this poll. + """ + viewerCanVote: Boolean! + + """ + Indicates if the viewer has voted for any option in this poll. + """ + viewerHasVoted: Boolean! +} + +""" +An option for a discussion poll. +""" +type DiscussionPollOption implements Node { + id: ID! + + """ + The text for this option. + """ + option: String! + + """ + The discussion poll that this option belongs to. + """ + poll: DiscussionPoll + + """ + The total number of votes that have been cast for this option. + """ + totalVoteCount: Int! + + """ + Indicates if the viewer has voted for this option in the poll. + """ + viewerHasVoted: Boolean! +} + +""" +The connection type for DiscussionPollOption. +""" +type DiscussionPollOptionConnection { + """ + A list of edges. + """ + edges: [DiscussionPollOptionEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionPollOption] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionPollOptionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionPollOption +} + +""" +Ordering options for discussion poll option connections. +""" +input DiscussionPollOptionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order poll options by. + """ + field: DiscussionPollOptionOrderField! +} + +""" +Properties by which discussion poll option connections can be ordered. +""" +enum DiscussionPollOptionOrderField { + """ + Order poll options by the order that the poll author specified when creating the poll. + """ + AUTHORED_ORDER + + """ + Order poll options by the number of votes it has. + """ + VOTE_COUNT +} + +""" +Autogenerated input type of DismissPullRequestReview +""" +input DismissPullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The contents of the pull request review dismissal message. + """ + message: String! + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of DismissPullRequestReview +""" +type DismissPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The dismissed pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +The possible reasons that a Dependabot alert was dismissed. +""" +enum DismissReason { + """ + A fix has already been started + """ + FIX_STARTED + + """ + This alert is inaccurate or incorrect + """ + INACCURATE + + """ + Vulnerable code is not actually used + """ + NOT_USED + + """ + No bandwidth to fix this + """ + NO_BANDWIDTH + + """ + Risk is tolerable to this project + """ + TOLERABLE_RISK +} + +""" +Autogenerated input type of DismissRepositoryVulnerabilityAlert +""" +input DismissRepositoryVulnerabilityAlertInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reason the Dependabot alert is being dismissed. + """ + dismissReason: DismissReason! + + """ + The Dependabot alert ID to dismiss. + """ + repositoryVulnerabilityAlertId: ID! @possibleTypes(concreteTypes: ["RepositoryVulnerabilityAlert"]) +} + +""" +Autogenerated return type of DismissRepositoryVulnerabilityAlert +""" +type DismissRepositoryVulnerabilityAlertPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Dependabot alert that was dismissed + """ + repositoryVulnerabilityAlert: RepositoryVulnerabilityAlert +} + +""" +A draft issue within a project. +""" +type DraftIssue implements Node { + """ + A list of users to assigned to this draft issue. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The body of the draft issue. + """ + body: String! + + """ + The body of the draft issue rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body of the draft issue rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this draft issue. + """ + creator: Actor + id: ID! + + """ + The project (beta) that contains this draft issue. + """ + project: ProjectNext! + + """ + The project (beta) item that wraps this draft issue. + """ + projectItem: ProjectNextItem! + + """ + List of items linked with the draft issue (currently draft issue can be linked to only one item). + """ + projectV2Items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2ItemConnection! + + """ + Projects that link to this draft issue (currently draft issue can be linked to only one project). + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2Connection! + + """ + The title of the draft issue + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Specifies a review comment to be left with a Pull Request Review. +""" +input DraftPullRequestReviewComment { + """ + Body of the comment to leave. + """ + body: String! + + """ + Path to the file being commented on. + """ + path: String! + + """ + Position in the file to leave a comment on. + """ + position: Int! +} + +""" +Specifies a review comment thread to be left with a Pull Request Review. +""" +input DraftPullRequestReviewThread { + """ + Body of the comment to leave. + """ + body: String! + + """ + The line of the blob to which the thread refers. The end of the line range for multi-line comments. + """ + line: Int! + + """ + Path to the file being commented on. + """ + path: String! + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT +} + +""" +Autogenerated input type of EnablePullRequestAutoMerge +""" +input EnablePullRequestAutoMergeInput { + """ + The email address to associate with this merge. + """ + authorEmail: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Commit body to use for the commit when the PR is mergable; if omitted, a + default message will be used. NOTE: when merging with a merge queue any input + value for commit message is ignored. + """ + commitBody: String + + """ + Commit headline to use for the commit when the PR is mergable; if omitted, a + default message will be used. NOTE: when merging with a merge queue any input + value for commit headline is ignored. + """ + commitHeadline: String + + """ + The merge method to use. If omitted, defaults to `MERGE`. NOTE: when merging + with a merge queue any input value for merge method is ignored. + """ + mergeMethod: PullRequestMergeMethod = MERGE + + """ + ID of the pull request to enable auto-merge on. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of EnablePullRequestAutoMerge +""" +type EnablePullRequestAutoMergePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request auto-merge was enabled on. + """ + pullRequest: PullRequest +} + +""" +An account to manage multiple organizations with consolidated policy and billing. +""" +type Enterprise implements Node { + """ + A URL pointing to the enterprise's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Enterprise billing information visible to enterprise billing managers. + """ + billingInfo: EnterpriseBillingInfo + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the enterprise. + """ + description: String + + """ + The description of the enterprise as HTML. + """ + descriptionHTML: HTML! + id: ID! + + """ + The location of the enterprise. + """ + location: String + + """ + A list of users who are members of this enterprise. + """ + members( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Only return members within the selected GitHub Enterprise deployment + """ + deployment: EnterpriseUserDeployment + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Only return members with this two-factor authentication status. Does not + include members who only have an account on a GitHub Enterprise Server instance. + """ + hasTwoFactorEnabled: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for members returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + Only return members within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + + """ + The role of the user in the enterprise organization or server. + """ + role: EnterpriseUserAccountMembershipRole + ): EnterpriseMemberConnection! + + """ + The name of the enterprise. + """ + name: String! + + """ + A list of organizations that belong to this enterprise. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations returned from the connection. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + The viewer's role in an organization. + """ + viewerOrganizationRole: RoleInOrganization + ): OrganizationConnection! + + """ + Enterprise information only visible to enterprise owners. + """ + ownerInfo: EnterpriseOwnerInfo + + """ + The HTTP path for this enterprise. + """ + resourcePath: URI! + + """ + The URL-friendly identifier for the enterprise. + """ + slug: String! + + """ + The HTTP URL for this enterprise. + """ + url: URI! + + """ + Is the current viewer an admin of this enterprise? + """ + viewerIsAdmin: Boolean! + + """ + The URL of the enterprise website. + """ + websiteUrl: URI +} + +""" +The connection type for User. +""" +type EnterpriseAdministratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an administrator of an enterprise. +""" +type EnterpriseAdministratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +An invitation for a user to become an owner or billing manager of an enterprise. +""" +type EnterpriseAdministratorInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email of the person who was invited to the enterprise. + """ + email: String + + """ + The enterprise the invitation is for. + """ + enterprise: Enterprise! + id: ID! + + """ + The user who was invited to the enterprise. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User + + """ + The invitee's pending role in the enterprise (owner or billing_manager). + """ + role: EnterpriseAdministratorRole! +} + +""" +The connection type for EnterpriseAdministratorInvitation. +""" +type EnterpriseAdministratorInvitationConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorInvitationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseAdministratorInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseAdministratorInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseAdministratorInvitation +} + +""" +Ordering options for enterprise administrator invitation connections +""" +input EnterpriseAdministratorInvitationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order enterprise administrator invitations by. + """ + field: EnterpriseAdministratorInvitationOrderField! +} + +""" +Properties by which enterprise administrator invitation connections can be ordered. +""" +enum EnterpriseAdministratorInvitationOrderField { + """ + Order enterprise administrator member invitations by creation time + """ + CREATED_AT +} + +""" +The possible administrator roles in an enterprise account. +""" +enum EnterpriseAdministratorRole { + """ + Represents a billing manager of the enterprise account. + """ + BILLING_MANAGER + + """ + Represents an owner of the enterprise account. + """ + OWNER +} + +""" +The possible values for the enterprise allow private repository forking policy value. +""" +enum EnterpriseAllowPrivateRepositoryForkingPolicyValue { + """ + Members can fork a repository to an organization within this enterprise. + """ + ENTERPRISE_ORGANIZATIONS + + """ + Members can fork a repository to their enterprise-managed user account or an organization inside this enterprise. + """ + ENTERPRISE_ORGANIZATIONS_USER_ACCOUNTS + + """ + Members can fork a repository to their user account or an organization, either inside or outside of this enterprise. + """ + EVERYWHERE + + """ + Members can fork a repository only within the same organization (intra-org). + """ + SAME_ORGANIZATION + + """ + Members can fork a repository to their user account or within the same organization. + """ + SAME_ORGANIZATION_USER_ACCOUNTS + + """ + Members can fork a repository to their user account. + """ + USER_ACCOUNTS +} + +""" +Metadata for an audit entry containing enterprise account information. +""" +interface EnterpriseAuditEntryData { + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI +} + +""" +Enterprise billing information visible to enterprise billing managers and owners. +""" +type EnterpriseBillingInfo { + """ + The number of licenseable users/emails across the enterprise. + """ + allLicensableUsersCount: Int! + + """ + The number of data packs used by all organizations owned by the enterprise. + """ + assetPacks: Int! + + """ + The bandwidth quota in GB for all organizations owned by the enterprise. + """ + bandwidthQuota: Float! + + """ + The bandwidth usage in GB for all organizations owned by the enterprise. + """ + bandwidthUsage: Float! + + """ + The bandwidth usage as a percentage of the bandwidth quota. + """ + bandwidthUsagePercentage: Int! + + """ + The storage quota in GB for all organizations owned by the enterprise. + """ + storageQuota: Float! + + """ + The storage usage in GB for all organizations owned by the enterprise. + """ + storageUsage: Float! + + """ + The storage usage as a percentage of the storage quota. + """ + storageUsagePercentage: Int! + + """ + The number of available licenses across all owned organizations based on the unique number of billable users. + """ + totalAvailableLicenses: Int! + + """ + The total number of licenses allocated. + """ + totalLicenses: Int! +} + +""" +The possible values for the enterprise base repository permission setting. +""" +enum EnterpriseDefaultRepositoryPermissionSettingValue { + """ + Organization members will be able to clone, pull, push, and add new collaborators to all organization repositories. + """ + ADMIN + + """ + Organization members will only be able to clone and pull public repositories. + """ + NONE + + """ + Organizations in the enterprise choose base repository permissions for their members. + """ + NO_POLICY + + """ + Organization members will be able to clone and pull all organization repositories. + """ + READ + + """ + Organization members will be able to clone, pull, and push all organization repositories. + """ + WRITE +} + +""" +The possible values for an enabled/disabled enterprise setting. +""" +enum EnterpriseEnabledDisabledSettingValue { + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED + + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +The possible values for an enabled/no policy enterprise setting. +""" +enum EnterpriseEnabledSettingValue { + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +An identity provider configured to provision identities for an enterprise. +""" +type EnterpriseIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm + + """ + The enterprise this identity provider belongs to. + """ + enterprise: Enterprise + + """ + ExternalIdentities provisioned by this identity provider. + """ + externalIdentities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter to external identities with the users login + """ + login: String + + """ + Filter to external identities with valid org membership only + """ + membersOnly: Boolean + + """ + Filter to external identities with the users userName/NameID attribute + """ + userName: String + ): ExternalIdentityConnection! + id: ID! + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML identity provider. + """ + issuer: String + + """ + Recovery codes that can be used by admins to access the enterprise if the identity provider is unavailable. + """ + recoveryCodes: [String!] + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An object that is a member of an enterprise. +""" +union EnterpriseMember = EnterpriseUserAccount | User + +""" +The connection type for EnterpriseMember. +""" +type EnterpriseMemberConnection { + """ + A list of edges. + """ + edges: [EnterpriseMemberEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseMember] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is a member of an enterprise through one or more organizations. +""" +type EnterpriseMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseMember +} + +""" +Ordering options for enterprise member connections. +""" +input EnterpriseMemberOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order enterprise members by. + """ + field: EnterpriseMemberOrderField! +} + +""" +Properties by which enterprise member connections can be ordered. +""" +enum EnterpriseMemberOrderField { + """ + Order enterprise members by creation time + """ + CREATED_AT + + """ + Order enterprise members by login + """ + LOGIN +} + +""" +The possible values for the enterprise members can create repositories setting. +""" +enum EnterpriseMembersCanCreateRepositoriesSettingValue { + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will not be able to create public or private repositories. + """ + DISABLED + + """ + Organization administrators choose whether to allow members to create repositories. + """ + NO_POLICY + + """ + Members will be able to create only private repositories. + """ + PRIVATE + + """ + Members will be able to create only public repositories. + """ + PUBLIC +} + +""" +The possible values for the members can make purchases setting. +""" +enum EnterpriseMembersCanMakePurchasesSettingValue { + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED + + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED +} + +""" +The connection type for Organization. +""" +type EnterpriseOrganizationMembershipConnection { + """ + A list of edges. + """ + edges: [EnterpriseOrganizationMembershipEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An enterprise organization that a user is a member of. +""" +type EnterpriseOrganizationMembershipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization + + """ + The role of the user in the enterprise membership. + """ + role: EnterpriseUserAccountMembershipRole! +} + +""" +The connection type for User. +""" +type EnterpriseOutsideCollaboratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseOutsideCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an outside collaborator of an enterprise through one or more organizations. +""" +type EnterpriseOutsideCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The enterprise organization repositories this user is a member of. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories. + """ + orderBy: RepositoryOrder = {field: NAME, direction: ASC} + ): EnterpriseRepositoryInfoConnection! +} + +""" +Enterprise information only visible to enterprise owners. +""" +type EnterpriseOwnerInfo { + """ + A list of all of the administrators for this enterprise. + """ + admins( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Only return administrators with this two-factor authentication status. + """ + hasTwoFactorEnabled: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for administrators returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + Only return members within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + + """ + The role to filter by. + """ + role: EnterpriseAdministratorRole + ): EnterpriseAdministratorConnection! + + """ + A list of users in the enterprise who currently have two-factor authentication disabled. + """ + affiliatedUsersWithTwoFactorDisabled( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + Whether or not affiliated users with two-factor authentication disabled exist in the enterprise. + """ + affiliatedUsersWithTwoFactorDisabledExist: Boolean! + + """ + The setting value for whether private repository forking is enabled for repositories in organizations in this enterprise. + """ + allowPrivateRepositoryForkingSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided private repository forking setting value. + """ + allowPrivateRepositoryForkingSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The value for the allow private repository forking policy on the enterprise. + """ + allowPrivateRepositoryForkingSettingPolicyValue: EnterpriseAllowPrivateRepositoryForkingPolicyValue + + """ + The setting value for base repository permissions for organizations in this enterprise. + """ + defaultRepositoryPermissionSetting: EnterpriseDefaultRepositoryPermissionSettingValue! + + """ + A list of enterprise organizations configured with the provided base repository permission. + """ + defaultRepositoryPermissionSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The permission to find organizations for. + """ + value: DefaultRepositoryPermissionField! + ): OrganizationConnection! + + """ + A list of domains owned by the enterprise. + """ + domains( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter whether or not the domain is approved. + """ + isApproved: Boolean = null + + """ + Filter whether or not the domain is verified. + """ + isVerified: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for verifiable domains returned. + """ + orderBy: VerifiableDomainOrder = {field: DOMAIN, direction: ASC} + ): VerifiableDomainConnection! + + """ + Enterprise Server installations owned by the enterprise. + """ + enterpriseServerInstallations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Whether or not to only return installations discovered via GitHub Connect. + """ + connectedOnly: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server installations returned. + """ + orderBy: EnterpriseServerInstallationOrder = {field: HOST_NAME, direction: ASC} + ): EnterpriseServerInstallationConnection! + + """ + The setting value for whether the enterprise has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the enterprise. + """ + ipAllowListEntries( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for IP allow list entries returned. + """ + orderBy: IpAllowListEntryOrder = {field: ALLOW_LIST_VALUE, direction: ASC} + ): IpAllowListEntryConnection! + + """ + The setting value for whether the enterprise has IP allow list configuration for installed GitHub Apps enabled. + """ + ipAllowListForInstalledAppsEnabledSetting: IpAllowListForInstalledAppsEnabledSettingValue! + + """ + Whether or not the base repository permission is currently being updated. + """ + isUpdatingDefaultRepositoryPermission: Boolean! + + """ + Whether the two-factor authentication requirement is currently being enforced. + """ + isUpdatingTwoFactorRequirement: Boolean! + + """ + The setting value for whether organization members with admin permissions on a + repository can change repository visibility. + """ + membersCanChangeRepositoryVisibilitySetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided can change repository visibility setting value. + """ + membersCanChangeRepositoryVisibilitySettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can create internal repositories. + """ + membersCanCreateInternalRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create private repositories. + """ + membersCanCreatePrivateRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create public repositories. + """ + membersCanCreatePublicRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create repositories. + """ + membersCanCreateRepositoriesSetting: EnterpriseMembersCanCreateRepositoriesSettingValue + + """ + A list of enterprise organizations configured with the provided repository creation setting value. + """ + membersCanCreateRepositoriesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting to find organizations for. + """ + value: OrganizationMembersCanCreateRepositoriesSettingValue! + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete issues. + """ + membersCanDeleteIssuesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete issues setting value. + """ + membersCanDeleteIssuesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete or transfer repositories. + """ + membersCanDeleteRepositoriesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete repositories setting value. + """ + membersCanDeleteRepositoriesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can invite outside collaborators. + """ + membersCanInviteCollaboratorsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can invite collaborators setting value. + """ + membersCanInviteCollaboratorsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + Indicates whether members of this enterprise's organizations can purchase additional services for those organizations. + """ + membersCanMakePurchasesSetting: EnterpriseMembersCanMakePurchasesSettingValue! + + """ + The setting value for whether members with admin permissions for repositories can update protected branches. + """ + membersCanUpdateProtectedBranchesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can update protected branches setting value. + """ + membersCanUpdateProtectedBranchesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members can view dependency insights. + """ + membersCanViewDependencyInsightsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can view dependency insights setting value. + """ + membersCanViewDependencyInsightsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + Indicates if email notification delivery for this enterprise is restricted to verified or approved domains. + """ + notificationDeliveryRestrictionEnabledSetting: NotificationRestrictionSettingValue! + + """ + The OIDC Identity Provider for the enterprise. + """ + oidcProvider: OIDCProvider + + """ + The setting value for whether organization projects are enabled for organizations in this enterprise. + """ + organizationProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided organization projects setting value. + """ + organizationProjectsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + A list of outside collaborators across the repositories in the enterprise. + """ + outsideCollaborators( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Only return outside collaborators with this two-factor authentication status. + """ + hasTwoFactorEnabled: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The login of one specific outside collaborator. + """ + login: String + + """ + Ordering options for outside collaborators returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + Only return outside collaborators within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + + """ + Only return outside collaborators on repositories with this visibility. + """ + visibility: RepositoryVisibility + ): EnterpriseOutsideCollaboratorConnection! + + """ + A list of pending administrator invitations for the enterprise. + """ + pendingAdminInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pending enterprise administrator invitations returned from the connection. + """ + orderBy: EnterpriseAdministratorInvitationOrder = {field: CREATED_AT, direction: DESC} + + """ + The search string to look for. + """ + query: String + + """ + The role to filter by. + """ + role: EnterpriseAdministratorRole + ): EnterpriseAdministratorInvitationConnection! + + """ + A list of pending collaborator invitations across the repositories in the enterprise. + """ + pendingCollaboratorInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pending repository collaborator invitations returned from the connection. + """ + orderBy: RepositoryInvitationOrder = {field: CREATED_AT, direction: DESC} + + """ + The search string to look for. + """ + query: String + ): RepositoryInvitationConnection! + + """ + A list of pending member invitations for organizations in the enterprise. + """ + pendingMemberInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Only return invitations within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + ): EnterprisePendingMemberInvitationConnection! + + """ + The setting value for whether repository projects are enabled in this enterprise. + """ + repositoryProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided repository projects setting value. + """ + repositoryProjectsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The SAML Identity Provider for the enterprise. When used by a GitHub App, + requires an installation token with read and write access to members. + """ + samlIdentityProvider: EnterpriseIdentityProvider + + """ + A list of enterprise organizations configured with the SAML single sign-on setting value. + """ + samlIdentityProviderSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: IdentityProviderConfigurationState! + ): OrganizationConnection! + + """ + A list of members with a support entitlement. + """ + supportEntitlements( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for support entitlement users returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + ): EnterpriseMemberConnection! + + """ + The setting value for whether team discussions are enabled for organizations in this enterprise. + """ + teamDiscussionsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided team discussions setting value. + """ + teamDiscussionsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether the enterprise requires two-factor authentication for its organizations and users. + """ + twoFactorRequiredSetting: EnterpriseEnabledSettingValue! + + """ + A list of enterprise organizations configured with the two-factor authentication setting value. + """ + twoFactorRequiredSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! +} + +""" +The connection type for OrganizationInvitation. +""" +type EnterprisePendingMemberInvitationConnection { + """ + A list of edges. + """ + edges: [EnterprisePendingMemberInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the total count of unique users in the connection. + """ + totalUniqueUserCount: Int! +} + +""" +An invitation to be a member in an enterprise organization. +""" +type EnterprisePendingMemberInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +A subset of repository information queryable from an enterprise. +""" +type EnterpriseRepositoryInfo implements Node { + id: ID! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + The repository's name. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! +} + +""" +The connection type for EnterpriseRepositoryInfo. +""" +type EnterpriseRepositoryInfoConnection { + """ + A list of edges. + """ + edges: [EnterpriseRepositoryInfoEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseRepositoryInfo] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseRepositoryInfoEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseRepositoryInfo +} + +""" +An Enterprise Server installation. +""" +type EnterpriseServerInstallation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The customer name to which the Enterprise Server installation belongs. + """ + customerName: String! + + """ + The host name of the Enterprise Server installation. + """ + hostName: String! + id: ID! + + """ + Whether or not the installation is connected to an Enterprise Server installation via GitHub Connect. + """ + isConnected: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + User accounts on this Enterprise Server installation. + """ + userAccounts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user accounts returned from the connection. + """ + orderBy: EnterpriseServerUserAccountOrder = {field: LOGIN, direction: ASC} + ): EnterpriseServerUserAccountConnection! + + """ + User accounts uploads for the Enterprise Server installation. + """ + userAccountsUploads( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user accounts uploads returned from the connection. + """ + orderBy: EnterpriseServerUserAccountsUploadOrder = {field: CREATED_AT, direction: DESC} + ): EnterpriseServerUserAccountsUploadConnection! +} + +""" +The connection type for EnterpriseServerInstallation. +""" +type EnterpriseServerInstallationConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerInstallationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerInstallation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerInstallationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerInstallation +} + +""" +Ordering options for Enterprise Server installation connections. +""" +input EnterpriseServerInstallationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order Enterprise Server installations by. + """ + field: EnterpriseServerInstallationOrderField! +} + +""" +Properties by which Enterprise Server installation connections can be ordered. +""" +enum EnterpriseServerInstallationOrderField { + """ + Order Enterprise Server installations by creation time + """ + CREATED_AT + + """ + Order Enterprise Server installations by customer name + """ + CUSTOMER_NAME + + """ + Order Enterprise Server installations by host name + """ + HOST_NAME +} + +""" +A user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccount implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + User emails belonging to this user account. + """ + emails( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user account emails returned from the connection. + """ + orderBy: EnterpriseServerUserAccountEmailOrder = {field: EMAIL, direction: ASC} + ): EnterpriseServerUserAccountEmailConnection! + + """ + The Enterprise Server installation on which this user account exists. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + id: ID! + + """ + Whether the user account is a site administrator on the Enterprise Server installation. + """ + isSiteAdmin: Boolean! + + """ + The login of the user account on the Enterprise Server installation. + """ + login: String! + + """ + The profile name of the user account on the Enterprise Server installation. + """ + profileName: String + + """ + The date and time when the user account was created on the Enterprise Server installation. + """ + remoteCreatedAt: DateTime! + + """ + The ID of the user account on the Enterprise Server installation. + """ + remoteUserId: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccount. +""" +type EnterpriseServerUserAccountConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccount] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccount +} + +""" +An email belonging to a user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccountEmail implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address. + """ + email: String! + id: ID! + + """ + Indicates whether this is the primary email of the associated user account. + """ + isPrimary: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user account to which the email belongs. + """ + userAccount: EnterpriseServerUserAccount! +} + +""" +The connection type for EnterpriseServerUserAccountEmail. +""" +type EnterpriseServerUserAccountEmailConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEmailEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountEmail] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEmailEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountEmail +} + +""" +Ordering options for Enterprise Server user account email connections. +""" +input EnterpriseServerUserAccountEmailOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order emails by. + """ + field: EnterpriseServerUserAccountEmailOrderField! +} + +""" +Properties by which Enterprise Server user account email connections can be ordered. +""" +enum EnterpriseServerUserAccountEmailOrderField { + """ + Order emails by email + """ + EMAIL +} + +""" +Ordering options for Enterprise Server user account connections. +""" +input EnterpriseServerUserAccountOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user accounts by. + """ + field: EnterpriseServerUserAccountOrderField! +} + +""" +Properties by which Enterprise Server user account connections can be ordered. +""" +enum EnterpriseServerUserAccountOrderField { + """ + Order user accounts by login + """ + LOGIN + + """ + Order user accounts by creation time on the Enterprise Server installation + """ + REMOTE_CREATED_AT +} + +""" +A user accounts upload from an Enterprise Server installation. +""" +type EnterpriseServerUserAccountsUpload implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise to which this upload belongs. + """ + enterprise: Enterprise! + + """ + The Enterprise Server installation for which this upload was generated. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + id: ID! + + """ + The name of the file uploaded. + """ + name: String! + + """ + The synchronization state of the upload + """ + syncState: EnterpriseServerUserAccountsUploadSyncState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccountsUpload. +""" +type EnterpriseServerUserAccountsUploadConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountsUploadEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountsUpload] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountsUploadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountsUpload +} + +""" +Ordering options for Enterprise Server user accounts upload connections. +""" +input EnterpriseServerUserAccountsUploadOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user accounts uploads by. + """ + field: EnterpriseServerUserAccountsUploadOrderField! +} + +""" +Properties by which Enterprise Server user accounts upload connections can be ordered. +""" +enum EnterpriseServerUserAccountsUploadOrderField { + """ + Order user accounts uploads by creation time + """ + CREATED_AT +} + +""" +Synchronization state of the Enterprise Server user accounts upload +""" +enum EnterpriseServerUserAccountsUploadSyncState { + """ + The synchronization of the upload failed. + """ + FAILURE + + """ + The synchronization of the upload is pending. + """ + PENDING + + """ + The synchronization of the upload succeeded. + """ + SUCCESS +} + +""" +An account for a user who is an admin of an enterprise or a member of an enterprise through one or more organizations. +""" +type EnterpriseUserAccount implements Actor & Node { + """ + A URL pointing to the enterprise user account's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise in which this user account exists. + """ + enterprise: Enterprise! + id: ID! + + """ + An identifier for the enterprise user account, a login or email address + """ + login: String! + + """ + The name of the enterprise user account + """ + name: String + + """ + A list of enterprise organizations this user is a member of. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations returned from the connection. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + The role of the user in the enterprise organization. + """ + role: EnterpriseUserAccountMembershipRole + ): EnterpriseOrganizationMembershipConnection! + + """ + The HTTP path for this user. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user. + """ + url: URI! + + """ + The user within the enterprise. + """ + user: User +} + +""" +The possible roles for enterprise membership. +""" +enum EnterpriseUserAccountMembershipRole { + """ + The user is a member of an organization in the enterprise. + """ + MEMBER + + """ + The user is an owner of an organization in the enterprise. + """ + OWNER + + """ + The user is not an owner of the enterprise, and not a member or owner of any + organizations in the enterprise; only for EMU-enabled enterprises. + """ + UNAFFILIATED +} + +""" +The possible GitHub Enterprise deployments where this user can exist. +""" +enum EnterpriseUserDeployment { + """ + The user is part of a GitHub Enterprise Cloud deployment. + """ + CLOUD + + """ + The user is part of a GitHub Enterprise Server deployment. + """ + SERVER +} + +""" +An environment. +""" +type Environment implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The name of the environment + """ + name: String! + + """ + The protection rules defined for this environment + """ + protectionRules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentProtectionRuleConnection! +} + +""" +The connection type for Environment. +""" +type EnvironmentConnection { + """ + A list of edges. + """ + edges: [EnvironmentEdge] + + """ + A list of nodes. + """ + nodes: [Environment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnvironmentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Environment +} + +""" +An external identity provisioned by SAML SSO or SCIM. +""" +type ExternalIdentity implements Node { + """ + The GUID for this identity + """ + guid: String! + id: ID! + + """ + Organization invitation for this SCIM-provisioned external identity + """ + organizationInvitation: OrganizationInvitation + + """ + SAML Identity attributes + """ + samlIdentity: ExternalIdentitySamlAttributes + + """ + SCIM Identity attributes + """ + scimIdentity: ExternalIdentityScimAttributes + + """ + User linked to this external identity. Will be NULL if this identity has not been claimed by an organization member. + """ + user: User +} + +""" +An attribute for the External Identity attributes collection +""" +type ExternalIdentityAttribute { + """ + The attribute metadata as JSON + """ + metadata: String + + """ + The attribute name + """ + name: String! + + """ + The attribute value + """ + value: String! +} + +""" +The connection type for ExternalIdentity. +""" +type ExternalIdentityConnection { + """ + A list of edges. + """ + edges: [ExternalIdentityEdge] + + """ + A list of nodes. + """ + nodes: [ExternalIdentity] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ExternalIdentityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ExternalIdentity +} + +""" +SAML attributes for the External Identity +""" +type ExternalIdentitySamlAttributes { + """ + SAML Identity attributes + """ + attributes: [ExternalIdentityAttribute!]! + + """ + The emails associated with the SAML identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SAML identity + """ + familyName: String + + """ + Given name of the SAML identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The NameID of the SAML identity + """ + nameId: String + + """ + The userName of the SAML identity + """ + username: String +} + +""" +SCIM attributes for the External Identity +""" +type ExternalIdentityScimAttributes { + """ + The emails associated with the SCIM identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SCIM identity + """ + familyName: String + + """ + Given name of the SCIM identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The userName of the SCIM identity + """ + username: String +} + +""" +A command to add a file at the given path with the given contents as part of a +commit. Any existing file at that that path will be replaced. +""" +input FileAddition { + """ + The base64 encoded contents of the file + """ + contents: Base64String! + + """ + The path in the repository where the file will be located + """ + path: String! +} + +""" +A description of a set of changes to a file tree to be made as part of +a git commit, modeled as zero or more file `additions` and zero or more +file `deletions`. + +Both fields are optional; omitting both will produce a commit with no +file changes. + +`deletions` and `additions` describe changes to files identified +by their path in the git tree using unix-style path separators, i.e. +`/`. The root of a git tree is an empty string, so paths are not +slash-prefixed. + +`path` values must be unique across all `additions` and `deletions` +provided. Any duplication will result in a validation error. + +### Encoding + +File contents must be provided in full for each `FileAddition`. + +The `contents` of a `FileAddition` must be encoded using RFC 4648 +compliant base64, i.e. correct padding is required and no characters +outside the standard alphabet may be used. Invalid base64 +encoding will be rejected with a validation error. + +The encoded contents may be binary. + +For text files, no assumptions are made about the character encoding of +the file contents (after base64 decoding). No charset transcoding or +line-ending normalization will be performed; it is the client's +responsibility to manage the character encoding of files they provide. +However, for maximum compatibility we recommend using UTF-8 encoding +and ensuring that all files in a repository use a consistent +line-ending convention (`\n` or `\r\n`), and that all files end +with a newline. + +### Modeling file changes + +Each of the the five types of conceptual changes that can be made in a +git commit can be described using the `FileChanges` type as follows: + +1. New file addition: create file `hello world\n` at path `docs/README.txt`: + + { + "additions" [ + { + "path": "docs/README.txt", + "contents": base64encode("hello world\n") + } + ] + } + +2. Existing file modification: change existing `docs/README.txt` to have new + content `new content here\n`: + + { + "additions" [ + { + "path": "docs/README.txt", + "contents": base64encode("new content here\n") + } + ] + } + +3. Existing file deletion: remove existing file `docs/README.txt`. + Note that the path is required to exist -- specifying a + path that does not exist on the given branch will abort the + commit and return an error. + + { + "deletions" [ + { + "path": "docs/README.txt" + } + ] + } + + +4. File rename with no changes: rename `docs/README.txt` with + previous content `hello world\n` to the same content at + `newdocs/README.txt`: + + { + "deletions" [ + { + "path": "docs/README.txt", + } + ], + "additions" [ + { + "path": "newdocs/README.txt", + "contents": base64encode("hello world\n") + } + ] + } + + +5. File rename with changes: rename `docs/README.txt` with + previous content `hello world\n` to a file at path + `newdocs/README.txt` with content `new contents\n`: + + { + "deletions" [ + { + "path": "docs/README.txt", + } + ], + "additions" [ + { + "path": "newdocs/README.txt", + "contents": base64encode("new contents\n") + } + ] + } +""" +input FileChanges { + """ + File to add or change. + """ + additions: [FileAddition!] = [] + + """ + Files to delete. + """ + deletions: [FileDeletion!] = [] +} + +""" +A command to delete the file at the given path as part of a commit. +""" +input FileDeletion { + """ + The path to delete + """ + path: String! +} + +""" +The possible viewed states of a file . +""" +enum FileViewedState { + """ + The file has new changes since last viewed. + """ + DISMISSED + + """ + The file has not been marked as viewed. + """ + UNVIEWED + + """ + The file has been marked as viewed. + """ + VIEWED +} + +""" +Autogenerated input type of FollowOrganization +""" +input FollowOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the organization to follow. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of FollowOrganization +""" +type FollowOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that was followed. + """ + organization: Organization +} + +""" +Autogenerated input type of FollowUser +""" +input FollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to follow. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of FollowUser +""" +type FollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was followed. + """ + user: User +} + +""" +The connection type for User. +""" +type FollowerConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The connection type for User. +""" +type FollowingConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A funding platform link for a repository. +""" +type FundingLink { + """ + The funding platform this link is for. + """ + platform: FundingPlatform! + + """ + The configured URL for this funding link. + """ + url: URI! +} + +""" +The possible funding platforms for repository funding links. +""" +enum FundingPlatform { + """ + Community Bridge funding platform. + """ + COMMUNITY_BRIDGE + + """ + Custom funding platform. + """ + CUSTOM + + """ + GitHub funding platform. + """ + GITHUB + + """ + IssueHunt funding platform. + """ + ISSUEHUNT + + """ + Ko-fi funding platform. + """ + KO_FI + + """ + LFX Crowdfunding funding platform. + """ + LFX_CROWDFUNDING + + """ + Liberapay funding platform. + """ + LIBERAPAY + + """ + Open Collective funding platform. + """ + OPEN_COLLECTIVE + + """ + Otechie funding platform. + """ + OTECHIE + + """ + Patreon funding platform. + """ + PATREON + + """ + Tidelift funding platform. + """ + TIDELIFT +} + +""" +A generic hovercard context with a message and icon +""" +type GenericHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +A Gist. +""" +type Gist implements Node & Starrable & UniformResourceLocatable { + """ + A list of comments associated with the gist + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GistCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The gist description. + """ + description: String + + """ + The files in this gist. + """ + files( + """ + The maximum number of files to return. + """ + limit: Int = 10 + + """ + The oid of the files to return + """ + oid: GitObjectID + ): [GistFile] + + """ + A list of forks associated with the gist + """ + forks( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for gists returned from the connection + """ + orderBy: GistOrder + ): GistConnection! + id: ID! + + """ + Identifies if the gist is a fork. + """ + isFork: Boolean! + + """ + Whether the gist is public or not. + """ + isPublic: Boolean! + + """ + The gist name. + """ + name: String! + + """ + The gist owner. + """ + owner: RepositoryOwner + + """ + Identifies when the gist was last pushed to. + """ + pushedAt: DateTime + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this Gist. + """ + url: URI! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Represents a comment on an Gist. +""" +type GistComment implements Comment & Deletable & Minimizable & Node & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the gist. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The associated gist. + """ + gist: Gist! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for GistComment. +""" +type GistCommentConnection { + """ + A list of edges. + """ + edges: [GistCommentEdge] + + """ + A list of nodes. + """ + nodes: [GistComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GistComment +} + +""" +The connection type for Gist. +""" +type GistConnection { + """ + A list of edges. + """ + edges: [GistEdge] + + """ + A list of nodes. + """ + nodes: [Gist] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Gist +} + +""" +A file in a gist. +""" +type GistFile { + """ + The file name encoded to remove characters that are invalid in URL paths. + """ + encodedName: String + + """ + The gist file encoding. + """ + encoding: String + + """ + The file extension from the file name. + """ + extension: String + + """ + Indicates if this file is an image. + """ + isImage: Boolean! + + """ + Whether the file's contents were truncated. + """ + isTruncated: Boolean! + + """ + The programming language this file is written in. + """ + language: Language + + """ + The gist file name. + """ + name: String + + """ + The gist file size in bytes. + """ + size: Int + + """ + UTF8 text data or null if the file is binary + """ + text( + """ + Optionally truncate the returned file to this length. + """ + truncate: Int + ): String +} + +""" +Ordering options for gist connections +""" +input GistOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: GistOrderField! +} + +""" +Properties by which gist connections can be ordered. +""" +enum GistOrderField { + """ + Order gists by creation time + """ + CREATED_AT + + """ + Order gists by push time + """ + PUSHED_AT + + """ + Order gists by update time + """ + UPDATED_AT +} + +""" +The privacy of a Gist +""" +enum GistPrivacy { + """ + Gists that are public and secret + """ + ALL + + """ + Public + """ + PUBLIC + + """ + Secret + """ + SECRET +} + +""" +Represents an actor in a Git commit (ie. an author or committer). +""" +type GitActor { + """ + A URL pointing to the author's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The timestamp of the Git action (authoring or committing). + """ + date: GitTimestamp + + """ + The email in the Git commit. + """ + email: String + + """ + The name in the Git commit. + """ + name: String + + """ + The GitHub user corresponding to the email field. Null if no such user exists. + """ + user: User +} + +""" +The connection type for GitActor. +""" +type GitActorConnection { + """ + A list of edges. + """ + edges: [GitActorEdge] + + """ + A list of nodes. + """ + nodes: [GitActor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GitActorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GitActor +} + +""" +Represents information about the GitHub instance. +""" +type GitHubMetadata { + """ + Returns a String that's a SHA of `github-services` + """ + gitHubServicesSha: GitObjectID! + + """ + IP addresses that users connect to for git operations + """ + gitIpAddresses: [String!] + + """ + IP addresses that service hooks are sent from + """ + hookIpAddresses: [String!] + + """ + IP addresses that the importer connects from + """ + importerIpAddresses: [String!] + + """ + Whether or not users are verified + """ + isPasswordAuthenticationVerifiable: Boolean! + + """ + IP addresses for GitHub Pages' A records + """ + pagesIpAddresses: [String!] +} + +""" +Represents a Git object. +""" +interface GitObject { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +A Git object ID. +""" +scalar GitObjectID + +""" +A fully qualified reference name (e.g. `refs/heads/master`). +""" +scalar GitRefname @preview(toggledBy: "update-refs-preview") + +""" +Git SSH string +""" +scalar GitSSHRemote + +""" +Information about a signature (GPG or S/MIME) on a Commit or Tag. +""" +interface GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +The state of a Git signature. +""" +enum GitSignatureState { + """ + The signing certificate or its chain could not be verified + """ + BAD_CERT + + """ + Invalid email used for signing + """ + BAD_EMAIL + + """ + Signing key expired + """ + EXPIRED_KEY + + """ + Internal error - the GPG verification service misbehaved + """ + GPGVERIFY_ERROR + + """ + Internal error - the GPG verification service is unavailable at the moment + """ + GPGVERIFY_UNAVAILABLE + + """ + Invalid signature + """ + INVALID + + """ + Malformed signature + """ + MALFORMED_SIG + + """ + The usage flags for the key that signed this don't allow signing + """ + NOT_SIGNING_KEY + + """ + Email used for signing not known to GitHub + """ + NO_USER + + """ + Valid signature, though certificate revocation check failed + """ + OCSP_ERROR + + """ + Valid signature, pending certificate revocation checking + """ + OCSP_PENDING + + """ + One or more certificates in chain has been revoked + """ + OCSP_REVOKED + + """ + Key used for signing not known to GitHub + """ + UNKNOWN_KEY + + """ + Unknown signature type + """ + UNKNOWN_SIG_TYPE + + """ + Unsigned + """ + UNSIGNED + + """ + Email used for signing unverified on GitHub + """ + UNVERIFIED_EMAIL + + """ + Valid signature and verified by GitHub + """ + VALID +} + +""" +An ISO-8601 encoded date string. Unlike the DateTime type, GitTimestamp is not converted in UTC. +""" +scalar GitTimestamp + +""" +Represents a GPG signature on a Commit or Tag. +""" +type GpgSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Hex-encoded ID of the key that signed this object. + """ + keyId: String + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Autogenerated input type of GrantEnterpriseOrganizationsMigratorRole +""" +input GrantEnterpriseOrganizationsMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which all organizations managed by it will be granted the migrator role. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the user to grant the migrator role + """ + login: String! +} + +""" +Autogenerated return type of GrantEnterpriseOrganizationsMigratorRole +""" +type GrantEnterpriseOrganizationsMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organizations that had the migrator role applied to for the given user. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection +} + +""" +Autogenerated input type of GrantMigratorRole +""" +input GrantMigratorRoleInput { + """ + The user login or Team slug to grant the migrator role. + """ + actor: String! + + """ + Specifies the type of the actor, can be either USER or TEAM. + """ + actorType: ActorType! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that the user/team belongs to. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of GrantMigratorRole +""" +type GrantMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +A string containing HTML code. +""" +scalar HTML + +""" +Represents a 'head_ref_deleted' event on a given pull request. +""" +type HeadRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the Ref associated with the `head_ref_deleted` event. + """ + headRef: Ref + + """ + Identifies the name of the Ref associated with the `head_ref_deleted` event. + """ + headRefName: String! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'head_ref_force_pushed' event on a given pull request. +""" +type HeadRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'head_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'head_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'head_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents a 'head_ref_restored' event on a given pull request. +""" +type HeadRefRestoredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Detail needed to display a hovercard for a user +""" +type Hovercard { + """ + Each of the contexts for this hovercard + """ + contexts: [HovercardContext!]! +} + +""" +An individual line of a hovercard +""" +interface HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +The possible states in which authentication can be configured with an identity provider. +""" +enum IdentityProviderConfigurationState { + """ + Authentication with an identity provider is configured but not enforced. + """ + CONFIGURED + + """ + Authentication with an identity provider is configured and enforced. + """ + ENFORCED + + """ + Authentication with an identity provider is not configured. + """ + UNCONFIGURED +} + +""" +Autogenerated input type of ImportProject +""" +input ImportProjectInput { + """ + The description of Project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A list of columns containing issues and pull requests. + """ + columnImports: [ProjectColumnImport!]! + + """ + The name of Project. + """ + name: String! + + """ + The name of the Organization or User to create the Project under. + """ + ownerName: String! + + """ + Whether the Project is public or not. + """ + public: Boolean = false +} + +""" +Autogenerated return type of ImportProject +""" +type ImportProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new Project! + """ + project: Project +} + +""" +Autogenerated input type of InviteEnterpriseAdmin +""" +input InviteEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The email of the person to invite as an administrator. + """ + email: String + + """ + The ID of the enterprise to which you want to invite an administrator. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a user to invite as an administrator. + """ + invitee: String + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole +} + +""" +Autogenerated return type of InviteEnterpriseAdmin +""" +type InviteEnterpriseAdminPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created enterprise administrator invitation. + """ + invitation: EnterpriseAdministratorInvitation +} + +""" +The possible values for the IP allow list enabled setting. +""" +enum IpAllowListEnabledSettingValue { + """ + The setting is disabled for the owner. + """ + DISABLED + + """ + The setting is enabled for the owner. + """ + ENABLED +} + +""" +An IP address or range of addresses that is allowed to access an owner's resources. +""" +type IpAllowListEntry implements Node { + """ + A single IP address or range of IP addresses in CIDR notation. + """ + allowListValue: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Whether the entry is currently active. + """ + isActive: Boolean! + + """ + The name of the IP allow list entry. + """ + name: String + + """ + The owner of the IP allow list entry. + """ + owner: IpAllowListOwner! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for IpAllowListEntry. +""" +type IpAllowListEntryConnection { + """ + A list of edges. + """ + edges: [IpAllowListEntryEdge] + + """ + A list of nodes. + """ + nodes: [IpAllowListEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IpAllowListEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IpAllowListEntry +} + +""" +Ordering options for IP allow list entry connections. +""" +input IpAllowListEntryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order IP allow list entries by. + """ + field: IpAllowListEntryOrderField! +} + +""" +Properties by which IP allow list entry connections can be ordered. +""" +enum IpAllowListEntryOrderField { + """ + Order IP allow list entries by the allow list value. + """ + ALLOW_LIST_VALUE + + """ + Order IP allow list entries by creation time. + """ + CREATED_AT +} + +""" +The possible values for the IP allow list configuration for installed GitHub Apps setting. +""" +enum IpAllowListForInstalledAppsEnabledSettingValue { + """ + The setting is disabled for the owner. + """ + DISABLED + + """ + The setting is enabled for the owner. + """ + ENABLED +} + +""" +Types that can own an IP allow list. +""" +union IpAllowListOwner = App | Enterprise | Organization + +""" +An Issue is a place to discuss ideas, enhancements, tasks, and bugs for a project. +""" +type Issue implements Assignable & Closable & Comment & Labelable & Lockable & Node & ProjectNextOwner & ProjectV2Owner & Reactable & RepositoryNode & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the body of the issue. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The http path for this issue body + """ + bodyResourcePath: URI! + + """ + Identifies the body of the issue rendered to text. + """ + bodyText: String! + + """ + The http URL for this issue body + """ + bodyUrl: URI! + + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + A list of comments associated with the Issue. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The hovercard information for this issue + """ + hovercard( + """ + Whether or not to include notification contexts + """ + includeNotificationContexts: Boolean = true + ): Hovercard! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Indicates whether or not this issue is currently pinned to the repository issues list + """ + isPinned: Boolean + + """ + Is this issue read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Branches linked to this issue. + """ + linkedBranches( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): LinkedBranchConnection! + + """ + `true` if the object is locked + """ + locked: Boolean! + + """ + Identifies the milestone associated with the issue. + """ + milestone: Milestone + + """ + Identifies the issue number. + """ + number: Int! + + """ + A list of Users that are participating in the Issue conversation. + """ + participants( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + List of project cards associated with this issue. + """ + projectCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + List of project items associated with this issue. + """ + projectItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Include archived items. + """ + includeArchived: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2ItemConnection! + + """ + Find a project by project (beta) number. + """ + projectNext( + """ + The project (beta) number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + List of project (beta) items associated with this issue. + """ + projectNextItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Include archived items. + """ + includeArchived: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectNextItemConnection! + + """ + Find a project by number. + """ + projectV2( + """ + The project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects (beta) under the owner. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for under the the owner. + """ + query: String + + """ + How to order the returned projects (beta). + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + A list of projects under the owner. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for under the the owner. + """ + query: String + ): ProjectV2Connection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + Identifies the state of the issue. + """ + state: IssueState! + + """ + Identifies the reason for the issue state. + """ + stateReason: IssueStateReason + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timeline( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows filtering timeline events by a `since` timestamp. + """ + since: DateTime + ): IssueTimelineConnection! + @deprecated(reason: "`timeline` will be removed Use Issue.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timelineItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter timeline items by type. + """ + itemTypes: [IssueTimelineItemsItemType!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter timeline items by a `since` timestamp. + """ + since: DateTime + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): IssueTimelineItemsConnection! + + """ + Identifies the issue title. + """ + title: String! + + """ + Identifies the issue title rendered to HTML. + """ + titleHTML: String! + + """ + A list of issues that track this issue + """ + trackedInIssues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): IssueConnection! + + """ + A list of issues tracked inside the current issue + """ + trackedIssues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): IssueConnection! + + """ + The number of tracked issues for this issue + """ + trackedIssuesCount( + """ + Limit the count to tracked issues with the specified states. + """ + states: [TrackedIssueStates] + ): Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +The possible state reasons of a closed issue. +""" +enum IssueClosedStateReason { + """ + An issue that has been closed as completed + """ + COMPLETED + + """ + An issue that has been closed as not planned + """ + NOT_PLANNED +} + +""" +Represents a comment on an Issue. +""" +type IssueComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Identifies the issue associated with the comment. + """ + issue: Issue! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Returns the pull request associated with the comment, if this comment was made on a + pull request. + """ + pullRequest: PullRequest + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue comment + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue comment + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for IssueComment. +""" +type IssueCommentConnection { + """ + A list of edges. + """ + edges: [IssueCommentEdge] + + """ + A list of nodes. + """ + nodes: [IssueComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IssueCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueComment +} + +""" +Ways in which lists of issue comments can be ordered upon return. +""" +input IssueCommentOrder { + """ + The direction in which to order issue comments by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order issue comments by. + """ + field: IssueCommentOrderField! +} + +""" +Properties by which issue comment connections can be ordered. +""" +enum IssueCommentOrderField { + """ + Order issue comments by update time + """ + UPDATED_AT +} + +""" +The connection type for Issue. +""" +type IssueConnection { + """ + A list of edges. + """ + edges: [IssueEdge] + + """ + A list of nodes. + """ + nodes: [Issue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates issues opened by a user within one repository. +""" +type IssueContributionsByRepository { + """ + The issue contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedIssueContributionConnection! + + """ + The repository in which the issues were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type IssueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Issue +} + +""" +Ways in which to filter lists of issues. +""" +input IssueFilters { + """ + List issues assigned to given name. Pass in `null` for issues with no assigned + user, and `*` for issues assigned to any user. + """ + assignee: String + + """ + List issues created by given name. + """ + createdBy: String + + """ + List issues where the list of label names exist on the issue. + """ + labels: [String!] + + """ + List issues where the given name is mentioned in the issue. + """ + mentioned: String + + """ + List issues by given milestone argument. If an string representation of an + integer is passed, it should refer to a milestone by its database ID. Pass in + `null` for issues with no milestone, and `*` for issues that are assigned to any milestone. + """ + milestone: String + + """ + List issues by given milestone argument. If an string representation of an + integer is passed, it should refer to a milestone by its number field. Pass in + `null` for issues with no milestone, and `*` for issues that are assigned to any milestone. + """ + milestoneNumber: String + + """ + List issues that have been updated at or after the given date. + """ + since: DateTime + + """ + List issues filtered by the list of states given. + """ + states: [IssueState!] + + """ + List issues subscribed to by viewer. + """ + viewerSubscribed: Boolean = false +} + +""" +Used for return value of Repository.issueOrPullRequest. +""" +union IssueOrPullRequest = Issue | PullRequest + +""" +Ways in which lists of issues can be ordered upon return. +""" +input IssueOrder { + """ + The direction in which to order issues by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order issues by. + """ + field: IssueOrderField! +} + +""" +Properties by which issue connections can be ordered. +""" +enum IssueOrderField { + """ + Order issues by comment count + """ + COMMENTS + + """ + Order issues by creation time + """ + CREATED_AT + + """ + Order issues by update time + """ + UPDATED_AT +} + +""" +The possible states of an issue. +""" +enum IssueState { + """ + An issue that has been closed + """ + CLOSED + + """ + An issue that is still open + """ + OPEN +} + +""" +The possible state reasons of an issue. +""" +enum IssueStateReason { + """ + An issue that has been closed as completed + """ + COMPLETED + + """ + An issue that has been closed as not planned + """ + NOT_PLANNED + + """ + An issue that has been reopened + """ + REOPENED +} + +""" +A repository issue template. +""" +type IssueTemplate { + """ + The template purpose. + """ + about: String + + """ + The suggested issue body. + """ + body: String + + """ + The template filename. + """ + filename: String! + + """ + The template name. + """ + name: String! + + """ + The suggested issue title. + """ + title: String +} + +""" +The connection type for IssueTimelineItem. +""" +type IssueTimelineConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [IssueTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in an issue timeline +""" +union IssueTimelineItem = + AssignedEvent + | ClosedEvent + | Commit + | CrossReferencedEvent + | DemilestonedEvent + | IssueComment + | LabeledEvent + | LockedEvent + | MilestonedEvent + | ReferencedEvent + | RenamedTitleEvent + | ReopenedEvent + | SubscribedEvent + | TransferredEvent + | UnassignedEvent + | UnlabeledEvent + | UnlockedEvent + | UnsubscribedEvent + | UserBlockedEvent + +""" +An edge in a connection. +""" +type IssueTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItem +} + +""" +An item in an issue timeline +""" +union IssueTimelineItems = + AddedToProjectEvent + | AssignedEvent + | ClosedEvent + | CommentDeletedEvent + | ConnectedEvent + | ConvertedNoteToIssueEvent + | ConvertedToDiscussionEvent + | CrossReferencedEvent + | DemilestonedEvent + | DisconnectedEvent + | IssueComment + | LabeledEvent + | LockedEvent + | MarkedAsDuplicateEvent + | MentionedEvent + | MilestonedEvent + | MovedColumnsInProjectEvent + | PinnedEvent + | ReferencedEvent + | RemovedFromProjectEvent + | RenamedTitleEvent + | ReopenedEvent + | SubscribedEvent + | TransferredEvent + | UnassignedEvent + | UnlabeledEvent + | UnlockedEvent + | UnmarkedAsDuplicateEvent + | UnpinnedEvent + | UnsubscribedEvent + | UserBlockedEvent + +""" +The connection type for IssueTimelineItems. +""" +type IssueTimelineItemsConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [IssueTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type IssueTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum IssueTimelineItemsItemType { + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a 'converted_to_discussion' event on a given issue. + """ + CONVERTED_TO_DISCUSSION_EVENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT +} + +""" +Represents a user signing up for a GitHub account. +""" +type JoinedGitHubContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A label for categorizing Issues, Pull Requests, Milestones, or Discussions with a given Repository. +""" +type Label implements Node { + """ + Identifies the label color. + """ + color: String! + + """ + Identifies the date and time when the label was created. + """ + createdAt: DateTime + + """ + A brief description of this label. + """ + description: String + id: ID! + + """ + Indicates whether or not this is a default label. + """ + isDefault: Boolean! + + """ + A list of issues associated with this label. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Identifies the label name. + """ + name: String! + + """ + A list of pull requests associated with this label. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + The repository associated with this label. + """ + repository: Repository! + + """ + The HTTP path for this label. + """ + resourcePath: URI! + + """ + Identifies the date and time when the label was last updated. + """ + updatedAt: DateTime + + """ + The HTTP URL for this label. + """ + url: URI! +} + +""" +The connection type for Label. +""" +type LabelConnection { + """ + A list of edges. + """ + edges: [LabelEdge] + + """ + A list of nodes. + """ + nodes: [Label] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type LabelEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Label +} + +""" +Ways in which lists of labels can be ordered upon return. +""" +input LabelOrder { + """ + The direction in which to order labels by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order labels by. + """ + field: LabelOrderField! +} + +""" +Properties by which label connections can be ordered. +""" +enum LabelOrderField { + """ + Order labels by creation time + """ + CREATED_AT + + """ + Order labels by name + """ + NAME +} + +""" +An object that can have labels assigned to it. +""" +interface Labelable { + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection +} + +""" +Represents a 'labeled' event on a given issue or pull request. +""" +type LabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the label associated with the 'labeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Represents a given language found in repositories. +""" +type Language implements Node { + """ + The color defined for the current language. + """ + color: String + id: ID! + + """ + The name of the current language. + """ + name: String! +} + +""" +A list of languages associated with the parent. +""" +type LanguageConnection { + """ + A list of edges. + """ + edges: [LanguageEdge] + + """ + A list of nodes. + """ + nodes: [Language] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in bytes of files written in that language. + """ + totalSize: Int! +} + +""" +Represents the language of a repository. +""" +type LanguageEdge { + cursor: String! + node: Language! + + """ + The number of bytes of code written in the language. + """ + size: Int! +} + +""" +Ordering options for language connections. +""" +input LanguageOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order languages by. + """ + field: LanguageOrderField! +} + +""" +Properties by which language connections can be ordered. +""" +enum LanguageOrderField { + """ + Order languages by the size of all files containing the language + """ + SIZE +} + +""" +A repository's open source license +""" +type License implements Node { + """ + The full text of the license + """ + body: String! + + """ + The conditions set by the license + """ + conditions: [LicenseRule]! + + """ + A human-readable description of the license + """ + description: String + + """ + Whether the license should be featured + """ + featured: Boolean! + + """ + Whether the license should be displayed in license pickers + """ + hidden: Boolean! + id: ID! + + """ + Instructions on how to implement the license + """ + implementation: String + + """ + The lowercased SPDX ID of the license + """ + key: String! + + """ + The limitations set by the license + """ + limitations: [LicenseRule]! + + """ + The license full name specified by + """ + name: String! + + """ + Customary short name if applicable (e.g, GPLv3) + """ + nickname: String + + """ + The permissions set by the license + """ + permissions: [LicenseRule]! + + """ + Whether the license is a pseudo-license placeholder (e.g., other, no-license) + """ + pseudoLicense: Boolean! + + """ + Short identifier specified by + """ + spdxId: String + + """ + URL to the license on + """ + url: URI +} + +""" +Describes a License's conditions, permissions, and limitations +""" +type LicenseRule { + """ + A description of the rule + """ + description: String! + + """ + The machine-readable rule key + """ + key: String! + + """ + The human-readable rule label + """ + label: String! +} + +""" +Autogenerated input type of LinkProjectV2ToRepository +""" +input LinkProjectV2ToRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to link to the repository. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The ID of the repository to link to the project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of LinkProjectV2ToRepository +""" +type LinkProjectV2ToRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the project is linked to. + """ + repository: Repository +} + +""" +Autogenerated input type of LinkProjectV2ToTeam +""" +input LinkProjectV2ToTeamInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to link to the team. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The ID of the team to link to the project. + """ + teamId: ID! @possibleTypes(concreteTypes: ["Team"]) +} + +""" +Autogenerated return type of LinkProjectV2ToTeam +""" +type LinkProjectV2ToTeamPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team the project is linked to + """ + team: Team +} + +""" +Autogenerated input type of LinkRepositoryToProject +""" +input LinkRepositoryToProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to link to a Repository + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The ID of the Repository to link to a Project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of LinkRepositoryToProject +""" +type LinkRepositoryToProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +A branch linked to an issue. +""" +type LinkedBranch implements Node { + id: ID! + + """ + The branch's ref. + """ + ref: Ref +} + +""" +The connection type for LinkedBranch. +""" +type LinkedBranchConnection { + """ + A list of edges. + """ + edges: [LinkedBranchEdge] + + """ + A list of nodes. + """ + nodes: [LinkedBranch] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type LinkedBranchEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: LinkedBranch +} + +""" +Autogenerated input type of LockLockable +""" +input LockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A reason for why the item will be locked. + """ + lockReason: LockReason + + """ + ID of the item to be locked. + """ + lockableId: ID! @possibleTypes(concreteTypes: ["Discussion", "Issue", "PullRequest"], abstractType: "Lockable") +} + +""" +Autogenerated return type of LockLockable +""" +type LockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was locked. + """ + lockedRecord: Lockable +} + +""" +The possible reasons that an issue or pull request was locked. +""" +enum LockReason { + """ + The issue or pull request was locked because the conversation was off-topic. + """ + OFF_TOPIC + + """ + The issue or pull request was locked because the conversation was resolved. + """ + RESOLVED + + """ + The issue or pull request was locked because the conversation was spam. + """ + SPAM + + """ + The issue or pull request was locked because the conversation was too heated. + """ + TOO_HEATED +} + +""" +An object that can be locked. +""" +interface Lockable { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + `true` if the object is locked + """ + locked: Boolean! +} + +""" +Represents a 'locked' event on a given issue or pull request. +""" +type LockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reason that the conversation was locked (optional). + """ + lockReason: LockReason + + """ + Object that was locked. + """ + lockable: Lockable! +} + +""" +A placeholder user for attribution of imported data on GitHub. +""" +type Mannequin implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The user that has claimed the data attributed to this mannequin. + """ + claimant: User + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The mannequin's email on the source instance. + """ + email: String + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +Autogenerated input type of MarkDiscussionCommentAsAnswer +""" +input MarkDiscussionCommentAsAnswerInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to mark as an answer. + """ + id: ID! @possibleTypes(concreteTypes: ["DiscussionComment"]) +} + +""" +Autogenerated return type of MarkDiscussionCommentAsAnswer +""" +type MarkDiscussionCommentAsAnswerPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that includes the chosen comment. + """ + discussion: Discussion +} + +""" +Autogenerated input type of MarkFileAsViewed +""" +input MarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The path of the file to mark as viewed + """ + path: String! + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MarkFileAsViewed +""" +type MarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of MarkPullRequestReadyForReview +""" +input MarkPullRequestReadyForReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be marked as ready for review. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MarkPullRequestReadyForReview +""" +type MarkPullRequestReadyForReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is ready for review. + """ + pullRequest: PullRequest +} + +""" +Represents a 'marked_as_duplicate' event on a given issue or pull request. +""" +type MarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +A public description of a Marketplace category. +""" +type MarketplaceCategory implements Node { + """ + The category's description. + """ + description: String + + """ + The technical description of how apps listed in this category work with GitHub. + """ + howItWorks: String + id: ID! + + """ + The category's name. + """ + name: String! + + """ + How many Marketplace listings have this as their primary category. + """ + primaryListingCount: Int! + + """ + The HTTP path for this Marketplace category. + """ + resourcePath: URI! + + """ + How many Marketplace listings have this as their secondary category. + """ + secondaryListingCount: Int! + + """ + The short name of the category used in its URL. + """ + slug: String! + + """ + The HTTP URL for this Marketplace category. + """ + url: URI! +} + +""" +A listing in the GitHub integration marketplace. +""" +type MarketplaceListing implements Node { + """ + The GitHub App this listing represents. + """ + app: App + + """ + URL to the listing owner's company site. + """ + companyUrl: URI + + """ + The HTTP path for configuring access to the listing's integration or OAuth app + """ + configurationResourcePath: URI! + + """ + The HTTP URL for configuring access to the listing's integration or OAuth app + """ + configurationUrl: URI! + + """ + URL to the listing's documentation. + """ + documentationUrl: URI + + """ + The listing's detailed description. + """ + extendedDescription: String + + """ + The listing's detailed description rendered to HTML. + """ + extendedDescriptionHTML: HTML! + + """ + The listing's introductory description. + """ + fullDescription: String! + + """ + The listing's introductory description rendered to HTML. + """ + fullDescriptionHTML: HTML! + + """ + Does this listing have any plans with a free trial? + """ + hasPublishedFreeTrialPlans: Boolean! + + """ + Does this listing have a terms of service link? + """ + hasTermsOfService: Boolean! + + """ + Whether the creator of the app is a verified org + """ + hasVerifiedOwner: Boolean! + + """ + A technical description of how this app works with GitHub. + """ + howItWorks: String + + """ + The listing's technical description rendered to HTML. + """ + howItWorksHTML: HTML! + id: ID! + + """ + URL to install the product to the viewer's account or organization. + """ + installationUrl: URI + + """ + Whether this listing's app has been installed for the current viewer + """ + installedForViewer: Boolean! + + """ + Whether this listing has been removed from the Marketplace. + """ + isArchived: Boolean! + + """ + Whether this listing is still an editable draft that has not been submitted + for review and is not publicly visible in the Marketplace. + """ + isDraft: Boolean! + + """ + Whether the product this listing represents is available as part of a paid plan. + """ + isPaid: Boolean! + + """ + Whether this listing has been approved for display in the Marketplace. + """ + isPublic: Boolean! + + """ + Whether this listing has been rejected by GitHub for display in the Marketplace. + """ + isRejected: Boolean! + + """ + Whether this listing has been approved for unverified display in the Marketplace. + """ + isUnverified: Boolean! + + """ + Whether this draft listing has been submitted for review for approval to be unverified in the Marketplace. + """ + isUnverifiedPending: Boolean! + + """ + Whether this draft listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromDraft: Boolean! + + """ + Whether this unverified listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromUnverified: Boolean! + + """ + Whether this listing has been approved for verified display in the Marketplace. + """ + isVerified: Boolean! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + URL for the listing's logo image. + """ + logoUrl( + """ + The size in pixels of the resulting square image. + """ + size: Int = 400 + ): URI + + """ + The listing's full name. + """ + name: String! + + """ + The listing's very short description without a trailing period or ampersands. + """ + normalizedShortDescription: String! + + """ + URL to the listing's detailed pricing. + """ + pricingUrl: URI + + """ + The category that best describes the listing. + """ + primaryCategory: MarketplaceCategory! + + """ + URL to the listing's privacy policy, may return an empty string for listings that do not require a privacy policy URL. + """ + privacyPolicyUrl: URI! + + """ + The HTTP path for the Marketplace listing. + """ + resourcePath: URI! + + """ + The URLs for the listing's screenshots. + """ + screenshotUrls: [String]! + + """ + An alternate category that describes the listing. + """ + secondaryCategory: MarketplaceCategory + + """ + The listing's very short description. + """ + shortDescription: String! + + """ + The short name of the listing used in its URL. + """ + slug: String! + + """ + URL to the listing's status page. + """ + statusUrl: URI + + """ + An email address for support for this listing's app. + """ + supportEmail: String + + """ + Either a URL or an email address for support for this listing's app, may + return an empty string for listings that do not require a support URL. + """ + supportUrl: URI! + + """ + URL to the listing's terms of service. + """ + termsOfServiceUrl: URI + + """ + The HTTP URL for the Marketplace listing. + """ + url: URI! + + """ + Can the current viewer add plans for this Marketplace listing. + """ + viewerCanAddPlans: Boolean! + + """ + Can the current viewer approve this Marketplace listing. + """ + viewerCanApprove: Boolean! + + """ + Can the current viewer delist this Marketplace listing. + """ + viewerCanDelist: Boolean! + + """ + Can the current viewer edit this Marketplace listing. + """ + viewerCanEdit: Boolean! + + """ + Can the current viewer edit the primary and secondary category of this + Marketplace listing. + """ + viewerCanEditCategories: Boolean! + + """ + Can the current viewer edit the plans for this Marketplace listing. + """ + viewerCanEditPlans: Boolean! + + """ + Can the current viewer return this Marketplace listing to draft state + so it becomes editable again. + """ + viewerCanRedraft: Boolean! + + """ + Can the current viewer reject this Marketplace listing by returning it to + an editable draft state or rejecting it entirely. + """ + viewerCanReject: Boolean! + + """ + Can the current viewer request this listing be reviewed for display in + the Marketplace as verified. + """ + viewerCanRequestApproval: Boolean! + + """ + Indicates whether the current user has an active subscription to this Marketplace listing. + """ + viewerHasPurchased: Boolean! + + """ + Indicates if the current user has purchased a subscription to this Marketplace listing + for all of the organizations the user owns. + """ + viewerHasPurchasedForAllOrganizations: Boolean! + + """ + Does the current viewer role allow them to administer this Marketplace listing. + """ + viewerIsListingAdmin: Boolean! +} + +""" +Look up Marketplace Listings +""" +type MarketplaceListingConnection { + """ + A list of edges. + """ + edges: [MarketplaceListingEdge] + + """ + A list of nodes. + """ + nodes: [MarketplaceListing] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MarketplaceListingEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: MarketplaceListing +} + +""" +Entities that have members who can set status messages. +""" +interface MemberStatusable { + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! +} + +""" +Audit log entry for a members_can_delete_repos.clear event. +""" +type MembersCanDeleteReposClearAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.disable event. +""" +type MembersCanDeleteReposDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.enable event. +""" +type MembersCanDeleteReposEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Represents a 'mentioned' event on a given issue or pull request. +""" +type MentionedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! +} + +""" +Autogenerated input type of MergeBranch +""" +input MergeBranchInput { + """ + The email address to associate with this commit. + """ + authorEmail: String + + """ + The name of the base branch that the provided head will be merged into. + """ + base: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Message to use for the merge commit. If omitted, a default will be used. + """ + commitMessage: String + + """ + The head to merge into the base branch. This can be a branch name or a commit GitObjectID. + """ + head: String! + + """ + The Node ID of the Repository containing the base branch that will be modified. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of MergeBranch +""" +type MergeBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The resulting merge Commit. + """ + mergeCommit: Commit +} + +""" +The possible default commit messages for merges. +""" +enum MergeCommitMessage { + """ + Default to a blank commit message. + """ + BLANK + + """ + Default to the pull request's body. + """ + PR_BODY + + """ + Default to the pull request's title. + """ + PR_TITLE +} + +""" +The possible default commit titles for merges. +""" +enum MergeCommitTitle { + """ + Default to the classic title for a merge message (e.g., Merge pull request #123 from branch-name). + """ + MERGE_MESSAGE + + """ + Default to the pull request's title. + """ + PR_TITLE +} + +""" +Autogenerated input type of MergePullRequest +""" +input MergePullRequestInput { + """ + The email address to associate with this merge. + """ + authorEmail: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Commit body to use for the merge commit; if omitted, a default message will be used + """ + commitBody: String + + """ + Commit headline to use for the merge commit; if omitted, a default message will be used. + """ + commitHeadline: String + + """ + OID that the pull request head ref must match to allow merge; if omitted, no check is performed. + """ + expectedHeadOid: GitObjectID + + """ + The merge method to use. If omitted, defaults to 'MERGE' + """ + mergeMethod: PullRequestMergeMethod = MERGE + + """ + ID of the pull request to be merged. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MergePullRequest +""" +type MergePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was merged. + """ + pullRequest: PullRequest +} + +""" +Detailed status information about a pull request merge. +""" +enum MergeStateStatus { + """ + The head ref is out of date. + """ + BEHIND + + """ + The merge is blocked. + """ + BLOCKED + + """ + Mergeable and passing commit status. + """ + CLEAN + + """ + The merge commit cannot be cleanly created. + """ + DIRTY + + """ + The merge is blocked due to the pull request being a draft. + """ + DRAFT + @deprecated( + reason: "DRAFT state will be removed from this enum and `isDraft` should be used instead Use PullRequest.isDraft instead. Removal on 2021-01-01 UTC." + ) + + """ + Mergeable with passing commit status and pre-receive hooks. + """ + HAS_HOOKS + + """ + The state cannot currently be determined. + """ + UNKNOWN + + """ + Mergeable with non-passing commit status. + """ + UNSTABLE +} + +""" +Whether or not a PullRequest can be merged. +""" +enum MergeableState { + """ + The pull request cannot be merged due to merge conflicts. + """ + CONFLICTING + + """ + The pull request can be merged. + """ + MERGEABLE + + """ + The mergeability of the pull request is still being calculated. + """ + UNKNOWN +} + +""" +Represents a 'merged' event on a given pull request. +""" +type MergedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the `merge` event. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the Ref associated with the `merge` event. + """ + mergeRef: Ref + + """ + Identifies the name of the Ref associated with the `merge` event. + """ + mergeRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this merged event. + """ + resourcePath: URI! + + """ + The HTTP URL for this merged event. + """ + url: URI! +} + +""" +Represents an Octoshift migration. +""" +interface Migration { + """ + The Octoshift migration flag to continue on error. + """ + continueOnError: Boolean! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: String + + """ + The reason the migration failed. + """ + failureReason: String + id: ID! + + """ + The URL for the migration log (expires 1 day after migration completes). + """ + migrationLogUrl: URI + + """ + The Octoshift migration source. + """ + migrationSource: MigrationSource! + + """ + The target repository name. + """ + repositoryName: String! + + """ + The Octoshift migration source URL. + """ + sourceUrl: URI! + + """ + The Octoshift migration state. + """ + state: MigrationState! +} + +""" +An Octoshift migration source. +""" +type MigrationSource implements Node { + id: ID! + + """ + The Octoshift migration source name. + """ + name: String! + + """ + The Octoshift migration source type. + """ + type: MigrationSourceType! + + """ + The Octoshift migration source URL. + """ + url: URI! +} + +""" +Represents the different Octoshift migration sources. +""" +enum MigrationSourceType { + """ + An Azure DevOps migration source. + """ + AZURE_DEVOPS + + """ + A Bitbucket Server migration source. + """ + BITBUCKET_SERVER + + """ + A GitHub Migration API source. + """ + GITHUB_ARCHIVE +} + +""" +The Octoshift migration state. +""" +enum MigrationState { + """ + The Octoshift migration has failed. + """ + FAILED + + """ + The Octoshift migration has invalid credentials. + """ + FAILED_VALIDATION + + """ + The Octoshift migration is in progress. + """ + IN_PROGRESS + + """ + The Octoshift migration has not started. + """ + NOT_STARTED + + """ + The Octoshift migration needs to have its credentials validated. + """ + PENDING_VALIDATION + + """ + The Octoshift migration has been queued. + """ + QUEUED + + """ + The Octoshift migration has succeeded. + """ + SUCCEEDED +} + +""" +Represents a Milestone object on a given repository. +""" +type Milestone implements Closable & Node & UniformResourceLocatable { + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who created the milestone. + """ + creator: Actor + + """ + Identifies the description of the milestone. + """ + description: String + + """ + Identifies the due date of the milestone. + """ + dueOn: DateTime + id: ID! + + """ + A list of issues associated with the milestone. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Identifies the number of the milestone. + """ + number: Int! + + """ + Identifies the percentage complete for the milestone + """ + progressPercentage: Float! + + """ + A list of pull requests associated with the milestone. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + The repository associated with this milestone. + """ + repository: Repository! + + """ + The HTTP path for this milestone + """ + resourcePath: URI! + + """ + Identifies the state of the milestone. + """ + state: MilestoneState! + + """ + Identifies the title of the milestone. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this milestone + """ + url: URI! +} + +""" +The connection type for Milestone. +""" +type MilestoneConnection { + """ + A list of edges. + """ + edges: [MilestoneEdge] + + """ + A list of nodes. + """ + nodes: [Milestone] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MilestoneEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Milestone +} + +""" +Types that can be inside a Milestone. +""" +union MilestoneItem = Issue | PullRequest + +""" +Ordering options for milestone connections. +""" +input MilestoneOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order milestones by. + """ + field: MilestoneOrderField! +} + +""" +Properties by which milestone connections can be ordered. +""" +enum MilestoneOrderField { + """ + Order milestones by when they were created. + """ + CREATED_AT + + """ + Order milestones by when they are due. + """ + DUE_DATE + + """ + Order milestones by their number. + """ + NUMBER + + """ + Order milestones by when they were last updated. + """ + UPDATED_AT +} + +""" +The possible states of a milestone. +""" +enum MilestoneState { + """ + A milestone that has been closed. + """ + CLOSED + + """ + A milestone that is still open. + """ + OPEN +} + +""" +Represents a 'milestoned' event on a given issue or pull request. +""" +type MilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the milestone title associated with the 'milestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +Entities that can be minimized. +""" +interface Minimizable { + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! +} + +""" +Autogenerated input type of MinimizeComment +""" +input MinimizeCommentInput { + """ + The classification of comment + """ + classifier: ReportedContentClassifiers! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + @possibleTypes( + concreteTypes: ["CommitComment", "DiscussionComment", "GistComment", "IssueComment", "PullRequestReviewComment"] + abstractType: "Minimizable" + ) +} + +""" +Autogenerated return type of MinimizeComment +""" +type MinimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was minimized. + """ + minimizedComment: Minimizable +} + +""" +Autogenerated input type of MoveProjectCard +""" +input MoveProjectCardInput { + """ + Place the new card after the card with this id. Pass null to place it at the top. + """ + afterCardId: ID @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + The id of the card to move. + """ + cardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to move it into. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of MoveProjectCard +""" +type MoveProjectCardPayload { + """ + The new edge of the moved card. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of MoveProjectColumn +""" +input MoveProjectColumnInput { + """ + Place the new column after the column with this id. Pass null to place it at the front. + """ + afterColumnId: ID @possibleTypes(concreteTypes: ["ProjectColumn"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to move. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of MoveProjectColumn +""" +type MoveProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new edge of the moved column. + """ + columnEdge: ProjectColumnEdge +} + +""" +Represents a 'moved_columns_in_project' event on a given issue or pull request. +""" +type MovedColumnsInProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Column name the issue or pull request was moved from. + """ + previousProjectColumnName: String! @preview(toggledBy: "starfox-preview") + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name the issue or pull request was moved to. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +The root query for implementing GraphQL mutations. +""" +type Mutation { + """ + Clear all of a customer's queued migrations + """ + abortQueuedMigrations( + """ + Parameters for AbortQueuedMigrations + """ + input: AbortQueuedMigrationsInput! + ): AbortQueuedMigrationsPayload + + """ + Accepts a pending invitation for a user to become an administrator of an enterprise. + """ + acceptEnterpriseAdministratorInvitation( + """ + Parameters for AcceptEnterpriseAdministratorInvitation + """ + input: AcceptEnterpriseAdministratorInvitationInput! + ): AcceptEnterpriseAdministratorInvitationPayload + + """ + Applies a suggested topic to the repository. + """ + acceptTopicSuggestion( + """ + Parameters for AcceptTopicSuggestion + """ + input: AcceptTopicSuggestionInput! + ): AcceptTopicSuggestionPayload + + """ + Adds assignees to an assignable object. + """ + addAssigneesToAssignable( + """ + Parameters for AddAssigneesToAssignable + """ + input: AddAssigneesToAssignableInput! + ): AddAssigneesToAssignablePayload + + """ + Adds a comment to an Issue or Pull Request. + """ + addComment( + """ + Parameters for AddComment + """ + input: AddCommentInput! + ): AddCommentPayload + + """ + Adds a comment to a Discussion, possibly as a reply to another comment. + """ + addDiscussionComment( + """ + Parameters for AddDiscussionComment + """ + input: AddDiscussionCommentInput! + ): AddDiscussionCommentPayload + + """ + Vote for an option in a discussion poll. + """ + addDiscussionPollVote( + """ + Parameters for AddDiscussionPollVote + """ + input: AddDiscussionPollVoteInput! + ): AddDiscussionPollVotePayload + + """ + Adds enterprise members to an organization within the enterprise. + """ + addEnterpriseOrganizationMember( + """ + Parameters for AddEnterpriseOrganizationMember + """ + input: AddEnterpriseOrganizationMemberInput! + ): AddEnterpriseOrganizationMemberPayload + + """ + Adds a support entitlement to an enterprise member. + """ + addEnterpriseSupportEntitlement( + """ + Parameters for AddEnterpriseSupportEntitlement + """ + input: AddEnterpriseSupportEntitlementInput! + ): AddEnterpriseSupportEntitlementPayload + + """ + Adds labels to a labelable object. + """ + addLabelsToLabelable( + """ + Parameters for AddLabelsToLabelable + """ + input: AddLabelsToLabelableInput! + ): AddLabelsToLabelablePayload + + """ + Adds a card to a ProjectColumn. Either `contentId` or `note` must be provided but **not** both. + """ + addProjectCard( + """ + Parameters for AddProjectCard + """ + input: AddProjectCardInput! + ): AddProjectCardPayload + + """ + Adds a column to a Project. + """ + addProjectColumn( + """ + Parameters for AddProjectColumn + """ + input: AddProjectColumnInput! + ): AddProjectColumnPayload + + """ + Creates a new draft issue and add it to a Project. + """ + addProjectDraftIssue( + """ + Parameters for AddProjectDraftIssue + """ + input: AddProjectDraftIssueInput! + ): AddProjectDraftIssuePayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Adds an existing item (Issue or PullRequest) to a Project. + """ + addProjectNextItem( + """ + Parameters for AddProjectNextItem + """ + input: AddProjectNextItemInput! + ): AddProjectNextItemPayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Creates a new draft issue and add it to a Project. + """ + addProjectV2DraftIssue( + """ + Parameters for AddProjectV2DraftIssue + """ + input: AddProjectV2DraftIssueInput! + ): AddProjectV2DraftIssuePayload + + """ + Links an existing content instance to a Project. + """ + addProjectV2ItemById( + """ + Parameters for AddProjectV2ItemById + """ + input: AddProjectV2ItemByIdInput! + ): AddProjectV2ItemByIdPayload + + """ + Adds a review to a Pull Request. + """ + addPullRequestReview( + """ + Parameters for AddPullRequestReview + """ + input: AddPullRequestReviewInput! + ): AddPullRequestReviewPayload + + """ + Adds a comment to a review. + """ + addPullRequestReviewComment( + """ + Parameters for AddPullRequestReviewComment + """ + input: AddPullRequestReviewCommentInput! + ): AddPullRequestReviewCommentPayload + + """ + Adds a new thread to a pending Pull Request Review. + """ + addPullRequestReviewThread( + """ + Parameters for AddPullRequestReviewThread + """ + input: AddPullRequestReviewThreadInput! + ): AddPullRequestReviewThreadPayload + + """ + Adds a reaction to a subject. + """ + addReaction( + """ + Parameters for AddReaction + """ + input: AddReactionInput! + ): AddReactionPayload + + """ + Adds a star to a Starrable. + """ + addStar( + """ + Parameters for AddStar + """ + input: AddStarInput! + ): AddStarPayload + + """ + Add an upvote to a discussion or discussion comment. + """ + addUpvote( + """ + Parameters for AddUpvote + """ + input: AddUpvoteInput! + ): AddUpvotePayload + + """ + Adds a verifiable domain to an owning account. + """ + addVerifiableDomain( + """ + Parameters for AddVerifiableDomain + """ + input: AddVerifiableDomainInput! + ): AddVerifiableDomainPayload + + """ + Approve all pending deployments under one or more environments + """ + approveDeployments( + """ + Parameters for ApproveDeployments + """ + input: ApproveDeploymentsInput! + ): ApproveDeploymentsPayload + + """ + Approve a verifiable domain for notification delivery. + """ + approveVerifiableDomain( + """ + Parameters for ApproveVerifiableDomain + """ + input: ApproveVerifiableDomainInput! + ): ApproveVerifiableDomainPayload + + """ + Archives a ProjectV2Item + """ + archiveProjectV2Item( + """ + Parameters for ArchiveProjectV2Item + """ + input: ArchiveProjectV2ItemInput! + ): ArchiveProjectV2ItemPayload + + """ + Marks a repository as archived. + """ + archiveRepository( + """ + Parameters for ArchiveRepository + """ + input: ArchiveRepositoryInput! + ): ArchiveRepositoryPayload + + """ + Cancels a pending invitation for an administrator to join an enterprise. + """ + cancelEnterpriseAdminInvitation( + """ + Parameters for CancelEnterpriseAdminInvitation + """ + input: CancelEnterpriseAdminInvitationInput! + ): CancelEnterpriseAdminInvitationPayload + + """ + Cancel an active sponsorship. + """ + cancelSponsorship( + """ + Parameters for CancelSponsorship + """ + input: CancelSponsorshipInput! + ): CancelSponsorshipPayload + + """ + Update your status on GitHub. + """ + changeUserStatus( + """ + Parameters for ChangeUserStatus + """ + input: ChangeUserStatusInput! + ): ChangeUserStatusPayload + + """ + Clears all labels from a labelable object. + """ + clearLabelsFromLabelable( + """ + Parameters for ClearLabelsFromLabelable + """ + input: ClearLabelsFromLabelableInput! + ): ClearLabelsFromLabelablePayload + + """ + This mutation clears the value of a field for an item in a Project. Currently + only text, number, date, assignees, labels, single-select, iteration and + milestone fields are supported. + """ + clearProjectV2ItemFieldValue( + """ + Parameters for ClearProjectV2ItemFieldValue + """ + input: ClearProjectV2ItemFieldValueInput! + ): ClearProjectV2ItemFieldValuePayload + + """ + Creates a new project by cloning configuration from an existing project. + """ + cloneProject( + """ + Parameters for CloneProject + """ + input: CloneProjectInput! + ): CloneProjectPayload + + """ + Create a new repository with the same files and directory structure as a template repository. + """ + cloneTemplateRepository( + """ + Parameters for CloneTemplateRepository + """ + input: CloneTemplateRepositoryInput! + ): CloneTemplateRepositoryPayload + + """ + Close an issue. + """ + closeIssue( + """ + Parameters for CloseIssue + """ + input: CloseIssueInput! + ): CloseIssuePayload + + """ + Close a pull request. + """ + closePullRequest( + """ + Parameters for ClosePullRequest + """ + input: ClosePullRequestInput! + ): ClosePullRequestPayload + + """ + Convert a project note card to one associated with a newly created issue. + """ + convertProjectCardNoteToIssue( + """ + Parameters for ConvertProjectCardNoteToIssue + """ + input: ConvertProjectCardNoteToIssueInput! + ): ConvertProjectCardNoteToIssuePayload + + """ + Converts a pull request to draft + """ + convertPullRequestToDraft( + """ + Parameters for ConvertPullRequestToDraft + """ + input: ConvertPullRequestToDraftInput! + ): ConvertPullRequestToDraftPayload + + """ + Create a new branch protection rule + """ + createBranchProtectionRule( + """ + Parameters for CreateBranchProtectionRule + """ + input: CreateBranchProtectionRuleInput! + ): CreateBranchProtectionRulePayload + + """ + Create a check run. + """ + createCheckRun( + """ + Parameters for CreateCheckRun + """ + input: CreateCheckRunInput! + ): CreateCheckRunPayload + + """ + Create a check suite + """ + createCheckSuite( + """ + Parameters for CreateCheckSuite + """ + input: CreateCheckSuiteInput! + ): CreateCheckSuitePayload + + """ + Appends a commit to the given branch as the authenticated user. + + This mutation creates a commit whose parent is the HEAD of the provided + branch and also updates that branch to point to the new commit. + It can be thought of as similar to `git commit`. + + ### Locating a Branch + + Commits are appended to a `branch` of type `Ref`. + This must refer to a git branch (i.e. the fully qualified path must + begin with `refs/heads/`, although including this prefix is optional. + + Callers may specify the `branch` to commit to either by its global node + ID or by passing both of `repositoryNameWithOwner` and `refName`. For + more details see the documentation for `CommittableBranch`. + + ### Describing Changes + + `fileChanges` are specified as a `FilesChanges` object describing + `FileAdditions` and `FileDeletions`. + + Please see the documentation for `FileChanges` for more information on + how to use this argument to describe any set of file changes. + + ### Authorship + + Similar to the web commit interface, this mutation does not support + specifying the author or committer of the commit and will not add + support for this in the future. + + A commit created by a successful execution of this mutation will be + authored by the owner of the credential which authenticates the API + request. The committer will be identical to that of commits authored + using the web interface. + + If you need full control over author and committer information, please + use the Git Database REST API instead. + + ### Commit Signing + + Commits made using this mutation are automatically signed by GitHub if + supported and will be marked as verified in the user interface. + """ + createCommitOnBranch( + """ + Parameters for CreateCommitOnBranch + """ + input: CreateCommitOnBranchInput! + ): CreateCommitOnBranchPayload + + """ + Creates a new deployment event. + """ + createDeployment( + """ + Parameters for CreateDeployment + """ + input: CreateDeploymentInput! + ): CreateDeploymentPayload @preview(toggledBy: "flash-preview") + + """ + Create a deployment status. + """ + createDeploymentStatus( + """ + Parameters for CreateDeploymentStatus + """ + input: CreateDeploymentStatusInput! + ): CreateDeploymentStatusPayload @preview(toggledBy: "flash-preview") + + """ + Create a discussion. + """ + createDiscussion( + """ + Parameters for CreateDiscussion + """ + input: CreateDiscussionInput! + ): CreateDiscussionPayload + + """ + Creates an organization as part of an enterprise account. + """ + createEnterpriseOrganization( + """ + Parameters for CreateEnterpriseOrganization + """ + input: CreateEnterpriseOrganizationInput! + ): CreateEnterpriseOrganizationPayload + + """ + Creates an environment or simply returns it if already exists. + """ + createEnvironment( + """ + Parameters for CreateEnvironment + """ + input: CreateEnvironmentInput! + ): CreateEnvironmentPayload + + """ + Creates a new IP allow list entry. + """ + createIpAllowListEntry( + """ + Parameters for CreateIpAllowListEntry + """ + input: CreateIpAllowListEntryInput! + ): CreateIpAllowListEntryPayload + + """ + Creates a new issue. + """ + createIssue( + """ + Parameters for CreateIssue + """ + input: CreateIssueInput! + ): CreateIssuePayload + + """ + Creates a new label. + """ + createLabel( + """ + Parameters for CreateLabel + """ + input: CreateLabelInput! + ): CreateLabelPayload @preview(toggledBy: "bane-preview") + + """ + Create a branch linked to an issue. + """ + createLinkedBranch( + """ + Parameters for CreateLinkedBranch + """ + input: CreateLinkedBranchInput! + ): CreateLinkedBranchPayload + + """ + Creates an Octoshift migration source. + """ + createMigrationSource( + """ + Parameters for CreateMigrationSource + """ + input: CreateMigrationSourceInput! + ): CreateMigrationSourcePayload + + """ + Creates a new project. + """ + createProject( + """ + Parameters for CreateProject + """ + input: CreateProjectInput! + ): CreateProjectPayload + + """ + Creates a new project. + """ + createProjectV2( + """ + Parameters for CreateProjectV2 + """ + input: CreateProjectV2Input! + ): CreateProjectV2Payload + + """ + Create a new pull request + """ + createPullRequest( + """ + Parameters for CreatePullRequest + """ + input: CreatePullRequestInput! + ): CreatePullRequestPayload + + """ + Create a new Git Ref. + """ + createRef( + """ + Parameters for CreateRef + """ + input: CreateRefInput! + ): CreateRefPayload + + """ + Create a new repository. + """ + createRepository( + """ + Parameters for CreateRepository + """ + input: CreateRepositoryInput! + ): CreateRepositoryPayload + + """ + Create a new payment tier for your GitHub Sponsors profile. + """ + createSponsorsTier( + """ + Parameters for CreateSponsorsTier + """ + input: CreateSponsorsTierInput! + ): CreateSponsorsTierPayload + + """ + Start a new sponsorship of a maintainer in GitHub Sponsors, or reactivate a past sponsorship. + """ + createSponsorship( + """ + Parameters for CreateSponsorship + """ + input: CreateSponsorshipInput! + ): CreateSponsorshipPayload + + """ + Creates a new team discussion. + """ + createTeamDiscussion( + """ + Parameters for CreateTeamDiscussion + """ + input: CreateTeamDiscussionInput! + ): CreateTeamDiscussionPayload + + """ + Creates a new team discussion comment. + """ + createTeamDiscussionComment( + """ + Parameters for CreateTeamDiscussionComment + """ + input: CreateTeamDiscussionCommentInput! + ): CreateTeamDiscussionCommentPayload + + """ + Rejects a suggested topic for the repository. + """ + declineTopicSuggestion( + """ + Parameters for DeclineTopicSuggestion + """ + input: DeclineTopicSuggestionInput! + ): DeclineTopicSuggestionPayload + + """ + Delete a branch protection rule + """ + deleteBranchProtectionRule( + """ + Parameters for DeleteBranchProtectionRule + """ + input: DeleteBranchProtectionRuleInput! + ): DeleteBranchProtectionRulePayload + + """ + Deletes a deployment. + """ + deleteDeployment( + """ + Parameters for DeleteDeployment + """ + input: DeleteDeploymentInput! + ): DeleteDeploymentPayload + + """ + Delete a discussion and all of its replies. + """ + deleteDiscussion( + """ + Parameters for DeleteDiscussion + """ + input: DeleteDiscussionInput! + ): DeleteDiscussionPayload + + """ + Delete a discussion comment. If it has replies, wipe it instead. + """ + deleteDiscussionComment( + """ + Parameters for DeleteDiscussionComment + """ + input: DeleteDiscussionCommentInput! + ): DeleteDiscussionCommentPayload + + """ + Deletes an environment + """ + deleteEnvironment( + """ + Parameters for DeleteEnvironment + """ + input: DeleteEnvironmentInput! + ): DeleteEnvironmentPayload + + """ + Deletes an IP allow list entry. + """ + deleteIpAllowListEntry( + """ + Parameters for DeleteIpAllowListEntry + """ + input: DeleteIpAllowListEntryInput! + ): DeleteIpAllowListEntryPayload + + """ + Deletes an Issue object. + """ + deleteIssue( + """ + Parameters for DeleteIssue + """ + input: DeleteIssueInput! + ): DeleteIssuePayload + + """ + Deletes an IssueComment object. + """ + deleteIssueComment( + """ + Parameters for DeleteIssueComment + """ + input: DeleteIssueCommentInput! + ): DeleteIssueCommentPayload + + """ + Deletes a label. + """ + deleteLabel( + """ + Parameters for DeleteLabel + """ + input: DeleteLabelInput! + ): DeleteLabelPayload @preview(toggledBy: "bane-preview") + + """ + Unlink a branch from an issue. + """ + deleteLinkedBranch( + """ + Parameters for DeleteLinkedBranch + """ + input: DeleteLinkedBranchInput! + ): DeleteLinkedBranchPayload + + """ + Delete a package version. + """ + deletePackageVersion( + """ + Parameters for DeletePackageVersion + """ + input: DeletePackageVersionInput! + ): DeletePackageVersionPayload @preview(toggledBy: "package-deletes-preview") + + """ + Deletes a project. + """ + deleteProject( + """ + Parameters for DeleteProject + """ + input: DeleteProjectInput! + ): DeleteProjectPayload + + """ + Deletes a project card. + """ + deleteProjectCard( + """ + Parameters for DeleteProjectCard + """ + input: DeleteProjectCardInput! + ): DeleteProjectCardPayload + + """ + Deletes a project column. + """ + deleteProjectColumn( + """ + Parameters for DeleteProjectColumn + """ + input: DeleteProjectColumnInput! + ): DeleteProjectColumnPayload + + """ + Deletes an item from a Project. + """ + deleteProjectNextItem( + """ + Parameters for DeleteProjectNextItem + """ + input: DeleteProjectNextItemInput! + ): DeleteProjectNextItemPayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Deletes an item from a Project. + """ + deleteProjectV2Item( + """ + Parameters for DeleteProjectV2Item + """ + input: DeleteProjectV2ItemInput! + ): DeleteProjectV2ItemPayload + + """ + Deletes a pull request review. + """ + deletePullRequestReview( + """ + Parameters for DeletePullRequestReview + """ + input: DeletePullRequestReviewInput! + ): DeletePullRequestReviewPayload + + """ + Deletes a pull request review comment. + """ + deletePullRequestReviewComment( + """ + Parameters for DeletePullRequestReviewComment + """ + input: DeletePullRequestReviewCommentInput! + ): DeletePullRequestReviewCommentPayload + + """ + Delete a Git Ref. + """ + deleteRef( + """ + Parameters for DeleteRef + """ + input: DeleteRefInput! + ): DeleteRefPayload + + """ + Deletes a team discussion. + """ + deleteTeamDiscussion( + """ + Parameters for DeleteTeamDiscussion + """ + input: DeleteTeamDiscussionInput! + ): DeleteTeamDiscussionPayload + + """ + Deletes a team discussion comment. + """ + deleteTeamDiscussionComment( + """ + Parameters for DeleteTeamDiscussionComment + """ + input: DeleteTeamDiscussionCommentInput! + ): DeleteTeamDiscussionCommentPayload + + """ + Deletes a verifiable domain. + """ + deleteVerifiableDomain( + """ + Parameters for DeleteVerifiableDomain + """ + input: DeleteVerifiableDomainInput! + ): DeleteVerifiableDomainPayload + + """ + Disable auto merge on the given pull request + """ + disablePullRequestAutoMerge( + """ + Parameters for DisablePullRequestAutoMerge + """ + input: DisablePullRequestAutoMergeInput! + ): DisablePullRequestAutoMergePayload + + """ + Dismisses an approved or rejected pull request review. + """ + dismissPullRequestReview( + """ + Parameters for DismissPullRequestReview + """ + input: DismissPullRequestReviewInput! + ): DismissPullRequestReviewPayload + + """ + Dismisses the Dependabot alert. + """ + dismissRepositoryVulnerabilityAlert( + """ + Parameters for DismissRepositoryVulnerabilityAlert + """ + input: DismissRepositoryVulnerabilityAlertInput! + ): DismissRepositoryVulnerabilityAlertPayload + + """ + Enable the default auto-merge on a pull request. + """ + enablePullRequestAutoMerge( + """ + Parameters for EnablePullRequestAutoMerge + """ + input: EnablePullRequestAutoMergeInput! + ): EnablePullRequestAutoMergePayload + + """ + Follow an organization. + """ + followOrganization( + """ + Parameters for FollowOrganization + """ + input: FollowOrganizationInput! + ): FollowOrganizationPayload + + """ + Follow a user. + """ + followUser( + """ + Parameters for FollowUser + """ + input: FollowUserInput! + ): FollowUserPayload + + """ + Grant the migrator role to a user for all organizations under an enterprise account. + """ + grantEnterpriseOrganizationsMigratorRole( + """ + Parameters for GrantEnterpriseOrganizationsMigratorRole + """ + input: GrantEnterpriseOrganizationsMigratorRoleInput! + ): GrantEnterpriseOrganizationsMigratorRolePayload + + """ + Grant the migrator role to a user or a team. + """ + grantMigratorRole( + """ + Parameters for GrantMigratorRole + """ + input: GrantMigratorRoleInput! + ): GrantMigratorRolePayload + + """ + Creates a new project by importing columns and a list of issues/PRs. + """ + importProject( + """ + Parameters for ImportProject + """ + input: ImportProjectInput! + ): ImportProjectPayload @preview(toggledBy: "slothette-preview") + + """ + Invite someone to become an administrator of the enterprise. + """ + inviteEnterpriseAdmin( + """ + Parameters for InviteEnterpriseAdmin + """ + input: InviteEnterpriseAdminInput! + ): InviteEnterpriseAdminPayload + + """ + Links a project to a repository. + """ + linkProjectV2ToRepository( + """ + Parameters for LinkProjectV2ToRepository + """ + input: LinkProjectV2ToRepositoryInput! + ): LinkProjectV2ToRepositoryPayload + + """ + Links a project to a team. + """ + linkProjectV2ToTeam( + """ + Parameters for LinkProjectV2ToTeam + """ + input: LinkProjectV2ToTeamInput! + ): LinkProjectV2ToTeamPayload + + """ + Creates a repository link for a project. + """ + linkRepositoryToProject( + """ + Parameters for LinkRepositoryToProject + """ + input: LinkRepositoryToProjectInput! + ): LinkRepositoryToProjectPayload + + """ + Lock a lockable object + """ + lockLockable( + """ + Parameters for LockLockable + """ + input: LockLockableInput! + ): LockLockablePayload + + """ + Mark a discussion comment as the chosen answer for discussions in an answerable category. + """ + markDiscussionCommentAsAnswer( + """ + Parameters for MarkDiscussionCommentAsAnswer + """ + input: MarkDiscussionCommentAsAnswerInput! + ): MarkDiscussionCommentAsAnswerPayload + + """ + Mark a pull request file as viewed + """ + markFileAsViewed( + """ + Parameters for MarkFileAsViewed + """ + input: MarkFileAsViewedInput! + ): MarkFileAsViewedPayload + + """ + Marks a pull request ready for review. + """ + markPullRequestReadyForReview( + """ + Parameters for MarkPullRequestReadyForReview + """ + input: MarkPullRequestReadyForReviewInput! + ): MarkPullRequestReadyForReviewPayload + + """ + Merge a head into a branch. + """ + mergeBranch( + """ + Parameters for MergeBranch + """ + input: MergeBranchInput! + ): MergeBranchPayload + + """ + Merge a pull request. + """ + mergePullRequest( + """ + Parameters for MergePullRequest + """ + input: MergePullRequestInput! + ): MergePullRequestPayload + + """ + Minimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + minimizeComment( + """ + Parameters for MinimizeComment + """ + input: MinimizeCommentInput! + ): MinimizeCommentPayload + + """ + Moves a project card to another place. + """ + moveProjectCard( + """ + Parameters for MoveProjectCard + """ + input: MoveProjectCardInput! + ): MoveProjectCardPayload + + """ + Moves a project column to another place. + """ + moveProjectColumn( + """ + Parameters for MoveProjectColumn + """ + input: MoveProjectColumnInput! + ): MoveProjectColumnPayload + + """ + Pin an issue to a repository + """ + pinIssue( + """ + Parameters for PinIssue + """ + input: PinIssueInput! + ): PinIssuePayload + + """ + Regenerates the identity provider recovery codes for an enterprise + """ + regenerateEnterpriseIdentityProviderRecoveryCodes( + """ + Parameters for RegenerateEnterpriseIdentityProviderRecoveryCodes + """ + input: RegenerateEnterpriseIdentityProviderRecoveryCodesInput! + ): RegenerateEnterpriseIdentityProviderRecoveryCodesPayload + + """ + Regenerates a verifiable domain's verification token. + """ + regenerateVerifiableDomainToken( + """ + Parameters for RegenerateVerifiableDomainToken + """ + input: RegenerateVerifiableDomainTokenInput! + ): RegenerateVerifiableDomainTokenPayload + + """ + Reject all pending deployments under one or more environments + """ + rejectDeployments( + """ + Parameters for RejectDeployments + """ + input: RejectDeploymentsInput! + ): RejectDeploymentsPayload + + """ + Removes assignees from an assignable object. + """ + removeAssigneesFromAssignable( + """ + Parameters for RemoveAssigneesFromAssignable + """ + input: RemoveAssigneesFromAssignableInput! + ): RemoveAssigneesFromAssignablePayload + + """ + Removes an administrator from the enterprise. + """ + removeEnterpriseAdmin( + """ + Parameters for RemoveEnterpriseAdmin + """ + input: RemoveEnterpriseAdminInput! + ): RemoveEnterpriseAdminPayload + + """ + Removes the identity provider from an enterprise + """ + removeEnterpriseIdentityProvider( + """ + Parameters for RemoveEnterpriseIdentityProvider + """ + input: RemoveEnterpriseIdentityProviderInput! + ): RemoveEnterpriseIdentityProviderPayload + + """ + Removes an organization from the enterprise + """ + removeEnterpriseOrganization( + """ + Parameters for RemoveEnterpriseOrganization + """ + input: RemoveEnterpriseOrganizationInput! + ): RemoveEnterpriseOrganizationPayload + + """ + Removes a support entitlement from an enterprise member. + """ + removeEnterpriseSupportEntitlement( + """ + Parameters for RemoveEnterpriseSupportEntitlement + """ + input: RemoveEnterpriseSupportEntitlementInput! + ): RemoveEnterpriseSupportEntitlementPayload + + """ + Removes labels from a Labelable object. + """ + removeLabelsFromLabelable( + """ + Parameters for RemoveLabelsFromLabelable + """ + input: RemoveLabelsFromLabelableInput! + ): RemoveLabelsFromLabelablePayload + + """ + Removes outside collaborator from all repositories in an organization. + """ + removeOutsideCollaborator( + """ + Parameters for RemoveOutsideCollaborator + """ + input: RemoveOutsideCollaboratorInput! + ): RemoveOutsideCollaboratorPayload + + """ + Removes a reaction from a subject. + """ + removeReaction( + """ + Parameters for RemoveReaction + """ + input: RemoveReactionInput! + ): RemoveReactionPayload + + """ + Removes a star from a Starrable. + """ + removeStar( + """ + Parameters for RemoveStar + """ + input: RemoveStarInput! + ): RemoveStarPayload + + """ + Remove an upvote to a discussion or discussion comment. + """ + removeUpvote( + """ + Parameters for RemoveUpvote + """ + input: RemoveUpvoteInput! + ): RemoveUpvotePayload + + """ + Reopen a issue. + """ + reopenIssue( + """ + Parameters for ReopenIssue + """ + input: ReopenIssueInput! + ): ReopenIssuePayload + + """ + Reopen a pull request. + """ + reopenPullRequest( + """ + Parameters for ReopenPullRequest + """ + input: ReopenPullRequestInput! + ): ReopenPullRequestPayload + + """ + Set review requests on a pull request. + """ + requestReviews( + """ + Parameters for RequestReviews + """ + input: RequestReviewsInput! + ): RequestReviewsPayload + + """ + Rerequests an existing check suite. + """ + rerequestCheckSuite( + """ + Parameters for RerequestCheckSuite + """ + input: RerequestCheckSuiteInput! + ): RerequestCheckSuitePayload + + """ + Marks a review thread as resolved. + """ + resolveReviewThread( + """ + Parameters for ResolveReviewThread + """ + input: ResolveReviewThreadInput! + ): ResolveReviewThreadPayload + + """ + Revoke the migrator role to a user for all organizations under an enterprise account. + """ + revokeEnterpriseOrganizationsMigratorRole( + """ + Parameters for RevokeEnterpriseOrganizationsMigratorRole + """ + input: RevokeEnterpriseOrganizationsMigratorRoleInput! + ): RevokeEnterpriseOrganizationsMigratorRolePayload + + """ + Revoke the migrator role from a user or a team. + """ + revokeMigratorRole( + """ + Parameters for RevokeMigratorRole + """ + input: RevokeMigratorRoleInput! + ): RevokeMigratorRolePayload + + """ + Creates or updates the identity provider for an enterprise. + """ + setEnterpriseIdentityProvider( + """ + Parameters for SetEnterpriseIdentityProvider + """ + input: SetEnterpriseIdentityProviderInput! + ): SetEnterpriseIdentityProviderPayload + + """ + Set an organization level interaction limit for an organization's public repositories. + """ + setOrganizationInteractionLimit( + """ + Parameters for SetOrganizationInteractionLimit + """ + input: SetOrganizationInteractionLimitInput! + ): SetOrganizationInteractionLimitPayload + + """ + Sets an interaction limit setting for a repository. + """ + setRepositoryInteractionLimit( + """ + Parameters for SetRepositoryInteractionLimit + """ + input: SetRepositoryInteractionLimitInput! + ): SetRepositoryInteractionLimitPayload + + """ + Set a user level interaction limit for an user's public repositories. + """ + setUserInteractionLimit( + """ + Parameters for SetUserInteractionLimit + """ + input: SetUserInteractionLimitInput! + ): SetUserInteractionLimitPayload + + """ + Start a repository migration. + """ + startRepositoryMigration( + """ + Parameters for StartRepositoryMigration + """ + input: StartRepositoryMigrationInput! + ): StartRepositoryMigrationPayload + + """ + Submits a pending pull request review. + """ + submitPullRequestReview( + """ + Parameters for SubmitPullRequestReview + """ + input: SubmitPullRequestReviewInput! + ): SubmitPullRequestReviewPayload + + """ + Transfer an organization from one enterprise to another enterprise. + """ + transferEnterpriseOrganization( + """ + Parameters for TransferEnterpriseOrganization + """ + input: TransferEnterpriseOrganizationInput! + ): TransferEnterpriseOrganizationPayload + + """ + Transfer an issue to a different repository + """ + transferIssue( + """ + Parameters for TransferIssue + """ + input: TransferIssueInput! + ): TransferIssuePayload + + """ + Unarchives a ProjectV2Item + """ + unarchiveProjectV2Item( + """ + Parameters for UnarchiveProjectV2Item + """ + input: UnarchiveProjectV2ItemInput! + ): UnarchiveProjectV2ItemPayload + + """ + Unarchives a repository. + """ + unarchiveRepository( + """ + Parameters for UnarchiveRepository + """ + input: UnarchiveRepositoryInput! + ): UnarchiveRepositoryPayload + + """ + Unfollow an organization. + """ + unfollowOrganization( + """ + Parameters for UnfollowOrganization + """ + input: UnfollowOrganizationInput! + ): UnfollowOrganizationPayload + + """ + Unfollow a user. + """ + unfollowUser( + """ + Parameters for UnfollowUser + """ + input: UnfollowUserInput! + ): UnfollowUserPayload + + """ + Unlinks a project from a repository. + """ + unlinkProjectV2FromRepository( + """ + Parameters for UnlinkProjectV2FromRepository + """ + input: UnlinkProjectV2FromRepositoryInput! + ): UnlinkProjectV2FromRepositoryPayload + + """ + Unlinks a project to a team. + """ + unlinkProjectV2FromTeam( + """ + Parameters for UnlinkProjectV2FromTeam + """ + input: UnlinkProjectV2FromTeamInput! + ): UnlinkProjectV2FromTeamPayload + + """ + Deletes a repository link from a project. + """ + unlinkRepositoryFromProject( + """ + Parameters for UnlinkRepositoryFromProject + """ + input: UnlinkRepositoryFromProjectInput! + ): UnlinkRepositoryFromProjectPayload + + """ + Unlock a lockable object + """ + unlockLockable( + """ + Parameters for UnlockLockable + """ + input: UnlockLockableInput! + ): UnlockLockablePayload + + """ + Unmark a discussion comment as the chosen answer for discussions in an answerable category. + """ + unmarkDiscussionCommentAsAnswer( + """ + Parameters for UnmarkDiscussionCommentAsAnswer + """ + input: UnmarkDiscussionCommentAsAnswerInput! + ): UnmarkDiscussionCommentAsAnswerPayload + + """ + Unmark a pull request file as viewed + """ + unmarkFileAsViewed( + """ + Parameters for UnmarkFileAsViewed + """ + input: UnmarkFileAsViewedInput! + ): UnmarkFileAsViewedPayload + + """ + Unmark an issue as a duplicate of another issue. + """ + unmarkIssueAsDuplicate( + """ + Parameters for UnmarkIssueAsDuplicate + """ + input: UnmarkIssueAsDuplicateInput! + ): UnmarkIssueAsDuplicatePayload + + """ + Unminimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + unminimizeComment( + """ + Parameters for UnminimizeComment + """ + input: UnminimizeCommentInput! + ): UnminimizeCommentPayload + + """ + Unpin a pinned issue from a repository + """ + unpinIssue( + """ + Parameters for UnpinIssue + """ + input: UnpinIssueInput! + ): UnpinIssuePayload + + """ + Marks a review thread as unresolved. + """ + unresolveReviewThread( + """ + Parameters for UnresolveReviewThread + """ + input: UnresolveReviewThreadInput! + ): UnresolveReviewThreadPayload + + """ + Update a branch protection rule + """ + updateBranchProtectionRule( + """ + Parameters for UpdateBranchProtectionRule + """ + input: UpdateBranchProtectionRuleInput! + ): UpdateBranchProtectionRulePayload + + """ + Update a check run + """ + updateCheckRun( + """ + Parameters for UpdateCheckRun + """ + input: UpdateCheckRunInput! + ): UpdateCheckRunPayload + + """ + Modifies the settings of an existing check suite + """ + updateCheckSuitePreferences( + """ + Parameters for UpdateCheckSuitePreferences + """ + input: UpdateCheckSuitePreferencesInput! + ): UpdateCheckSuitePreferencesPayload + + """ + Update a discussion + """ + updateDiscussion( + """ + Parameters for UpdateDiscussion + """ + input: UpdateDiscussionInput! + ): UpdateDiscussionPayload + + """ + Update the contents of a comment on a Discussion + """ + updateDiscussionComment( + """ + Parameters for UpdateDiscussionComment + """ + input: UpdateDiscussionCommentInput! + ): UpdateDiscussionCommentPayload + + """ + Updates the role of an enterprise administrator. + """ + updateEnterpriseAdministratorRole( + """ + Parameters for UpdateEnterpriseAdministratorRole + """ + input: UpdateEnterpriseAdministratorRoleInput! + ): UpdateEnterpriseAdministratorRolePayload + + """ + Sets whether private repository forks are enabled for an enterprise. + """ + updateEnterpriseAllowPrivateRepositoryForkingSetting( + """ + Parameters for UpdateEnterpriseAllowPrivateRepositoryForkingSetting + """ + input: UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput! + ): UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload + + """ + Sets the base repository permission for organizations in an enterprise. + """ + updateEnterpriseDefaultRepositoryPermissionSetting( + """ + Parameters for UpdateEnterpriseDefaultRepositoryPermissionSetting + """ + input: UpdateEnterpriseDefaultRepositoryPermissionSettingInput! + ): UpdateEnterpriseDefaultRepositoryPermissionSettingPayload + + """ + Sets whether organization members with admin permissions on a repository can change repository visibility. + """ + updateEnterpriseMembersCanChangeRepositoryVisibilitySetting( + """ + Parameters for UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting + """ + input: UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput! + ): UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload + + """ + Sets the members can create repositories setting for an enterprise. + """ + updateEnterpriseMembersCanCreateRepositoriesSetting( + """ + Parameters for UpdateEnterpriseMembersCanCreateRepositoriesSetting + """ + input: UpdateEnterpriseMembersCanCreateRepositoriesSettingInput! + ): UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload + + """ + Sets the members can delete issues setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteIssuesSetting( + """ + Parameters for UpdateEnterpriseMembersCanDeleteIssuesSetting + """ + input: UpdateEnterpriseMembersCanDeleteIssuesSettingInput! + ): UpdateEnterpriseMembersCanDeleteIssuesSettingPayload + + """ + Sets the members can delete repositories setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteRepositoriesSetting( + """ + Parameters for UpdateEnterpriseMembersCanDeleteRepositoriesSetting + """ + input: UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput! + ): UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload + + """ + Sets whether members can invite collaborators are enabled for an enterprise. + """ + updateEnterpriseMembersCanInviteCollaboratorsSetting( + """ + Parameters for UpdateEnterpriseMembersCanInviteCollaboratorsSetting + """ + input: UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput! + ): UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload + + """ + Sets whether or not an organization admin can make purchases. + """ + updateEnterpriseMembersCanMakePurchasesSetting( + """ + Parameters for UpdateEnterpriseMembersCanMakePurchasesSetting + """ + input: UpdateEnterpriseMembersCanMakePurchasesSettingInput! + ): UpdateEnterpriseMembersCanMakePurchasesSettingPayload + + """ + Sets the members can update protected branches setting for an enterprise. + """ + updateEnterpriseMembersCanUpdateProtectedBranchesSetting( + """ + Parameters for UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting + """ + input: UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput! + ): UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload + + """ + Sets the members can view dependency insights for an enterprise. + """ + updateEnterpriseMembersCanViewDependencyInsightsSetting( + """ + Parameters for UpdateEnterpriseMembersCanViewDependencyInsightsSetting + """ + input: UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput! + ): UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload + + """ + Sets whether organization projects are enabled for an enterprise. + """ + updateEnterpriseOrganizationProjectsSetting( + """ + Parameters for UpdateEnterpriseOrganizationProjectsSetting + """ + input: UpdateEnterpriseOrganizationProjectsSettingInput! + ): UpdateEnterpriseOrganizationProjectsSettingPayload + + """ + Updates the role of an enterprise owner with an organization. + """ + updateEnterpriseOwnerOrganizationRole( + """ + Parameters for UpdateEnterpriseOwnerOrganizationRole + """ + input: UpdateEnterpriseOwnerOrganizationRoleInput! + ): UpdateEnterpriseOwnerOrganizationRolePayload + + """ + Updates an enterprise's profile. + """ + updateEnterpriseProfile( + """ + Parameters for UpdateEnterpriseProfile + """ + input: UpdateEnterpriseProfileInput! + ): UpdateEnterpriseProfilePayload + + """ + Sets whether repository projects are enabled for a enterprise. + """ + updateEnterpriseRepositoryProjectsSetting( + """ + Parameters for UpdateEnterpriseRepositoryProjectsSetting + """ + input: UpdateEnterpriseRepositoryProjectsSettingInput! + ): UpdateEnterpriseRepositoryProjectsSettingPayload + + """ + Sets whether team discussions are enabled for an enterprise. + """ + updateEnterpriseTeamDiscussionsSetting( + """ + Parameters for UpdateEnterpriseTeamDiscussionsSetting + """ + input: UpdateEnterpriseTeamDiscussionsSettingInput! + ): UpdateEnterpriseTeamDiscussionsSettingPayload + + """ + Sets whether two factor authentication is required for all users in an enterprise. + """ + updateEnterpriseTwoFactorAuthenticationRequiredSetting( + """ + Parameters for UpdateEnterpriseTwoFactorAuthenticationRequiredSetting + """ + input: UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput! + ): UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload + + """ + Updates an environment. + """ + updateEnvironment( + """ + Parameters for UpdateEnvironment + """ + input: UpdateEnvironmentInput! + ): UpdateEnvironmentPayload + + """ + Sets whether an IP allow list is enabled on an owner. + """ + updateIpAllowListEnabledSetting( + """ + Parameters for UpdateIpAllowListEnabledSetting + """ + input: UpdateIpAllowListEnabledSettingInput! + ): UpdateIpAllowListEnabledSettingPayload + + """ + Updates an IP allow list entry. + """ + updateIpAllowListEntry( + """ + Parameters for UpdateIpAllowListEntry + """ + input: UpdateIpAllowListEntryInput! + ): UpdateIpAllowListEntryPayload + + """ + Sets whether IP allow list configuration for installed GitHub Apps is enabled on an owner. + """ + updateIpAllowListForInstalledAppsEnabledSetting( + """ + Parameters for UpdateIpAllowListForInstalledAppsEnabledSetting + """ + input: UpdateIpAllowListForInstalledAppsEnabledSettingInput! + ): UpdateIpAllowListForInstalledAppsEnabledSettingPayload + + """ + Updates an Issue. + """ + updateIssue( + """ + Parameters for UpdateIssue + """ + input: UpdateIssueInput! + ): UpdateIssuePayload + + """ + Updates an IssueComment object. + """ + updateIssueComment( + """ + Parameters for UpdateIssueComment + """ + input: UpdateIssueCommentInput! + ): UpdateIssueCommentPayload + + """ + Updates an existing label. + """ + updateLabel( + """ + Parameters for UpdateLabel + """ + input: UpdateLabelInput! + ): UpdateLabelPayload @preview(toggledBy: "bane-preview") + + """ + Update the setting to restrict notifications to only verified or approved domains available to an owner. + """ + updateNotificationRestrictionSetting( + """ + Parameters for UpdateNotificationRestrictionSetting + """ + input: UpdateNotificationRestrictionSettingInput! + ): UpdateNotificationRestrictionSettingPayload + + """ + Sets whether private repository forks are enabled for an organization. + """ + updateOrganizationAllowPrivateRepositoryForkingSetting( + """ + Parameters for UpdateOrganizationAllowPrivateRepositoryForkingSetting + """ + input: UpdateOrganizationAllowPrivateRepositoryForkingSettingInput! + ): UpdateOrganizationAllowPrivateRepositoryForkingSettingPayload + + """ + Sets whether contributors are required to sign off on web-based commits for repositories in an organization. + """ + updateOrganizationWebCommitSignoffSetting( + """ + Parameters for UpdateOrganizationWebCommitSignoffSetting + """ + input: UpdateOrganizationWebCommitSignoffSettingInput! + ): UpdateOrganizationWebCommitSignoffSettingPayload + + """ + Updates an existing project. + """ + updateProject( + """ + Parameters for UpdateProject + """ + input: UpdateProjectInput! + ): UpdateProjectPayload + + """ + Updates an existing project card. + """ + updateProjectCard( + """ + Parameters for UpdateProjectCard + """ + input: UpdateProjectCardInput! + ): UpdateProjectCardPayload + + """ + Updates an existing project column. + """ + updateProjectColumn( + """ + Parameters for UpdateProjectColumn + """ + input: UpdateProjectColumnInput! + ): UpdateProjectColumnPayload + + """ + Updates a draft issue within a Project. + """ + updateProjectDraftIssue( + """ + Parameters for UpdateProjectDraftIssue + """ + input: UpdateProjectDraftIssueInput! + ): UpdateProjectDraftIssuePayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Updates an existing project (beta). + """ + updateProjectNext( + """ + Parameters for UpdateProjectNext + """ + input: UpdateProjectNextInput! + ): UpdateProjectNextPayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Updates a field of an item from a Project. + """ + updateProjectNextItemField( + """ + Parameters for UpdateProjectNextItemField + """ + input: UpdateProjectNextItemFieldInput! + ): UpdateProjectNextItemFieldPayload + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Updates an existing project (beta). + """ + updateProjectV2( + """ + Parameters for UpdateProjectV2 + """ + input: UpdateProjectV2Input! + ): UpdateProjectV2Payload + + """ + Updates a draft issue within a Project. + """ + updateProjectV2DraftIssue( + """ + Parameters for UpdateProjectV2DraftIssue + """ + input: UpdateProjectV2DraftIssueInput! + ): UpdateProjectV2DraftIssuePayload + + """ + This mutation updates the value of a field for an item in a Project. Currently + only single-select, text, number, date, and iteration fields are supported. + """ + updateProjectV2ItemFieldValue( + """ + Parameters for UpdateProjectV2ItemFieldValue + """ + input: UpdateProjectV2ItemFieldValueInput! + ): UpdateProjectV2ItemFieldValuePayload + + """ + This mutation updates the position of the item in the project, where the position represents the priority of an item. + """ + updateProjectV2ItemPosition( + """ + Parameters for UpdateProjectV2ItemPosition + """ + input: UpdateProjectV2ItemPositionInput! + ): UpdateProjectV2ItemPositionPayload + + """ + Update a pull request + """ + updatePullRequest( + """ + Parameters for UpdatePullRequest + """ + input: UpdatePullRequestInput! + ): UpdatePullRequestPayload + + """ + Merge or Rebase HEAD from upstream branch into pull request branch + """ + updatePullRequestBranch( + """ + Parameters for UpdatePullRequestBranch + """ + input: UpdatePullRequestBranchInput! + ): UpdatePullRequestBranchPayload + + """ + Updates the body of a pull request review. + """ + updatePullRequestReview( + """ + Parameters for UpdatePullRequestReview + """ + input: UpdatePullRequestReviewInput! + ): UpdatePullRequestReviewPayload + + """ + Updates a pull request review comment. + """ + updatePullRequestReviewComment( + """ + Parameters for UpdatePullRequestReviewComment + """ + input: UpdatePullRequestReviewCommentInput! + ): UpdatePullRequestReviewCommentPayload + + """ + Update a Git Ref. + """ + updateRef( + """ + Parameters for UpdateRef + """ + input: UpdateRefInput! + ): UpdateRefPayload + + """ + Creates, updates and/or deletes multiple refs in a repository. + + This mutation takes a list of `RefUpdate`s and performs these updates + on the repository. All updates are performed atomically, meaning that + if one of them is rejected, no other ref will be modified. + + `RefUpdate.beforeOid` specifies that the given reference needs to point + to the given value before performing any updates. A value of + `0000000000000000000000000000000000000000` can be used to verify that + the references should not exist. + + `RefUpdate.afterOid` specifies the value that the given reference + will point to after performing all updates. A value of + `0000000000000000000000000000000000000000` can be used to delete a + reference. + + If `RefUpdate.force` is set to `true`, a non-fast-forward updates + for the given reference will be allowed. + """ + updateRefs( + """ + Parameters for UpdateRefs + """ + input: UpdateRefsInput! + ): UpdateRefsPayload @preview(toggledBy: "update-refs-preview") + + """ + Update information about a repository. + """ + updateRepository( + """ + Parameters for UpdateRepository + """ + input: UpdateRepositoryInput! + ): UpdateRepositoryPayload + + """ + Sets whether contributors are required to sign off on web-based commits for a repository. + """ + updateRepositoryWebCommitSignoffSetting( + """ + Parameters for UpdateRepositoryWebCommitSignoffSetting + """ + input: UpdateRepositoryWebCommitSignoffSettingInput! + ): UpdateRepositoryWebCommitSignoffSettingPayload + + """ + Change visibility of your sponsorship and opt in or out of email updates from the maintainer. + """ + updateSponsorshipPreferences( + """ + Parameters for UpdateSponsorshipPreferences + """ + input: UpdateSponsorshipPreferencesInput! + ): UpdateSponsorshipPreferencesPayload + + """ + Updates the state for subscribable subjects. + """ + updateSubscription( + """ + Parameters for UpdateSubscription + """ + input: UpdateSubscriptionInput! + ): UpdateSubscriptionPayload + + """ + Updates a team discussion. + """ + updateTeamDiscussion( + """ + Parameters for UpdateTeamDiscussion + """ + input: UpdateTeamDiscussionInput! + ): UpdateTeamDiscussionPayload + + """ + Updates a discussion comment. + """ + updateTeamDiscussionComment( + """ + Parameters for UpdateTeamDiscussionComment + """ + input: UpdateTeamDiscussionCommentInput! + ): UpdateTeamDiscussionCommentPayload + + """ + Updates team review assignment. + """ + updateTeamReviewAssignment( + """ + Parameters for UpdateTeamReviewAssignment + """ + input: UpdateTeamReviewAssignmentInput! + ): UpdateTeamReviewAssignmentPayload @preview(toggledBy: "stone-crop-preview") + + """ + Update team repository. + """ + updateTeamsRepository( + """ + Parameters for UpdateTeamsRepository + """ + input: UpdateTeamsRepositoryInput! + ): UpdateTeamsRepositoryPayload + + """ + Replaces the repository's topics with the given topics. + """ + updateTopics( + """ + Parameters for UpdateTopics + """ + input: UpdateTopicsInput! + ): UpdateTopicsPayload + + """ + Verify that a verifiable domain has the expected DNS record. + """ + verifyVerifiableDomain( + """ + Parameters for VerifyVerifiableDomain + """ + input: VerifyVerifiableDomainInput! + ): VerifyVerifiableDomainPayload +} + +""" +An object with an ID. +""" +interface Node { + """ + ID of the object. + """ + id: ID! +} + +""" +The possible values for the notification restriction setting. +""" +enum NotificationRestrictionSettingValue { + """ + The setting is disabled for the owner. + """ + DISABLED + + """ + The setting is enabled for the owner. + """ + ENABLED +} + +""" +An OIDC identity provider configured to provision identities for an enterprise. +""" +type OIDCProvider implements Node { + """ + The enterprise this identity provider belongs to. + """ + enterprise: Enterprise + + """ + ExternalIdentities provisioned by this identity provider. + """ + externalIdentities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter to external identities with the users login + """ + login: String + + """ + Filter to external identities with valid org membership only + """ + membersOnly: Boolean + + """ + Filter to external identities with the users userName/NameID attribute + """ + userName: String + ): ExternalIdentityConnection! + id: ID! + + """ + The OIDC identity provider type + """ + providerType: OIDCProviderType! + + """ + The id of the tenant this provider is attached to + """ + tenantId: String! +} + +""" +The OIDC identity provider type +""" +enum OIDCProviderType { + """ + Azure Active Directory + """ + AAD +} + +""" +Metadata for an audit entry with action oauth_application.* +""" +interface OauthApplicationAuditEntryData { + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI +} + +""" +Audit log entry for a oauth_application.create event. +""" +type OauthApplicationCreateAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The application URL of the OAuth Application. + """ + applicationUrl: URI + + """ + The callback URL of the OAuth Application. + """ + callbackUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The rate limit of the OAuth Application. + """ + rateLimit: Int + + """ + The state of the OAuth Application. + """ + state: OauthApplicationCreateAuditEntryState + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The state of an OAuth Application when it was created. +""" +enum OauthApplicationCreateAuditEntryState { + """ + The OAuth Application was active and allowed to have OAuth Accesses. + """ + ACTIVE + + """ + The OAuth Application was in the process of being deleted. + """ + PENDING_DELETION + + """ + The OAuth Application was suspended from generating OAuth Accesses due to abuse or security concerns. + """ + SUSPENDED +} + +""" +The corresponding operation type for the action +""" +enum OperationType { + """ + An existing resource was accessed + """ + ACCESS + + """ + A resource performed an authentication event + """ + AUTHENTICATION + + """ + A new resource was created + """ + CREATE + + """ + An existing resource was modified + """ + MODIFY + + """ + An existing resource was removed + """ + REMOVE + + """ + An existing resource was restored + """ + RESTORE + + """ + An existing resource was transferred between multiple resources + """ + TRANSFER +} + +""" +Possible directions in which to order a list of items when provided an `orderBy` argument. +""" +enum OrderDirection { + """ + Specifies an ascending order for a given `orderBy` argument. + """ + ASC + + """ + Specifies a descending order for a given `orderBy` argument. + """ + DESC +} + +""" +Audit log entry for a org.add_billing_manager +""" +type OrgAddBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The email address used to invite a billing manager for the organization. + """ + invitationEmail: String + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.add_member +""" +type OrgAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The permission level of the member added to the organization. + """ + permission: OrgAddMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgAddMemberAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + Can read and clone repositories. + """ + READ +} + +""" +Audit log entry for a org.block_user +""" +type OrgBlockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The blocked user. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.disable_collaborators_only event. +""" +type OrgConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.enable_collaborators_only event. +""" +type OrgConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.create event. +""" +type OrgCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The billing plan for the Organization. + """ + billingPlan: OrgCreateAuditEntryBillingPlan + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The billing plans available for organizations. +""" +enum OrgCreateAuditEntryBillingPlan { + """ + Team Plan + """ + BUSINESS + + """ + Enterprise Cloud Plan + """ + BUSINESS_PLUS + + """ + Free Plan + """ + FREE + + """ + Tiered Per Seat Plan + """ + TIERED_PER_SEAT + + """ + Legacy Unlimited Plan + """ + UNLIMITED +} + +""" +Audit log entry for a org.disable_oauth_app_restrictions event. +""" +type OrgDisableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_saml event. +""" +type OrgDisableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_two_factor_requirement event. +""" +type OrgDisableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_oauth_app_restrictions event. +""" +type OrgEnableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_saml event. +""" +type OrgEnableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_two_factor_requirement event. +""" +type OrgEnableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Ordering options for an organization's enterprise owner connections. +""" +input OrgEnterpriseOwnerOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order enterprise owners by. + """ + field: OrgEnterpriseOwnerOrderField! +} + +""" +Properties by which enterprise owners can be ordered. +""" +enum OrgEnterpriseOwnerOrderField { + """ + Order enterprise owners by login. + """ + LOGIN +} + +""" +Audit log entry for a org.invite_member event. +""" +type OrgInviteMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The email address of the organization invitation. + """ + email: String + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The organization invitation. + """ + organizationInvitation: OrganizationInvitation + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.invite_to_business event. +""" +type OrgInviteToBusinessAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_approved event. +""" +type OrgOauthAppAccessApprovedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_denied event. +""" +type OrgOauthAppAccessDeniedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_requested event. +""" +type OrgOauthAppAccessRequestedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.remove_billing_manager event. +""" +type OrgRemoveBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the billing manager being removed. + """ + reason: OrgRemoveBillingManagerAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reason a billing manager was removed from an Organization. +""" +enum OrgRemoveBillingManagerAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE +} + +""" +Audit log entry for a org.remove_member event. +""" +type OrgRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The types of membership the member has with the organization. + """ + membershipTypes: [OrgRemoveMemberAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the member being removed. + """ + reason: OrgRemoveMemberAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveMemberAuditEntryMembershipType { + """ + Organization administrators have full access and can change several settings, + including the names of repositories that belong to the Organization and Owners + team membership. In addition, organization admins can delete the organization + and all of its repositories. + """ + ADMIN + + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER + + """ + A direct member is a user that is a member of the Organization. + """ + DIRECT_MEMBER + + """ + An outside collaborator is a person who isn't explicitly a member of the + Organization, but who has Read, Write, or Admin permissions to one or more + repositories in the organization. + """ + OUTSIDE_COLLABORATOR + + """ + A suspended member. + """ + SUSPENDED + + """ + An unaffiliated collaborator is a person who is not a member of the + Organization and does not have access to any repositories in the Organization. + """ + UNAFFILIATED +} + +""" +The reason a member was removed from an Organization. +""" +enum OrgRemoveMemberAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY + + """ + User was removed from organization during account recovery + """ + TWO_FACTOR_ACCOUNT_RECOVERY + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE + + """ + User account has been deleted + """ + USER_ACCOUNT_DELETED +} + +""" +Audit log entry for a org.remove_outside_collaborator event. +""" +type OrgRemoveOutsideCollaboratorAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The types of membership the outside collaborator has with the organization. + """ + membershipTypes: [OrgRemoveOutsideCollaboratorAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the outside collaborator being removed from the Organization. + """ + reason: OrgRemoveOutsideCollaboratorAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryMembershipType { + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER + + """ + An outside collaborator is a person who isn't explicitly a member of the + Organization, but who has Read, Write, or Admin permissions to one or more + repositories in the organization. + """ + OUTSIDE_COLLABORATOR + + """ + An unaffiliated collaborator is a person who is not a member of the + Organization and does not have access to any repositories in the organization. + """ + UNAFFILIATED +} + +""" +The reason an outside collaborator was removed from an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE +} + +""" +Audit log entry for a org.restore_member event. +""" +type OrgRestoreMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The number of custom email routings for the restored member. + """ + restoredCustomEmailRoutingsCount: Int + + """ + The number of issue assignments for the restored member. + """ + restoredIssueAssignmentsCount: Int + + """ + Restored organization membership objects. + """ + restoredMemberships: [OrgRestoreMemberAuditEntryMembership!] + + """ + The number of restored memberships. + """ + restoredMembershipsCount: Int + + """ + The number of repositories of the restored member. + """ + restoredRepositoriesCount: Int + + """ + The number of starred repositories for the restored member. + """ + restoredRepositoryStarsCount: Int + + """ + The number of watched repositories for the restored member. + """ + restoredRepositoryWatchesCount: Int + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types of memberships that can be restored for an Organization member. +""" +union OrgRestoreMemberAuditEntryMembership = + OrgRestoreMemberMembershipOrganizationAuditEntryData + | OrgRestoreMemberMembershipRepositoryAuditEntryData + | OrgRestoreMemberMembershipTeamAuditEntryData + +""" +Metadata for an organization membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipOrganizationAuditEntryData implements OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +Metadata for a repository membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipRepositoryAuditEntryData implements RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +Metadata for a team membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipTeamAuditEntryData implements TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a org.unblock_user +""" +type OrgUnblockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The user being unblocked by the organization. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.update_default_repository_permission +""" +type OrgUpdateDefaultRepositoryPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new base repository permission level for the organization. + """ + permission: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The former base repository permission level for the organization. + """ + permissionWas: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The default permission a repository can have in an Organization. +""" +enum OrgUpdateDefaultRepositoryPermissionAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + No default permission value. + """ + NONE + + """ + Can read and clone repositories. + """ + READ + + """ + Can read, clone and push to repositories. + """ + WRITE +} + +""" +Audit log entry for a org.update_member event. +""" +type OrgUpdateMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new member permission level for the organization. + """ + permission: OrgUpdateMemberAuditEntryPermission + + """ + The former member permission level for the organization. + """ + permissionWas: OrgUpdateMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgUpdateMemberAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + Can read and clone repositories. + """ + READ +} + +""" +Audit log entry for a org.update_member_repository_creation_permission event. +""" +type OrgUpdateMemberRepositoryCreationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can members create repositories in the organization. + """ + canCreateRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The permission for visibility level of repositories for this organization. + """ + visibility: OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility +} + +""" +The permissions available for repository creation on an Organization. +""" +enum OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility { + """ + All organization members are restricted from creating any repositories. + """ + ALL + + """ + All organization members are restricted from creating internal repositories. + """ + INTERNAL + + """ + All organization members are allowed to create any repositories. + """ + NONE + + """ + All organization members are restricted from creating private repositories. + """ + PRIVATE + + """ + All organization members are restricted from creating private or internal repositories. + """ + PRIVATE_INTERNAL + + """ + All organization members are restricted from creating public repositories. + """ + PUBLIC + + """ + All organization members are restricted from creating public or internal repositories. + """ + PUBLIC_INTERNAL + + """ + All organization members are restricted from creating public or private repositories. + """ + PUBLIC_PRIVATE +} + +""" +Audit log entry for a org.update_member_repository_invitation_permission event. +""" +type OrgUpdateMemberRepositoryInvitationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can outside collaborators be invited to repositories in the organization. + """ + canInviteOutsideCollaboratorsToRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +An account on GitHub, with one or more owners, that has repositories, members and teams. +""" +type Organization implements Actor & MemberStatusable & Node & PackageOwner & ProfileOwner & ProjectNextOwner & ProjectOwner & ProjectV2Owner & ProjectV2Recent & RepositoryDiscussionAuthor & RepositoryDiscussionCommentAuthor & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + Audit log entries of the organization + """ + auditLog( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned audit log entries. + """ + orderBy: AuditLogOrder = {field: CREATED_AT, direction: DESC} + + """ + The query string to filter audit entries + """ + query: String + ): OrganizationAuditEntryConnection! + + """ + A URL pointing to the organization's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The organization's public profile description. + """ + description: String + + """ + The organization's public profile description rendered to HTML. + """ + descriptionHTML: String + + """ + A list of domains owned by the organization. + """ + domains( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter by if the domain is approved. + """ + isApproved: Boolean = null + + """ + Filter by if the domain is verified. + """ + isVerified: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for verifiable domains returned. + """ + orderBy: VerifiableDomainOrder = {field: DOMAIN, direction: ASC} + ): VerifiableDomainConnection + + """ + The organization's public email. + """ + email: String + + """ + A list of owners of the organization's enterprise account. + """ + enterpriseOwners( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for enterprise owners returned from the connection. + """ + orderBy: OrgEnterpriseOwnerOrder = {field: LOGIN, direction: ASC} + + """ + The organization role to filter by. + """ + organizationRole: RoleInOrganization + + """ + The search string to look for. + """ + query: String + ): OrganizationEnterpriseOwnerConnection! + + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + id: ID! + + """ + The interaction ability settings for this organization. + """ + interactionAbility: RepositoryInteractionAbility + + """ + The setting value for whether the organization has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the organization. + """ + ipAllowListEntries( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for IP allow list entries returned. + """ + orderBy: IpAllowListEntryOrder = {field: ALLOW_LIST_VALUE, direction: ASC} + ): IpAllowListEntryConnection! + + """ + The setting value for whether the organization has IP allow list configuration for installed GitHub Apps enabled. + """ + ipAllowListForInstalledAppsEnabledSetting: IpAllowListForInstalledAppsEnabledSettingValue! + + """ + Check if the given account is sponsoring this user/organization. + """ + isSponsoredBy( + """ + The target account's login. + """ + accountLogin: String! + ): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether the organization has verified its profile email and website. + """ + isVerified: Boolean! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The organization's public profile location. + """ + location: String + + """ + The organization's login name. + """ + login: String! + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! + + """ + Members can fork private repositories in this organization + """ + membersCanForkPrivateRepositories: Boolean! + + """ + A list of users who are members of this organization. + """ + membersWithRole( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationMemberConnection! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + The organization's public profile name. + """ + name: String + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + Indicates if email notification delivery for this organization is restricted to verified or approved domains. + """ + notificationDeliveryRestrictionEnabledSetting: NotificationRestrictionSettingValue! + + """ + The billing email for the organization. + """ + organizationBillingEmail: String + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + A list of users who have been invited to join this organization. + """ + pendingMembers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + Find a project by project (beta) number. + """ + projectNext( + """ + The project (beta) number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Find a project by number. + """ + projectV2( + """ + The project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + A list of projects (beta) under the owner. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for under the the owner. + """ + query: String + + """ + How to order the returned projects (beta). + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The HTTP path listing organization's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing organization's projects + """ + projectsUrl: URI! + + """ + A list of projects under the owner. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for under the the owner. + """ + query: String + ): ProjectV2Connection! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2Connection! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Follow repository renames. If disabled, a repository referenced by its old name will return an error. + """ + followRenames: Boolean = true + + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter discussion comments to only those that were marked as the answer + """ + onlyAnswers: Boolean = false + + """ + Filter discussion comments to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionCommentConnection! + + """ + Discussions this user has started. + """ + repositoryDiscussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Filter discussions to only those that have been answered or not. Defaults to + including both answered and unanswered discussions. + """ + answered: Boolean = null + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for discussions returned from the connection. + """ + orderBy: DiscussionOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter discussions to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionConnection! + + """ + A list of all repository migrations for this organization. + """ + repositoryMigrations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repository migrations returned. + """ + orderBy: RepositoryMigrationOrder = {field: CREATED_AT, direction: ASC} + + """ + Filter repository migrations by repository name. + """ + repositoryName: String + + """ + Filter repository migrations by state. + """ + state: MigrationState + ): RepositoryMigrationConnection! + + """ + When true the organization requires all members, billing managers, and outside + collaborators to enable two-factor authentication. + """ + requiresTwoFactorAuthentication: Boolean + + """ + The HTTP path for this organization. + """ + resourcePath: URI! + + """ + The Organization's SAML identity providers + """ + samlIdentityProvider: OrganizationIdentityProvider + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the users and organizations returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsors returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + + """ + If given, will filter for sponsors at the given tier. Will only return + sponsors whose tier the viewer is permitted to see. + """ + tierId: ID + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities( + """ + Filter activities to only the specified actions. + """ + actions: [SponsorsActivityAction!] = [] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether to include those events where this sponsorable acted as the sponsor. + Defaults to only including events where this sponsorable was the recipient + of a sponsorship. + """ + includeAsSponsor: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for activity returned from the connection. + """ + orderBy: SponsorsActivityOrder = {field: TIMESTAMP, direction: DESC} + + """ + Filter activities returned to only those that occurred in the most recent + specified time period. Set to ALL to avoid filtering by when the activity + occurred. Will be ignored if `since` or `until` is given. + """ + period: SponsorsActivityPeriod = MONTH + + """ + Filter activities to those that occurred on or after this time. + """ + since: DateTime + + """ + Filter activities to those that occurred before this time. + """ + until: DateTime + ): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the + sponsorship where you're the sponsor. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsor: Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the + sponsorship you're receiving. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsorable: Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorship updates returned from the connection. + """ + orderBy: SponsorshipNewsletterOrder = {field: CREATED_AT, direction: DESC} + ): SponsorshipNewsletterConnection! + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter sponsorships returned to those for the specified maintainers. That + is, the recipient of the sponsorship is a user or organization with one of + the given logins. + """ + maintainerLogins: [String!] + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Find an organization's team by its slug. + """ + team( + """ + The name or slug of the team to find. + """ + slug: String! + ): Team + + """ + A list of teams in this organization. + """ + teams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + If true, filters teams that are mapped to an LDAP Group (Enterprise only) + """ + ldapMapped: Boolean + + """ + Ordering options for teams returned from the connection + """ + orderBy: TeamOrder + + """ + If non-null, filters teams according to privacy + """ + privacy: TeamPrivacy + + """ + If non-null, filters teams with query on team name and team slug + """ + query: String + + """ + If non-null, filters teams according to whether the viewer is an admin or member on team + """ + role: TeamRole + + """ + If true, restrict to only root teams + """ + rootTeamsOnly: Boolean = false + + """ + User logins to filter by + """ + userLogins: [String!] + ): TeamConnection! + + """ + The HTTP path listing organization's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL listing organization's teams + """ + teamsUrl: URI! + + """ + The organization's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this organization. + """ + url: URI! + + """ + Organization is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Viewer can create repositories on this organization + """ + viewerCanCreateRepositories: Boolean! + + """ + Viewer can create teams on this organization. + """ + viewerCanCreateTeams: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Viewer is an active member of this organization. + """ + viewerIsAMember: Boolean! + + """ + Whether or not this Organization is followed by the viewer. + """ + viewerIsFollowing: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + Whether contributors are required to sign off on web-based commits for repositories in this organization. + """ + webCommitSignoffRequired: Boolean! + + """ + The organization's public profile URL. + """ + websiteUrl: URI +} + +""" +An audit entry in an organization audit log. +""" +union OrganizationAuditEntry = + MembersCanDeleteReposClearAuditEntry + | MembersCanDeleteReposDisableAuditEntry + | MembersCanDeleteReposEnableAuditEntry + | OauthApplicationCreateAuditEntry + | OrgAddBillingManagerAuditEntry + | OrgAddMemberAuditEntry + | OrgBlockUserAuditEntry + | OrgConfigDisableCollaboratorsOnlyAuditEntry + | OrgConfigEnableCollaboratorsOnlyAuditEntry + | OrgCreateAuditEntry + | OrgDisableOauthAppRestrictionsAuditEntry + | OrgDisableSamlAuditEntry + | OrgDisableTwoFactorRequirementAuditEntry + | OrgEnableOauthAppRestrictionsAuditEntry + | OrgEnableSamlAuditEntry + | OrgEnableTwoFactorRequirementAuditEntry + | OrgInviteMemberAuditEntry + | OrgInviteToBusinessAuditEntry + | OrgOauthAppAccessApprovedAuditEntry + | OrgOauthAppAccessDeniedAuditEntry + | OrgOauthAppAccessRequestedAuditEntry + | OrgRemoveBillingManagerAuditEntry + | OrgRemoveMemberAuditEntry + | OrgRemoveOutsideCollaboratorAuditEntry + | OrgRestoreMemberAuditEntry + | OrgUnblockUserAuditEntry + | OrgUpdateDefaultRepositoryPermissionAuditEntry + | OrgUpdateMemberAuditEntry + | OrgUpdateMemberRepositoryCreationPermissionAuditEntry + | OrgUpdateMemberRepositoryInvitationPermissionAuditEntry + | PrivateRepositoryForkingDisableAuditEntry + | PrivateRepositoryForkingEnableAuditEntry + | RepoAccessAuditEntry + | RepoAddMemberAuditEntry + | RepoAddTopicAuditEntry + | RepoArchivedAuditEntry + | RepoChangeMergeSettingAuditEntry + | RepoConfigDisableAnonymousGitAccessAuditEntry + | RepoConfigDisableCollaboratorsOnlyAuditEntry + | RepoConfigDisableContributorsOnlyAuditEntry + | RepoConfigDisableSockpuppetDisallowedAuditEntry + | RepoConfigEnableAnonymousGitAccessAuditEntry + | RepoConfigEnableCollaboratorsOnlyAuditEntry + | RepoConfigEnableContributorsOnlyAuditEntry + | RepoConfigEnableSockpuppetDisallowedAuditEntry + | RepoConfigLockAnonymousGitAccessAuditEntry + | RepoConfigUnlockAnonymousGitAccessAuditEntry + | RepoCreateAuditEntry + | RepoDestroyAuditEntry + | RepoRemoveMemberAuditEntry + | RepoRemoveTopicAuditEntry + | RepositoryVisibilityChangeDisableAuditEntry + | RepositoryVisibilityChangeEnableAuditEntry + | TeamAddMemberAuditEntry + | TeamAddRepositoryAuditEntry + | TeamChangeParentTeamAuditEntry + | TeamRemoveMemberAuditEntry + | TeamRemoveRepositoryAuditEntry + +""" +The connection type for OrganizationAuditEntry. +""" +type OrganizationAuditEntryConnection { + """ + A list of edges. + """ + edges: [OrganizationAuditEntryEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationAuditEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Metadata for an audit entry with action org.* +""" +interface OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +An edge in a connection. +""" +type OrganizationAuditEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationAuditEntry +} + +""" +A list of organizations managed by an enterprise. +""" +type OrganizationConnection { + """ + A list of edges. + """ + edges: [OrganizationEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization +} + +""" +The connection type for User. +""" +type OrganizationEnterpriseOwnerConnection { + """ + A list of edges. + """ + edges: [OrganizationEnterpriseOwnerEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An enterprise owner in the context of an organization that is part of the enterprise. +""" +type OrganizationEnterpriseOwnerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The role of the owner with respect to the organization. + """ + organizationRole: RoleInOrganization! +} + +""" +An Identity Provider configured to provision SAML and SCIM identities for Organizations +""" +type OrganizationIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the Identity Provider. + """ + digestMethod: URI + + """ + External Identities provisioned by this Identity Provider + """ + externalIdentities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter to external identities with the users login + """ + login: String + + """ + Filter to external identities with valid org membership only + """ + membersOnly: Boolean + + """ + Filter to external identities with the users userName/NameID attribute + """ + userName: String + ): ExternalIdentityConnection! + id: ID! + + """ + The x509 certificate used by the Identity Provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML Identity Provider + """ + issuer: String + + """ + Organization this Identity Provider belongs to + """ + organization: Organization + + """ + The signature algorithm used to sign SAML requests for the Identity Provider. + """ + signatureMethod: URI + + """ + The URL endpoint for the Identity Provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An Invitation for a user to an organization. +""" +type OrganizationInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address of the user invited to the organization. + """ + email: String + id: ID! + + """ + The type of invitation that was sent (e.g. email, user). + """ + invitationType: OrganizationInvitationType! + + """ + The user who was invited to the organization. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! + + """ + The organization the invite is for + """ + organization: Organization! + + """ + The user's pending role in the organization (e.g. member, owner). + """ + role: OrganizationInvitationRole! +} + +""" +The connection type for OrganizationInvitation. +""" +type OrganizationInvitationConnection { + """ + A list of edges. + """ + edges: [OrganizationInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +The possible organization invitation roles. +""" +enum OrganizationInvitationRole { + """ + The user is invited to be an admin of the organization. + """ + ADMIN + + """ + The user is invited to be a billing manager of the organization. + """ + BILLING_MANAGER + + """ + The user is invited to be a direct member of the organization. + """ + DIRECT_MEMBER + + """ + The user's previous role will be reinstated. + """ + REINSTATE +} + +""" +The possible organization invitation types. +""" +enum OrganizationInvitationType { + """ + The invitation was to an email address. + """ + EMAIL + + """ + The invitation was to an existing user. + """ + USER +} + +""" +The connection type for User. +""" +type OrganizationMemberConnection { + """ + A list of edges. + """ + edges: [OrganizationMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user within an organization. +""" +type OrganizationMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the organization member has two factor enabled or not. Returns null if information is not available to viewer. + """ + hasTwoFactorEnabled: Boolean + + """ + The item at the end of the edge. + """ + node: User + + """ + The role this user has in the organization. + """ + role: OrganizationMemberRole +} + +""" +The possible roles within an organization for its members. +""" +enum OrganizationMemberRole { + """ + The user is an administrator of the organization. + """ + ADMIN + + """ + The user is a member of the organization. + """ + MEMBER +} + +""" +The possible values for the members can create repositories setting on an organization. +""" +enum OrganizationMembersCanCreateRepositoriesSettingValue { + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will not be able to create public or private repositories. + """ + DISABLED + + """ + Members will be able to create only internal repositories. + """ + INTERNAL + + """ + Members will be able to create only private repositories. + """ + PRIVATE +} + +""" +Used for argument of CreateProjectV2 mutation. +""" +union OrganizationOrUser = Organization | User + +""" +Ordering options for organization connections. +""" +input OrganizationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order organizations by. + """ + field: OrganizationOrderField! +} + +""" +Properties by which organization connections can be ordered. +""" +enum OrganizationOrderField { + """ + Order organizations by creation time + """ + CREATED_AT + + """ + Order organizations by login + """ + LOGIN +} + +""" +An organization teams hovercard context +""" +type OrganizationTeamsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Teams in this organization the user is a member of that are relevant + """ + relevantTeams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + The path for the full team list for this user + """ + teamsResourcePath: URI! + + """ + The URL for the full team list for this user + """ + teamsUrl: URI! + + """ + The total number of teams the user is on in the organization + """ + totalTeamCount: Int! +} + +""" +An organization list hovercard context +""" +type OrganizationsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Organizations this user is a member of that are relevant + """ + relevantOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection! + + """ + The total number of organizations this user is in + """ + totalOrganizationCount: Int! +} + +""" +Information for an uploaded package. +""" +type Package implements Node { + id: ID! + + """ + Find the latest version for the package. + """ + latestVersion: PackageVersion + + """ + Identifies the name of the package. + """ + name: String! + + """ + Identifies the type of the package. + """ + packageType: PackageType! + + """ + The repository this package belongs to. + """ + repository: Repository + + """ + Statistics about package activity. + """ + statistics: PackageStatistics + + """ + Find package version by version string. + """ + version( + """ + The package version. + """ + version: String! + ): PackageVersion + + """ + list of versions for this package + """ + versions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering of the returned packages. + """ + orderBy: PackageVersionOrder = {field: CREATED_AT, direction: DESC} + ): PackageVersionConnection! +} + +""" +The connection type for Package. +""" +type PackageConnection { + """ + A list of edges. + """ + edges: [PackageEdge] + + """ + A list of nodes. + """ + nodes: [Package] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Package +} + +""" +A file in a package version. +""" +type PackageFile implements Node { + id: ID! + + """ + MD5 hash of the file. + """ + md5: String + + """ + Name of the file. + """ + name: String! + + """ + The package version this file belongs to. + """ + packageVersion: PackageVersion + + """ + SHA1 hash of the file. + """ + sha1: String + + """ + SHA256 hash of the file. + """ + sha256: String + + """ + Size of the file in bytes. + """ + size: Int + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + URL to download the asset. + """ + url: URI +} + +""" +The connection type for PackageFile. +""" +type PackageFileConnection { + """ + A list of edges. + """ + edges: [PackageFileEdge] + + """ + A list of nodes. + """ + nodes: [PackageFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageFile +} + +""" +Ways in which lists of package files can be ordered upon return. +""" +input PackageFileOrder { + """ + The direction in which to order package files by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order package files by. + """ + field: PackageFileOrderField +} + +""" +Properties by which package file connections can be ordered. +""" +enum PackageFileOrderField { + """ + Order package files by creation time + """ + CREATED_AT +} + +""" +Ways in which lists of packages can be ordered upon return. +""" +input PackageOrder { + """ + The direction in which to order packages by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order packages by. + """ + field: PackageOrderField +} + +""" +Properties by which package connections can be ordered. +""" +enum PackageOrderField { + """ + Order packages by creation time + """ + CREATED_AT +} + +""" +Represents an owner of a package. +""" +interface PackageOwner { + id: ID! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! +} + +""" +Represents a object that contains package activity statistics such as downloads. +""" +type PackageStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +A version tag contains the mapping between a tag name and a version. +""" +type PackageTag implements Node { + id: ID! + + """ + Identifies the tag name of the version. + """ + name: String! + + """ + Version that the tag is associated with. + """ + version: PackageVersion +} + +""" +The possible types of a package. +""" +enum PackageType { + """ + A debian package. + """ + DEBIAN + + """ + A docker image. + """ + DOCKER + @deprecated( + reason: "DOCKER will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2021-06-21 UTC." + ) + + """ + A maven package. + """ + MAVEN + + """ + An npm package. + """ + NPM + + """ + A nuget package. + """ + NUGET + + """ + A python package. + """ + PYPI + + """ + A rubygems package. + """ + RUBYGEMS +} + +""" +Information about a specific package version. +""" +type PackageVersion implements Node { + """ + List of files associated with this package version + """ + files( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering of the returned package files. + """ + orderBy: PackageFileOrder = {field: CREATED_AT, direction: ASC} + ): PackageFileConnection! + id: ID! + + """ + The package associated with this version. + """ + package: Package + + """ + The platform this version was built for. + """ + platform: String + + """ + Whether or not this version is a pre-release. + """ + preRelease: Boolean! + + """ + The README of this package version. + """ + readme: String + + """ + The release associated with this package version. + """ + release: Release + + """ + Statistics about package activity. + """ + statistics: PackageVersionStatistics + + """ + The package version summary. + """ + summary: String + + """ + The version string. + """ + version: String! +} + +""" +The connection type for PackageVersion. +""" +type PackageVersionConnection { + """ + A list of edges. + """ + edges: [PackageVersionEdge] + + """ + A list of nodes. + """ + nodes: [PackageVersion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageVersionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageVersion +} + +""" +Ways in which lists of package versions can be ordered upon return. +""" +input PackageVersionOrder { + """ + The direction in which to order package versions by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order package versions by. + """ + field: PackageVersionOrderField +} + +""" +Properties by which package version connections can be ordered. +""" +enum PackageVersionOrderField { + """ + Order package versions by creation time + """ + CREATED_AT +} + +""" +Represents a object that contains package version activity statistics such as downloads. +""" +type PackageVersionStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String + + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String +} + +""" +The possible types of patch statuses. +""" +enum PatchStatus { + """ + The file was added. Git status 'A'. + """ + ADDED + + """ + The file's type was changed. Git status 'T'. + """ + CHANGED + + """ + The file was copied. Git status 'C'. + """ + COPIED + + """ + The file was deleted. Git status 'D'. + """ + DELETED + + """ + The file's contents were changed. Git status 'M'. + """ + MODIFIED + + """ + The file was renamed. Git status 'R'. + """ + RENAMED +} + +""" +Types that can grant permissions on a repository to a user +""" +union PermissionGranter = Organization | Repository | Team + +""" +A level of permission and source for a user's access to a repository. +""" +type PermissionSource { + """ + The organization the repository belongs to. + """ + organization: Organization! + + """ + The level of access this source has granted to the user. + """ + permission: DefaultRepositoryPermissionField! + + """ + The source of this permission. + """ + source: PermissionGranter! +} + +""" +Autogenerated input type of PinIssue +""" +input PinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be pinned + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of PinIssue +""" +type PinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was pinned + """ + issue: Issue +} + +""" +Types that can be pinned to a profile page. +""" +union PinnableItem = Gist | Repository + +""" +The connection type for PinnableItem. +""" +type PinnableItemConnection { + """ + A list of edges. + """ + edges: [PinnableItemEdge] + + """ + A list of nodes. + """ + nodes: [PinnableItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnableItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnableItem +} + +""" +Represents items that can be pinned to a profile page or dashboard. +""" +enum PinnableItemType { + """ + A gist. + """ + GIST + + """ + An issue. + """ + ISSUE + + """ + An organization. + """ + ORGANIZATION + + """ + A project. + """ + PROJECT + + """ + A pull request. + """ + PULL_REQUEST + + """ + A repository. + """ + REPOSITORY + + """ + A team. + """ + TEAM + + """ + A user. + """ + USER +} + +""" +A Pinned Discussion is a discussion pinned to a repository's index page. +""" +type PinnedDiscussion implements Node & RepositoryNode { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The discussion that was pinned. + """ + discussion: Discussion! + + """ + Color stops of the chosen gradient + """ + gradientStopColors: [String!]! + id: ID! + + """ + Background texture pattern + """ + pattern: PinnedDiscussionPattern! + + """ + The actor that pinned this discussion. + """ + pinnedBy: Actor! + + """ + Preconfigured background gradient option + """ + preconfiguredGradient: PinnedDiscussionGradient + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for PinnedDiscussion. +""" +type PinnedDiscussionConnection { + """ + A list of edges. + """ + edges: [PinnedDiscussionEdge] + + """ + A list of nodes. + """ + nodes: [PinnedDiscussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedDiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedDiscussion +} + +""" +Preconfigured gradients that may be used to style discussions pinned within a repository. +""" +enum PinnedDiscussionGradient { + """ + A gradient of blue to mint + """ + BLUE_MINT + + """ + A gradient of blue to purple + """ + BLUE_PURPLE + + """ + A gradient of pink to blue + """ + PINK_BLUE + + """ + A gradient of purple to coral + """ + PURPLE_CORAL + + """ + A gradient of red to orange + """ + RED_ORANGE +} + +""" +Preconfigured background patterns that may be used to style discussions pinned within a repository. +""" +enum PinnedDiscussionPattern { + """ + An upward-facing chevron pattern + """ + CHEVRON_UP + + """ + A hollow dot pattern + """ + DOT + + """ + A solid dot pattern + """ + DOT_FILL + + """ + A heart pattern + """ + HEART_FILL + + """ + A plus sign pattern + """ + PLUS + + """ + A lightning bolt pattern + """ + ZAP +} + +""" +Represents a 'pinned' event on a given issue or pull request. +""" +type PinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +A Pinned Issue is a issue pinned to a repository's index page. +""" +type PinnedIssue implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The issue that was pinned. + """ + issue: Issue! + + """ + The actor that pinned this issue. + """ + pinnedBy: Actor! + + """ + The repository that this issue was pinned to. + """ + repository: Repository! +} + +""" +The connection type for PinnedIssue. +""" +type PinnedIssueConnection { + """ + A list of edges. + """ + edges: [PinnedIssueEdge] + + """ + A list of nodes. + """ + nodes: [PinnedIssue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedIssueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedIssue +} + +""" +An ISO-8601 encoded UTC date string with millisecond precision. +""" +scalar PreciseDateTime + +""" +Audit log entry for a private_repository_forking.disable event. +""" +type PrivateRepositoryForkingDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a private_repository_forking.enable event. +""" +type PrivateRepositoryForkingEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A curatable list of repositories relating to a repository owner, which defaults +to showing the most popular repositories they own. +""" +type ProfileItemShowcase { + """ + Whether or not the owner has pinned any repositories or gists. + """ + hasPinnedItems: Boolean! + + """ + The repositories and gists in the showcase. If the profile owner has any + pinned items, those will be returned. Otherwise, the profile owner's popular + repositories will be returned. + """ + items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PinnableItemConnection! +} + +""" +Represents any entity on GitHub that has a profile page. +""" +interface ProfileOwner { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + The public profile email. + """ + email: String + id: ID! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The public profile name. + """ + name: String + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + The public profile website URL. + """ + websiteUrl: URI +} + +""" +Projects manage issues, pull requests and notes within a project owner. +""" +type Project implements Closable & Node & Updatable { + """ + The project's description body. + """ + body: String + + """ + The projects description body rendered to HTML. + """ + bodyHTML: HTML! + + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + List of columns in the project + """ + columns( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectColumnConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who originally created the project. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project's name. + """ + name: String! + + """ + The project's number. + """ + number: Int! + + """ + The project's owner. Currently limited to repositories, organizations, and users. + """ + owner: ProjectOwner! + + """ + List of pending cards in this project + """ + pendingCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Project progress details. + """ + progress: ProjectProgress! + + """ + The HTTP path for this project + """ + resourcePath: URI! + + """ + Whether the project is open or closed. + """ + state: ProjectState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project + """ + url: URI! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +A card in a project. +""" +type ProjectCard implements Node { + """ + The project column this card is associated under. A card may only belong to one + project column at a time. The column field will be null if the card is created + in a pending state and has yet to be associated with a column. Once cards are + associated with a column, they will not become pending in the future. + """ + column: ProjectColumn + + """ + The card content item + """ + content: ProjectCardItem + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this card + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Whether the card is archived + """ + isArchived: Boolean! + + """ + The card note + """ + note: String + + """ + The project that contains this card. + """ + project: Project! + + """ + The HTTP path for this card + """ + resourcePath: URI! + + """ + The state of ProjectCard + """ + state: ProjectCardState + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this card + """ + url: URI! +} + +""" +The possible archived states of a project card. +""" +enum ProjectCardArchivedState { + """ + A project card that is archived + """ + ARCHIVED + + """ + A project card that is not archived + """ + NOT_ARCHIVED +} + +""" +The connection type for ProjectCard. +""" +type ProjectCardConnection { + """ + A list of edges. + """ + edges: [ProjectCardEdge] + + """ + A list of nodes. + """ + nodes: [ProjectCard] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectCardEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectCard +} + +""" +An issue or PR and its owning repository to be used in a project card. +""" +input ProjectCardImport { + """ + The issue or pull request number. + """ + number: Int! + + """ + Repository name with owner (owner/repository). + """ + repository: String! +} + +""" +Types that can be inside Project Cards. +""" +union ProjectCardItem = Issue | PullRequest + +""" +Various content states of a ProjectCard +""" +enum ProjectCardState { + """ + The card has content only. + """ + CONTENT_ONLY + + """ + The card has a note only. + """ + NOTE_ONLY + + """ + The card is redacted. + """ + REDACTED +} + +""" +A column inside a project. +""" +type ProjectColumn implements Node { + """ + List of cards in the column + """ + cards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project column's name. + """ + name: String! + + """ + The project that contains this column. + """ + project: Project! + + """ + The semantic purpose of the column + """ + purpose: ProjectColumnPurpose + + """ + The HTTP path for this project column + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project column + """ + url: URI! +} + +""" +The connection type for ProjectColumn. +""" +type ProjectColumnConnection { + """ + A list of edges. + """ + edges: [ProjectColumnEdge] + + """ + A list of nodes. + """ + nodes: [ProjectColumn] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectColumnEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectColumn +} + +""" +A project column and a list of its issues and PRs. +""" +input ProjectColumnImport { + """ + The name of the column. + """ + columnName: String! + + """ + A list of issues and pull requests in the column. + """ + issues: [ProjectCardImport!] + + """ + The position of the column, starting from 0. + """ + position: Int! +} + +""" +The semantic purpose of the column - todo, in progress, or done. +""" +enum ProjectColumnPurpose { + """ + The column contains cards which are complete + """ + DONE + + """ + The column contains cards which are currently being worked on + """ + IN_PROGRESS + + """ + The column contains cards still to be worked on + """ + TODO +} + +""" +A list of projects associated with the owner. +""" +type ProjectConnection { + """ + A list of edges. + """ + edges: [ProjectEdge] + + """ + A list of nodes. + """ + nodes: [Project] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Project +} + +""" +The type of a project item. +""" +enum ProjectItemType { + """ + Draft Issue + """ + DRAFT_ISSUE + + """ + Issue + """ + ISSUE + + """ + Pull Request + """ + PULL_REQUEST + + """ + Redacted Item + """ + REDACTED +} + +""" +New projects that manage issues, pull requests and drafts using tables and boards. +""" +type ProjectNext implements Closable & Node & Updatable { + """ + Returns true if the project is closed. + """ + closed: Boolean! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The actor who originally created the project. + """ + creator: Actor + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's description. + """ + description: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + List of fields in the project + """ + fields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectNextFieldConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + + """ + List of items in the project + """ + items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectNextItemConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's number. + """ + number: Int! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's owner. Currently limited to organizations and users. + """ + owner: ProjectNextOwner! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Returns true if the project is public. + """ + public: Boolean! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The repositories the project is linked to. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RepositoryConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The HTTP path for this project + """ + resourcePath: URI! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's short description. + """ + shortDescription: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's name. + """ + title: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The HTTP URL for this project + """ + url: URI! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + List of views in the project + """ + views( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectViewConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectNext. +""" +type ProjectNextConnection { + """ + A list of edges. + """ + edges: [ProjectNextEdge] + + """ + A list of nodes. + """ + nodes: [ProjectNext] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectNextEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectNext +} + +""" +A field inside a project. +""" +type ProjectNextField implements Node & ProjectNextFieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The field's type. + """ + dataType: ProjectNextFieldType! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + + """ + The project field's name. + """ + name: String! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project that contains this field. + """ + project: ProjectNext! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The field's settings. + """ + settings: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Common fields across different field types +""" +interface ProjectNextFieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The field's type. + """ + dataType: ProjectNextFieldType! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project field's name. + """ + name: String! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project that contains this field. + """ + project: ProjectNext! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The field's settings. + """ + settings: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectNextField. +""" +type ProjectNextFieldConnection { + """ + A list of edges. + """ + edges: [ProjectNextFieldEdge] + + """ + A list of nodes. + """ + nodes: [ProjectNextField] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectNextFieldEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectNextField +} + +""" +The type of a project next field. +""" +enum ProjectNextFieldType { + """ + Assignees + """ + ASSIGNEES + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Date + """ + DATE + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Iteration + """ + ITERATION + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Labels + """ + LABELS + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Linked Pull Requests + """ + LINKED_PULL_REQUESTS + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Milestone + """ + MILESTONE + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Number + """ + NUMBER + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Repository + """ + REPOSITORY + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Reviewers + """ + REVIEWERS + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Single Select + """ + SINGLE_SELECT + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Text + """ + TEXT + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Title + """ + TITLE + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Tracked by + """ + TRACKED_BY + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Tracks + """ + TRACKS + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +An item within a new Project. +""" +type ProjectNextItem implements Node { + """ + The content of the referenced draft issue, issue, or pull request + """ + content: ProjectNextItemContent + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The actor who created the item. + """ + creator: Actor + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + List of field values + """ + fieldValues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectNextItemFieldValueConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + + """ + Whether the item is archived. + """ + isArchived: Boolean! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project that contains this item. + """ + project: ProjectNext! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The title of the item + """ + title: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The type of the item. + """ + type: ProjectItemType! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectNextItem. +""" +type ProjectNextItemConnection { + """ + A list of edges. + """ + edges: [ProjectNextItemEdge] + + """ + A list of nodes. + """ + nodes: [ProjectNextItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Types that can be inside Project Items. +""" +union ProjectNextItemContent = DraftIssue | Issue | PullRequest + +""" +An edge in a connection. +""" +type ProjectNextItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectNextItem +} + +""" +An value of a field in an item of a new Project. +""" +type ProjectNextItemFieldValue implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The actor who created the item. + """ + creator: Actor + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + + """ + The project field that contains this value. + """ + projectField: ProjectNextField! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project item that contains this value. + """ + projectItem: ProjectNextItem! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The value of a field + """ + value: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectNextItemFieldValue. +""" +type ProjectNextItemFieldValueConnection { + """ + A list of edges. + """ + edges: [ProjectNextItemFieldValueEdge] + + """ + A list of nodes. + """ + nodes: [ProjectNextItemFieldValue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectNextItemFieldValueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectNextItemFieldValue +} + +""" +Properties by which the return project can be ordered. +""" +enum ProjectNextOrderField { + """ + The project's date and time of creation + """ + CREATED_AT + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's number + """ + NUMBER + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's title + """ + TITLE + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project's date and time of update + """ + UPDATED_AT + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Represents an owner of a project (beta). +""" +interface ProjectNextOwner { + id: ID! + + """ + Find a project by project (beta) number. + """ + projectNext( + """ + The project (beta) number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + A list of projects (beta) under the owner. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for under the the owner. + """ + query: String + + """ + How to order the returned projects (beta). + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Ways in which lists of projects can be ordered upon return. +""" +input ProjectOrder { + """ + The direction in which to order projects by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order projects by. + """ + field: ProjectOrderField! +} + +""" +Properties by which project connections can be ordered. +""" +enum ProjectOrderField { + """ + Order projects by creation time + """ + CREATED_AT + + """ + Order projects by name + """ + NAME + + """ + Order projects by update time + """ + UPDATED_AT +} + +""" +Represents an owner of a Project. +""" +interface ProjectOwner { + id: ID! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + The HTTP path listing owners projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing owners projects + """ + projectsUrl: URI! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! +} + +""" +Project progress stats. +""" +type ProjectProgress { + """ + The number of done cards. + """ + doneCount: Int! + + """ + The percentage of done cards. + """ + donePercentage: Float! + + """ + Whether progress tracking is enabled and cards with purpose exist for this project + """ + enabled: Boolean! + + """ + The number of in-progress cards. + """ + inProgressCount: Int! + + """ + The percentage of in-progress cards. + """ + inProgressPercentage: Float! + + """ + The number of to do cards. + """ + todoCount: Int! + + """ + The percentage of to do cards. + """ + todoPercentage: Float! +} + +""" +State of the project; either 'open' or 'closed' +""" +enum ProjectState { + """ + The project is closed. + """ + CLOSED + + """ + The project is open. + """ + OPEN +} + +""" +GitHub-provided templates for Projects +""" +enum ProjectTemplate { + """ + Create a board with v2 triggers to automatically move cards across To do, In progress and Done columns. + """ + AUTOMATED_KANBAN_V2 + + """ + Create a board with triggers to automatically move cards across columns with review automation. + """ + AUTOMATED_REVIEWS_KANBAN + + """ + Create a board with columns for To do, In progress and Done. + """ + BASIC_KANBAN + + """ + Create a board to triage and prioritize bugs with To do, priority, and Done columns. + """ + BUG_TRIAGE +} + +""" +New projects that manage issues, pull requests and drafts using tables and boards. +""" +type ProjectV2 implements Closable & Node & Updatable { + """ + Returns true if the project is closed. + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who originally created the project. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + A field of the project + """ + field( + """ + The name of the field + """ + name: String! + ): ProjectV2FieldConfiguration + + """ + List of fields and their constraints in the project + """ + fields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for project v2 fields returned from the connection + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConfigurationConnection! + id: ID! + + """ + List of items in the project + """ + items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for project v2 items returned from the connection + """ + orderBy: ProjectV2ItemOrder = {field: POSITION, direction: ASC} + ): ProjectV2ItemConnection! + + """ + The project's number. + """ + number: Int! + + """ + The project's owner. Currently limited to organizations and users. + """ + owner: ProjectV2Owner! + + """ + Returns true if the project is public. + """ + public: Boolean! + + """ + The project's readme. + """ + readme: String + + """ + The repositories the project is linked to. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder = {field: CREATED_AT, direction: DESC} + ): RepositoryConnection! + + """ + The HTTP path for this project + """ + resourcePath: URI! + + """ + The project's short description. + """ + shortDescription: String + + """ + The teams the project is linked to. + """ + teams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for teams returned from this connection. + """ + orderBy: TeamOrder = {field: NAME, direction: ASC} + ): TeamConnection! + + """ + The project's name. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project + """ + url: URI! + + """ + A view of the project + """ + view( + """ + The number of a view belonging to the project + """ + number: Int! + ): ProjectV2View + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + List of views in the project + """ + views( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for project v2 views returned from the connection + """ + orderBy: ProjectV2ViewOrder = {field: POSITION, direction: ASC} + ): ProjectV2ViewConnection! +} + +""" +The connection type for ProjectV2. +""" +type ProjectV2Connection { + """ + A list of edges. + """ + edges: [ProjectV2Edge] + + """ + A list of nodes. + """ + nodes: [ProjectV2] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2Edge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2 +} + +""" +A field inside a project. +""" +type ProjectV2Field implements Node & ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Common fields across different project field types +""" +interface ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Configurations for project fields. +""" +union ProjectV2FieldConfiguration = ProjectV2Field | ProjectV2IterationField | ProjectV2SingleSelectField + +""" +The connection type for ProjectV2FieldConfiguration. +""" +type ProjectV2FieldConfigurationConnection { + """ + A list of edges. + """ + edges: [ProjectV2FieldConfigurationEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2FieldConfiguration] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2FieldConfigurationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2FieldConfiguration +} + +""" +The connection type for ProjectV2Field. +""" +type ProjectV2FieldConnection { + """ + A list of edges. + """ + edges: [ProjectV2FieldEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Field] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2FieldEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Field +} + +""" +Ordering options for project v2 field connections +""" +input ProjectV2FieldOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order the project v2 fields by. + """ + field: ProjectV2FieldOrderField! +} + +""" +Properties by which project v2 field connections can be ordered. +""" +enum ProjectV2FieldOrderField { + """ + Order project v2 fields by creation time + """ + CREATED_AT + + """ + Order project v2 fields by name + """ + NAME + + """ + Order project v2 fields by position + """ + POSITION +} + +""" +The type of a project field. +""" +enum ProjectV2FieldType { + """ + Assignees + """ + ASSIGNEES + + """ + Date + """ + DATE + + """ + Iteration + """ + ITERATION + + """ + Labels + """ + LABELS + + """ + Linked Pull Requests + """ + LINKED_PULL_REQUESTS + + """ + Milestone + """ + MILESTONE + + """ + Number + """ + NUMBER + + """ + Repository + """ + REPOSITORY + + """ + Reviewers + """ + REVIEWERS + + """ + Single Select + """ + SINGLE_SELECT + + """ + Text + """ + TEXT + + """ + Title + """ + TITLE + + """ + Tracked by + """ + TRACKED_BY + + """ + Tracks + """ + TRACKS +} + +""" +The values that can be used to update a field of an item inside a Project. Only 1 value can be updated at a time. +""" +input ProjectV2FieldValue { + """ + The ISO 8601 date to set on the field. + """ + date: Date + + """ + The id of the iteration to set on the field. + """ + iterationId: String + + """ + The number to set on the field. + """ + number: Float + + """ + The id of the single select option to set on the field. + """ + singleSelectOptionId: String + + """ + The text to set on the field. + """ + text: String +} + +""" +Ways in which to filter lists of projects. +""" +input ProjectV2Filters { + """ + List project v2 filtered by the state given. + """ + state: ProjectV2State +} + +""" +An item within a Project. +""" +type ProjectV2Item implements Node { + """ + The content of the referenced draft issue, issue, or pull request + """ + content: ProjectV2ItemContent + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + A specific field value given a field name + """ + fieldValueByName( + """ + The name of the field to return the field value of + """ + name: String! + ): ProjectV2ItemFieldValue + + """ + List of field values + """ + fieldValues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for project v2 item field values returned from the connection + """ + orderBy: ProjectV2ItemFieldValueOrder = {field: POSITION, direction: ASC} + ): ProjectV2ItemFieldValueConnection! + id: ID! + + """ + Whether the item is archived. + """ + isArchived: Boolean! + + """ + The project that contains this item. + """ + project: ProjectV2! + + """ + The type of the item. + """ + type: ProjectV2ItemType! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2Item. +""" +type ProjectV2ItemConnection { + """ + A list of edges. + """ + edges: [ProjectV2ItemEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Item] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Types that can be inside Project Items. +""" +union ProjectV2ItemContent = DraftIssue | Issue | PullRequest + +""" +An edge in a connection. +""" +type ProjectV2ItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Item +} + +""" +The value of a date field in a Project item. +""" +type ProjectV2ItemFieldDateValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Date value for the field + """ + date: Date + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of an iteration field in a Project item. +""" +type ProjectV2ItemFieldIterationValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The duration of the iteration in days. + """ + duration: Int! + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + The ID of the iteration. + """ + iterationId: String! + + """ + The start date of the iteration. + """ + startDate: Date! + + """ + The title of the iteration. + """ + title: String! + + """ + The title of the iteration, with HTML. + """ + titleHTML: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of the labels field in a Project item. +""" +type ProjectV2ItemFieldLabelValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + Labels value of a field + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): LabelConnection +} + +""" +The value of a milestone field in a Project item. +""" +type ProjectV2ItemFieldMilestoneValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + Milestone value of a field + """ + milestone: Milestone +} + +""" +The value of a number field in a Project item. +""" +type ProjectV2ItemFieldNumberValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Number as a float(8) + """ + number: Float + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a pull request field in a Project item. +""" +type ProjectV2ItemFieldPullRequestValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The pull requests for this field + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests. + """ + orderBy: PullRequestOrder = {field: CREATED_AT, direction: ASC} + ): PullRequestConnection +} + +""" +The value of a repository field in a Project item. +""" +type ProjectV2ItemFieldRepositoryValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The repository for this field. + """ + repository: Repository +} + +""" +The value of a reviewers field in a Project item. +""" +type ProjectV2ItemFieldReviewerValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The reviewers for this field. + """ + reviewers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RequestedReviewerConnection +} + +""" +The value of a single select field in a Project item. +""" +type ProjectV2ItemFieldSingleSelectValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + The name of the selected single select option. + """ + name: String + + """ + The html name of the selected single select option. + """ + nameHTML: String + + """ + The id of the selected single select option. + """ + optionId: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a text field in a Project item. +""" +type ProjectV2ItemFieldTextValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Text value of a field + """ + text: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a user field in a Project item. +""" +type ProjectV2ItemFieldUserValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The users for this field + """ + users( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection +} + +""" +Project field values +""" +union ProjectV2ItemFieldValue = + ProjectV2ItemFieldDateValue + | ProjectV2ItemFieldIterationValue + | ProjectV2ItemFieldLabelValue + | ProjectV2ItemFieldMilestoneValue + | ProjectV2ItemFieldNumberValue + | ProjectV2ItemFieldPullRequestValue + | ProjectV2ItemFieldRepositoryValue + | ProjectV2ItemFieldReviewerValue + | ProjectV2ItemFieldSingleSelectValue + | ProjectV2ItemFieldTextValue + | ProjectV2ItemFieldUserValue + +""" +Common fields across different project field value types +""" +interface ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2ItemFieldValue. +""" +type ProjectV2ItemFieldValueConnection { + """ + A list of edges. + """ + edges: [ProjectV2ItemFieldValueEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2ItemFieldValue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2ItemFieldValueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2ItemFieldValue +} + +""" +Ordering options for project v2 item field value connections +""" +input ProjectV2ItemFieldValueOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order the project v2 item field values by. + """ + field: ProjectV2ItemFieldValueOrderField! +} + +""" +Properties by which project v2 item field value connections can be ordered. +""" +enum ProjectV2ItemFieldValueOrderField { + """ + Order project v2 item field values by the their position in the project + """ + POSITION +} + +""" +Ordering options for project v2 item connections +""" +input ProjectV2ItemOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order the project v2 items by. + """ + field: ProjectV2ItemOrderField! +} + +""" +Properties by which project v2 item connections can be ordered. +""" +enum ProjectV2ItemOrderField { + """ + Order project v2 items by the their position in the project + """ + POSITION +} + +""" +The type of a project item. +""" +enum ProjectV2ItemType { + """ + Draft Issue + """ + DRAFT_ISSUE + + """ + Issue + """ + ISSUE + + """ + Pull Request + """ + PULL_REQUEST + + """ + Redacted Item + """ + REDACTED +} + +""" +An iteration field inside a project. +""" +type ProjectV2IterationField implements Node & ProjectV2FieldCommon { + """ + Iteration configuration settings + """ + configuration: ProjectV2IterationFieldConfiguration! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Iteration field configuration for a project. +""" +type ProjectV2IterationFieldConfiguration { + """ + The iteration's completed iterations + """ + completedIterations: [ProjectV2IterationFieldIteration!]! + + """ + The iteration's duration in days + """ + duration: Int! + + """ + The iteration's iterations + """ + iterations: [ProjectV2IterationFieldIteration!]! + + """ + The iteration's start day of the week + """ + startDay: Int! +} + +""" +Iteration field iteration settings for a project. +""" +type ProjectV2IterationFieldIteration { + """ + The iteration's duration in days + """ + duration: Int! + + """ + The iteration's ID. + """ + id: String! + + """ + The iteration's start date + """ + startDate: Date! + + """ + The iteration's title. + """ + title: String! + + """ + The iteration's html title. + """ + titleHTML: String! +} + +""" +Ways in which lists of projects can be ordered upon return. +""" +input ProjectV2Order { + """ + The direction in which to order projects by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order projects by. + """ + field: ProjectV2OrderField! +} + +""" +Properties by which projects can be ordered. +""" +enum ProjectV2OrderField { + """ + The project's date and time of creation + """ + CREATED_AT + + """ + The project's number + """ + NUMBER + + """ + The project's title + """ + TITLE + + """ + The project's date and time of update + """ + UPDATED_AT +} + +""" +Represents an owner of a project (beta). +""" +interface ProjectV2Owner { + id: ID! + + """ + Find a project by number. + """ + projectV2( + """ + The project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects under the owner. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for under the the owner. + """ + query: String + ): ProjectV2Connection! +} + +""" +Recent projects for the owner. +""" +interface ProjectV2Recent { + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2Connection! +} + +""" +A single select field inside a project. +""" +type ProjectV2SingleSelectField implements Node & ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + Options for the single select field + """ + options: [ProjectV2SingleSelectFieldOption!]! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Single select field option for a configuration for a project. +""" +type ProjectV2SingleSelectFieldOption { + """ + The option's ID. + """ + id: String! + + """ + The option's name. + """ + name: String! + + """ + The option's html name. + """ + nameHTML: String! +} + +""" +Represents a sort by field and direction. +""" +type ProjectV2SortBy { + """ + The direction of the sorting. Possible values are ASC and DESC. + """ + direction: OrderDirection! + + """ + The field by which items are sorted. + """ + field: ProjectV2Field! +} + +""" +The connection type for ProjectV2SortBy. +""" +type ProjectV2SortByConnection { + """ + A list of edges. + """ + edges: [ProjectV2SortByEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2SortBy] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2SortByEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2SortBy +} + +""" +Represents a sort by field and direction. +""" +type ProjectV2SortByField { + """ + The direction of the sorting. Possible values are ASC and DESC. + """ + direction: OrderDirection! + + """ + The field by which items are sorted. + """ + field: ProjectV2FieldConfiguration! +} + +""" +The connection type for ProjectV2SortByField. +""" +type ProjectV2SortByFieldConnection { + """ + A list of edges. + """ + edges: [ProjectV2SortByFieldEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2SortByField] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2SortByFieldEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2SortByField +} + +""" +The possible states of a project v2. +""" +enum ProjectV2State { + """ + A project v2 that has been closed + """ + CLOSED + + """ + A project v2 that is still open + """ + OPEN +} + +""" +A view within a ProjectV2. +""" +type ProjectV2View implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The view's visible fields. + """ + fields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConfigurationConnection + + """ + The project view's filter. + """ + filter: String + + """ + The view's group-by field. + """ + groupBy( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConnection + @deprecated( + reason: "The `ProjectV2View#order_by` API is deprecated in favour of the more capable `ProjectV2View#group_by_field` API. Check out the `ProjectV2View#group_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC." + ) + + """ + The view's group-by field. + """ + groupByFields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConfigurationConnection + id: ID! + + """ + The project view's layout. + """ + layout: ProjectV2ViewLayout! + + """ + The project view's name. + """ + name: String! + + """ + The project view's number. + """ + number: Int! + + """ + The project that contains this view. + """ + project: ProjectV2! + + """ + The view's sort-by config. + """ + sortBy( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2SortByConnection + @deprecated( + reason: "The `ProjectV2View#sort_by` API is deprecated in favour of the more capable `ProjectV2View#sort_by_fields` API. Check out the `ProjectV2View#sort_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC." + ) + + """ + The view's sort-by config. + """ + sortByFields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2SortByFieldConnection + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The view's vertical-group-by field. + """ + verticalGroupBy( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConnection + @deprecated( + reason: "The `ProjectV2View#vertical_group_by` API is deprecated in favour of the more capable `ProjectV2View#vertical_group_by_fields` API. Check out the `ProjectV2View#vertical_group_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC." + ) + + """ + The view's vertical-group-by field. + """ + verticalGroupByFields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConfigurationConnection + + """ + The view's visible fields. + """ + visibleFields( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the project v2 fields returned from the connection. + """ + orderBy: ProjectV2FieldOrder = {field: POSITION, direction: ASC} + ): ProjectV2FieldConnection + @deprecated( + reason: "The `ProjectV2View#visibleFields` API is deprecated in favour of the more capable `ProjectV2View#fields` API. Check out the `ProjectV2View#fields` API as an example for the more capable alternative. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectV2View. +""" +type ProjectV2ViewConnection { + """ + A list of edges. + """ + edges: [ProjectV2ViewEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2View] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2ViewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2View +} + +""" +The layout of a project v2 view. +""" +enum ProjectV2ViewLayout { + """ + Board layout + """ + BOARD_LAYOUT + + """ + Table layout + """ + TABLE_LAYOUT +} + +""" +Ordering options for project v2 view connections +""" +input ProjectV2ViewOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order the project v2 views by. + """ + field: ProjectV2ViewOrderField! +} + +""" +Properties by which project v2 view connections can be ordered. +""" +enum ProjectV2ViewOrderField { + """ + Order project v2 views by creation time + """ + CREATED_AT + + """ + Order project v2 views by name + """ + NAME + + """ + Order project v2 views by position + """ + POSITION +} + +""" +A view within a Project. +""" +type ProjectView implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the primary key from the database. + """ + databaseId: Int + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project view's filter. + """ + filter: String + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The view's group-by field. + """ + groupBy: [Int!] + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + id: ID! + + """ + The project view's layout. + """ + layout: ProjectViewLayout! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project view's name. + """ + name: String! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project view's number. + """ + number: Int! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The project that contains this view. + """ + project: ProjectNext! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The view's sort-by config. + """ + sortBy: [SortBy!] + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The view's vertical-group-by field. + """ + verticalGroupBy: [Int!] + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The view's visible fields. + """ + visibleFields: [Int!] + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +The connection type for ProjectView. +""" +type ProjectViewConnection { + """ + A list of edges. + """ + edges: [ProjectViewEdge] + + """ + A list of nodes. + """ + nodes: [ProjectView] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectViewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectView +} + +""" +The layout of a project view. +""" +enum ProjectViewLayout { + """ + Board layout + """ + BOARD_LAYOUT + + """ + Table layout + """ + TABLE_LAYOUT +} + +""" +A user's public key. +""" +type PublicKey implements Node { + """ + The last time this authorization was used to perform an action. Values will be null for keys not owned by the user. + """ + accessedAt: DateTime + + """ + Identifies the date and time when the key was created. Keys created before + March 5th, 2014 have inaccurate values. Values will be null for keys not owned by the user. + """ + createdAt: DateTime + + """ + The fingerprint for this PublicKey. + """ + fingerprint: String! + id: ID! + + """ + Whether this PublicKey is read-only or not. Values will be null for keys not owned by the user. + """ + isReadOnly: Boolean + + """ + The public key string. + """ + key: String! + + """ + Identifies the date and time when the key was updated. Keys created before + March 5th, 2014 may have inaccurate values. Values will be null for keys not + owned by the user. + """ + updatedAt: DateTime +} + +""" +The connection type for PublicKey. +""" +type PublicKeyConnection { + """ + A list of edges. + """ + edges: [PublicKeyEdge] + + """ + A list of nodes. + """ + nodes: [PublicKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PublicKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PublicKey +} + +""" +A repository pull request. +""" +type PullRequest implements Assignable & Closable & Comment & Labelable & Lockable & Node & ProjectNextOwner & ProjectV2Owner & Reactable & RepositoryNode & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + The number of additions in this pull request. + """ + additions: Int! + + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Returns the auto-merge request object if one exists for this pull request. + """ + autoMergeRequest: AutoMergeRequest + + """ + Identifies the base Ref associated with the pull request. + """ + baseRef: Ref + + """ + Identifies the name of the base Ref associated with the pull request, even if the ref has been deleted. + """ + baseRefName: String! + + """ + Identifies the oid of the base ref associated with the pull request, even if the ref has been deleted. + """ + baseRefOid: GitObjectID! + + """ + The repository associated with this pull request's base Ref. + """ + baseRepository: Repository + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Whether or not the pull request is rebaseable. + """ + canBeRebased: Boolean! @preview(toggledBy: "merge-info-preview") + + """ + The number of changed files in this pull request. + """ + changedFiles: Int! + + """ + The HTTP path for the checks of this pull request. + """ + checksResourcePath: URI! + + """ + The HTTP URL for the checks of this pull request. + """ + checksUrl: URI! + + """ + `true` if the pull request is closed + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + List of issues that were may be closed by this pull request + """ + closingIssuesReferences( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection + """ + orderBy: IssueOrder + + """ + Return only manually linked Issues + """ + userLinkedOnly: Boolean = false + ): IssueConnection + + """ + A list of comments associated with the pull request. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + A list of commits present in this pull request's head branch not present in the base branch. + """ + commits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestCommitConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The number of deletions in this pull request. + """ + deletions: Int! + + """ + The actor who edited this pull request's body. + """ + editor: Actor + + """ + Lists the files changed within this pull request. + """ + files( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestChangedFileConnection + + """ + Identifies the head Ref associated with the pull request. + """ + headRef: Ref + + """ + Identifies the name of the head Ref associated with the pull request, even if the ref has been deleted. + """ + headRefName: String! + + """ + Identifies the oid of the head ref associated with the pull request, even if the ref has been deleted. + """ + headRefOid: GitObjectID! + + """ + The repository associated with this pull request's head Ref. + """ + headRepository: Repository + + """ + The owner of the repository associated with this pull request's head Ref. + """ + headRepositoryOwner: RepositoryOwner + + """ + The hovercard information for this issue + """ + hovercard( + """ + Whether or not to include notification contexts + """ + includeNotificationContexts: Boolean = true + ): Hovercard! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The head and base repositories are different. + """ + isCrossRepository: Boolean! + + """ + Identifies if the pull request is a draft. + """ + isDraft: Boolean! + + """ + Is this pull request read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + A list of latest reviews per user associated with the pull request. + """ + latestOpinionatedReviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Only return reviews from user who have write access to the repository + """ + writersOnly: Boolean = false + ): PullRequestReviewConnection + + """ + A list of latest reviews per user associated with the pull request that are not also pending review. + """ + latestReviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewConnection + + """ + `true` if the pull request is locked + """ + locked: Boolean! + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean! + + """ + The commit that was created when this pull request was merged. + """ + mergeCommit: Commit + + """ + Detailed information about the current pull request merge state status. + """ + mergeStateStatus: MergeStateStatus! @preview(toggledBy: "merge-info-preview") + + """ + Whether or not the pull request can be merged based on the existence of merge conflicts. + """ + mergeable: MergeableState! + + """ + Whether or not the pull request was merged. + """ + merged: Boolean! + + """ + The date and time that the pull request was merged. + """ + mergedAt: DateTime + + """ + The actor who merged the pull request. + """ + mergedBy: Actor + + """ + Identifies the milestone associated with the pull request. + """ + milestone: Milestone + + """ + Identifies the pull request number. + """ + number: Int! + + """ + A list of Users that are participating in the Pull Request conversation. + """ + participants( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The permalink to the pull request. + """ + permalink: URI! + + """ + The commit that GitHub automatically generated to test if this pull request + could be merged. This field will not return a value if the pull request is + merged, or if the test merge commit is still being generated. See the + `mergeable` field for more details on the mergeability of the pull request. + """ + potentialMergeCommit: Commit + + """ + List of project cards associated with this pull request. + """ + projectCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + List of project items associated with this pull request. + """ + projectItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Include archived items. + """ + includeArchived: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2ItemConnection! + + """ + Find a project by project (beta) number. + """ + projectNext( + """ + The project (beta) number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + List of project (beta) items associated with this pull request. + """ + projectNextItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Include archived items. + """ + includeArchived: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectNextItemConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Find a project by number. + """ + projectV2( + """ + The project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects (beta) under the owner. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for under the the owner. + """ + query: String + + """ + How to order the returned projects (beta). + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + A list of projects under the owner. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for under the the owner. + """ + query: String + ): ProjectV2Connection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + The HTTP path for reverting this pull request. + """ + revertResourcePath: URI! + + """ + The HTTP URL for reverting this pull request. + """ + revertUrl: URI! + + """ + The current status of this pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision + + """ + A list of review requests associated with the pull request. + """ + reviewRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReviewRequestConnection + + """ + The list of all review threads for this pull request. + """ + reviewThreads( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewThreadConnection! + + """ + A list of reviews associated with the pull request. + """ + reviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Filter by author of the review. + """ + author: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A list of states to filter the reviews. + """ + states: [PullRequestReviewState!] + ): PullRequestReviewConnection + + """ + Identifies the state of the pull request. + """ + state: PullRequestState! + + """ + A list of reviewer suggestions based on commit history and past review comments. + """ + suggestedReviewers: [SuggestedReviewer]! + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timeline( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows filtering timeline events by a `since` timestamp. + """ + since: DateTime + ): PullRequestTimelineConnection! + @deprecated(reason: "`timeline` will be removed Use PullRequest.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timelineItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter timeline items by type. + """ + itemTypes: [PullRequestTimelineItemsItemType!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter timeline items by a `since` timestamp. + """ + since: DateTime + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): PullRequestTimelineItemsConnection! + + """ + Identifies the pull request title. + """ + title: String! + + """ + Identifies the pull request title rendered to HTML. + """ + titleHTML: HTML! + + """ + Returns a count of how many comments this pull request has received. + """ + totalCommentsCount: Int + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Whether or not the viewer can apply suggestion. + """ + viewerCanApplySuggestion: Boolean! + + """ + Check if the viewer can restore the deleted head ref. + """ + viewerCanDeleteHeadRef: Boolean! + + """ + Whether or not the viewer can disable auto-merge + """ + viewerCanDisableAutoMerge: Boolean! + + """ + Can the viewer edit files within this pull request. + """ + viewerCanEditFiles: Boolean! + + """ + Whether or not the viewer can enable auto-merge + """ + viewerCanEnableAutoMerge: Boolean! + + """ + Indicates whether the viewer can bypass branch protections and merge the pull request immediately + """ + viewerCanMergeAsAdmin: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + The latest review given from the viewer. + """ + viewerLatestReview: PullRequestReview + + """ + The person who has requested the viewer for review on this pull request. + """ + viewerLatestReviewRequest: ReviewRequest + + """ + The merge body text for the viewer and method. + """ + viewerMergeBodyText( + """ + The merge method for the message. + """ + mergeType: PullRequestMergeMethod + ): String! + + """ + The merge headline text for the viewer and method. + """ + viewerMergeHeadlineText( + """ + The merge method for the message. + """ + mergeType: PullRequestMergeMethod + ): String! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A file changed in a pull request. +""" +type PullRequestChangedFile { + """ + The number of additions to the file. + """ + additions: Int! + + """ + How the file was changed in this PullRequest + """ + changeType: PatchStatus! + + """ + The number of deletions to the file. + """ + deletions: Int! + + """ + The path of the file. + """ + path: String! + + """ + The state of the file for the viewer. + """ + viewerViewedState: FileViewedState! +} + +""" +The connection type for PullRequestChangedFile. +""" +type PullRequestChangedFileConnection { + """ + A list of edges. + """ + edges: [PullRequestChangedFileEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestChangedFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestChangedFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestChangedFile +} + +""" +Represents a Git commit part of a pull request. +""" +type PullRequestCommit implements Node & UniformResourceLocatable { + """ + The Git commit object + """ + commit: Commit! + id: ID! + + """ + The pull request this commit belongs to + """ + pullRequest: PullRequest! + + """ + The HTTP path for this pull request commit + """ + resourcePath: URI! + + """ + The HTTP URL for this pull request commit + """ + url: URI! +} + +""" +Represents a commit comment thread part of a pull request. +""" +type PullRequestCommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit! + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The pull request this commit comment thread belongs to + """ + pullRequest: PullRequest! + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for PullRequestCommit. +""" +type PullRequestCommitConnection { + """ + A list of edges. + """ + edges: [PullRequestCommitEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestCommit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestCommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestCommit +} + +""" +The connection type for PullRequest. +""" +type PullRequestConnection { + """ + A list of edges. + """ + edges: [PullRequestEdge] + + """ + A list of nodes. + """ + nodes: [PullRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull requests opened by a user within one repository. +""" +type PullRequestContributionsByRepository { + """ + The pull request contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestContributionConnection! + + """ + The repository in which the pull requests were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type PullRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequest +} + +""" +Represents available types of methods to use when merging a pull request. +""" +enum PullRequestMergeMethod { + """ + Add all commits from the head branch to the base branch with a merge commit. + """ + MERGE + + """ + Add all commits from the head branch onto the base branch individually. + """ + REBASE + + """ + Combine all commits from the head branch into a single commit in the base branch. + """ + SQUASH +} + +""" +Ways in which lists of issues can be ordered upon return. +""" +input PullRequestOrder { + """ + The direction in which to order pull requests by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order pull requests by. + """ + field: PullRequestOrderField! +} + +""" +Properties by which pull_requests connections can be ordered. +""" +enum PullRequestOrderField { + """ + Order pull_requests by creation time + """ + CREATED_AT + + """ + Order pull_requests by update time + """ + UPDATED_AT +} + +""" +A review object for a given pull request. +""" +type PullRequestReview implements Comment & Deletable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Indicates whether the author of this review has push access to the repository. + """ + authorCanPushToRepository: Boolean! + + """ + Identifies the pull request review body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body of this review rendered as plain text. + """ + bodyText: String! + + """ + A list of review comments for the current pull request review. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewCommentConnection! + + """ + Identifies the commit associated with this pull request review. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + A list of teams that this review was made on behalf of. + """ + onBehalfOf( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the pull request associated with this pull request review. + """ + pullRequest: PullRequest! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this PullRequestReview. + """ + resourcePath: URI! + + """ + Identifies the current state of the pull request review. + """ + state: PullRequestReviewState! + + """ + Identifies when the Pull Request Review was submitted + """ + submittedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this PullRequestReview. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A review comment associated with a given repository pull request. +""" +type PullRequestReviewComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The comment body of this review comment. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The comment body of this review comment rendered as plain text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment. + """ + commit: Commit + + """ + Identifies when the comment was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The diff hunk to which the comment applies. + """ + diffHunk: String! + + """ + Identifies when the comment was created in a draft state. + """ + draftedAt: DateTime! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, + `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and + formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies the original commit associated with the comment. + """ + originalCommit: Commit + + """ + The original line index in the diff to which the comment applies. + """ + originalPosition: Int! + + """ + Identifies when the comment body is outdated + """ + outdated: Boolean! + + """ + The path to which the comment applies. + """ + path: String! + + """ + The line index in the diff to which the comment applies. + """ + position: Int + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + The pull request associated with this review comment. + """ + pullRequest: PullRequest! + + """ + The pull request review associated with this review comment. + """ + pullRequestReview: PullRequestReview + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The comment this is a reply to. + """ + replyTo: PullRequestReviewComment + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this review comment. + """ + resourcePath: URI! + + """ + Identifies the state of the comment. + """ + state: PullRequestReviewCommentState! + + """ + Identifies when the comment was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this review comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for PullRequestReviewComment. +""" +type PullRequestReviewCommentConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewCommentEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewComment +} + +""" +The possible states of a pull request review comment. +""" +enum PullRequestReviewCommentState { + """ + A comment that is part of a pending review + """ + PENDING + + """ + A comment that is part of a submitted review + """ + SUBMITTED +} + +""" +The connection type for PullRequestReview. +""" +type PullRequestReviewConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReview] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull request reviews made by a user within one repository. +""" +type PullRequestReviewContributionsByRepository { + """ + The pull request review contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestReviewContributionConnection! + + """ + The repository in which the pull request reviews were made. + """ + repository: Repository! +} + +""" +The review status of a pull request. +""" +enum PullRequestReviewDecision { + """ + The pull request has received an approving review. + """ + APPROVED + + """ + Changes have been requested on the pull request. + """ + CHANGES_REQUESTED + + """ + A review is required before the pull request can be merged. + """ + REVIEW_REQUIRED +} + +""" +An edge in a connection. +""" +type PullRequestReviewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReview +} + +""" +The possible events to perform on a pull request review. +""" +enum PullRequestReviewEvent { + """ + Submit feedback and approve merging these changes. + """ + APPROVE + + """ + Submit general feedback without explicit approval. + """ + COMMENT + + """ + Dismiss review so it now longer effects merging. + """ + DISMISS + + """ + Submit feedback that must be addressed before merging. + """ + REQUEST_CHANGES +} + +""" +The possible states of a pull request review. +""" +enum PullRequestReviewState { + """ + A review allowing the pull request to merge. + """ + APPROVED + + """ + A review blocking the pull request from merging. + """ + CHANGES_REQUESTED + + """ + An informational review. + """ + COMMENTED + + """ + A review that has been dismissed. + """ + DISMISSED + + """ + A review that has not yet been submitted. + """ + PENDING +} + +""" +A threaded list of comments for a given pull request. +""" +type PullRequestReviewThread implements Node { + """ + A list of pull request comments associated with the thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): PullRequestReviewCommentConnection! + + """ + The side of the diff on which this thread was placed. + """ + diffSide: DiffSide! + id: ID! + + """ + Whether or not the thread has been collapsed (resolved) + """ + isCollapsed: Boolean! + + """ + Indicates whether this thread was outdated by newer changes. + """ + isOutdated: Boolean! + + """ + Whether this thread has been resolved + """ + isResolved: Boolean! + + """ + The line in the file to which this thread refers + """ + line: Int + + """ + The original line in the file to which this thread refers. + """ + originalLine: Int + + """ + The original start line in the file to which this thread refers (multi-line only). + """ + originalStartLine: Int + + """ + Identifies the file path of this thread. + """ + path: String! + + """ + Identifies the pull request associated with this thread. + """ + pullRequest: PullRequest! + + """ + Identifies the repository associated with this thread. + """ + repository: Repository! + + """ + The user who resolved this thread + """ + resolvedBy: User + + """ + The side of the diff that the first line of the thread starts on (multi-line only) + """ + startDiffSide: DiffSide + + """ + The start line in the file to which this thread refers (multi-line only) + """ + startLine: Int + + """ + Indicates whether the current viewer can reply to this thread. + """ + viewerCanReply: Boolean! + + """ + Whether or not the viewer can resolve this thread + """ + viewerCanResolve: Boolean! + + """ + Whether or not the viewer can unresolve this thread + """ + viewerCanUnresolve: Boolean! +} + +""" +Review comment threads for a pull request review. +""" +type PullRequestReviewThreadConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewThreadEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewThread] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewThreadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewThread +} + +""" +Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. +""" +type PullRequestRevisionMarker { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The last commit the viewer has seen. + """ + lastSeenCommit: Commit! + + """ + The pull request to which the marker belongs. + """ + pullRequest: PullRequest! +} + +""" +The possible states of a pull request. +""" +enum PullRequestState { + """ + A pull request that has been closed without being merged. + """ + CLOSED + + """ + A pull request that has been closed by being merged. + """ + MERGED + + """ + A pull request that is still open. + """ + OPEN +} + +""" +A repository pull request template. +""" +type PullRequestTemplate { + """ + The body of the template + """ + body: String + + """ + The filename of the template + """ + filename: String + + """ + The repository the template belongs to + """ + repository: Repository! +} + +""" +A threaded list of comments for a given pull request. +""" +type PullRequestThread implements Node { + """ + A list of pull request comments associated with the thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): PullRequestReviewCommentConnection! + + """ + The side of the diff on which this thread was placed. + """ + diffSide: DiffSide! + id: ID! + + """ + Whether or not the thread has been collapsed (resolved) + """ + isCollapsed: Boolean! + + """ + Indicates whether this thread was outdated by newer changes. + """ + isOutdated: Boolean! + + """ + Whether this thread has been resolved + """ + isResolved: Boolean! + + """ + The line in the file to which this thread refers + """ + line: Int + + """ + Identifies the pull request associated with this thread. + """ + pullRequest: PullRequest! + + """ + Identifies the repository associated with this thread. + """ + repository: Repository! + + """ + The user who resolved this thread + """ + resolvedBy: User + + """ + The side of the diff that the first line of the thread starts on (multi-line only) + """ + startDiffSide: DiffSide + + """ + The line of the first file diff in the thread. + """ + startLine: Int + + """ + Indicates whether the current viewer can reply to this thread. + """ + viewerCanReply: Boolean! + + """ + Whether or not the viewer can resolve this thread + """ + viewerCanResolve: Boolean! + + """ + Whether or not the viewer can unresolve this thread + """ + viewerCanUnresolve: Boolean! +} + +""" +The connection type for PullRequestTimelineItem. +""" +type PullRequestTimelineConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItem = + AssignedEvent + | BaseRefDeletedEvent + | BaseRefForcePushedEvent + | ClosedEvent + | Commit + | CommitCommentThread + | CrossReferencedEvent + | DemilestonedEvent + | DeployedEvent + | DeploymentEnvironmentChangedEvent + | HeadRefDeletedEvent + | HeadRefForcePushedEvent + | HeadRefRestoredEvent + | IssueComment + | LabeledEvent + | LockedEvent + | MergedEvent + | MilestonedEvent + | PullRequestReview + | PullRequestReviewComment + | PullRequestReviewThread + | ReferencedEvent + | RenamedTitleEvent + | ReopenedEvent + | ReviewDismissedEvent + | ReviewRequestRemovedEvent + | ReviewRequestedEvent + | SubscribedEvent + | UnassignedEvent + | UnlabeledEvent + | UnlockedEvent + | UnsubscribedEvent + | UserBlockedEvent + +""" +An edge in a connection. +""" +type PullRequestTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItem +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItems = + AddedToProjectEvent + | AssignedEvent + | AutoMergeDisabledEvent + | AutoMergeEnabledEvent + | AutoRebaseEnabledEvent + | AutoSquashEnabledEvent + | AutomaticBaseChangeFailedEvent + | AutomaticBaseChangeSucceededEvent + | BaseRefChangedEvent + | BaseRefDeletedEvent + | BaseRefForcePushedEvent + | ClosedEvent + | CommentDeletedEvent + | ConnectedEvent + | ConvertToDraftEvent + | ConvertedNoteToIssueEvent + | ConvertedToDiscussionEvent + | CrossReferencedEvent + | DemilestonedEvent + | DeployedEvent + | DeploymentEnvironmentChangedEvent + | DisconnectedEvent + | HeadRefDeletedEvent + | HeadRefForcePushedEvent + | HeadRefRestoredEvent + | IssueComment + | LabeledEvent + | LockedEvent + | MarkedAsDuplicateEvent + | MentionedEvent + | MergedEvent + | MilestonedEvent + | MovedColumnsInProjectEvent + | PinnedEvent + | PullRequestCommit + | PullRequestCommitCommentThread + | PullRequestReview + | PullRequestReviewThread + | PullRequestRevisionMarker + | ReadyForReviewEvent + | ReferencedEvent + | RemovedFromProjectEvent + | RenamedTitleEvent + | ReopenedEvent + | ReviewDismissedEvent + | ReviewRequestRemovedEvent + | ReviewRequestedEvent + | SubscribedEvent + | TransferredEvent + | UnassignedEvent + | UnlabeledEvent + | UnlockedEvent + | UnmarkedAsDuplicateEvent + | UnpinnedEvent + | UnsubscribedEvent + | UserBlockedEvent + +""" +The connection type for PullRequestTimelineItems. +""" +type PullRequestTimelineItemsConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type PullRequestTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum PullRequestTimelineItemsItemType { + """ + Represents an 'added_to_merge_queue' event on a given pull request. + """ + ADDED_TO_MERGE_QUEUE_EVENT + + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'automatic_base_change_failed' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_FAILED_EVENT + + """ + Represents a 'automatic_base_change_succeeded' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_SUCCEEDED_EVENT + + """ + Represents a 'auto_merge_disabled' event on a given pull request. + """ + AUTO_MERGE_DISABLED_EVENT + + """ + Represents a 'auto_merge_enabled' event on a given pull request. + """ + AUTO_MERGE_ENABLED_EVENT + + """ + Represents a 'auto_rebase_enabled' event on a given pull request. + """ + AUTO_REBASE_ENABLED_EVENT + + """ + Represents a 'auto_squash_enabled' event on a given pull request. + """ + AUTO_SQUASH_ENABLED_EVENT + + """ + Represents a 'base_ref_changed' event on a given issue or pull request. + """ + BASE_REF_CHANGED_EVENT + + """ + Represents a 'base_ref_deleted' event on a given pull request. + """ + BASE_REF_DELETED_EVENT + + """ + Represents a 'base_ref_force_pushed' event on a given pull request. + """ + BASE_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a 'converted_to_discussion' event on a given issue. + """ + CONVERTED_TO_DISCUSSION_EVENT + + """ + Represents a 'convert_to_draft' event on a given pull request. + """ + CONVERT_TO_DRAFT_EVENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'deployed' event on a given pull request. + """ + DEPLOYED_EVENT + + """ + Represents a 'deployment_environment_changed' event on a given pull request. + """ + DEPLOYMENT_ENVIRONMENT_CHANGED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a 'head_ref_deleted' event on a given pull request. + """ + HEAD_REF_DELETED_EVENT + + """ + Represents a 'head_ref_force_pushed' event on a given pull request. + """ + HEAD_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'head_ref_restored' event on a given pull request. + """ + HEAD_REF_RESTORED_EVENT + + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'merged' event on a given pull request. + """ + MERGED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a Git commit part of a pull request. + """ + PULL_REQUEST_COMMIT + + """ + Represents a commit comment thread part of a pull request. + """ + PULL_REQUEST_COMMIT_COMMENT_THREAD + + """ + A review object for a given pull request. + """ + PULL_REQUEST_REVIEW + + """ + A threaded list of comments for a given pull request. + """ + PULL_REQUEST_REVIEW_THREAD + + """ + Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. + """ + PULL_REQUEST_REVISION_MARKER + + """ + Represents a 'ready_for_review' event on a given pull request. + """ + READY_FOR_REVIEW_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_merge_queue' event on a given pull request. + """ + REMOVED_FROM_MERGE_QUEUE_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'review_dismissed' event on a given issue or pull request. + """ + REVIEW_DISMISSED_EVENT + + """ + Represents an 'review_requested' event on a given pull request. + """ + REVIEW_REQUESTED_EVENT + + """ + Represents an 'review_request_removed' event on a given pull request. + """ + REVIEW_REQUEST_REMOVED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT +} + +""" +The possible target states when updating a pull request. +""" +enum PullRequestUpdateState { + """ + A pull request that has been closed without being merged. + """ + CLOSED + + """ + A pull request that is still open. + """ + OPEN +} + +""" +A Git push. +""" +type Push implements Node { + id: ID! + + """ + The SHA after the push + """ + nextSha: GitObjectID + + """ + The permalink for this push. + """ + permalink: URI! + + """ + The SHA before the push + """ + previousSha: GitObjectID + + """ + The actor who pushed + """ + pusher: Actor! + + """ + The repository that was pushed to + """ + repository: Repository! +} + +""" +A team, user, or app who has the ability to push to a protected branch. +""" +type PushAllowance implements Node { + """ + The actor that can push. + """ + actor: PushAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +Types that can be an actor. +""" +union PushAllowanceActor = App | Team | User + +""" +The connection type for PushAllowance. +""" +type PushAllowanceConnection { + """ + A list of edges. + """ + edges: [PushAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [PushAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PushAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PushAllowance +} + +""" +The query root of GitHub's GraphQL interface. +""" +type Query { + """ + Look up a code of conduct by its key + """ + codeOfConduct( + """ + The code of conduct's key + """ + key: String! + ): CodeOfConduct + + """ + Look up a code of conduct by its key + """ + codesOfConduct: [CodeOfConduct] + + """ + Look up an enterprise by URL slug. + """ + enterprise( + """ + The enterprise invitation token. + """ + invitationToken: String + + """ + The enterprise URL slug. + """ + slug: String! + ): Enterprise + + """ + Look up a pending enterprise administrator invitation by invitee, enterprise and role. + """ + enterpriseAdministratorInvitation( + """ + The slug of the enterprise the user was invited to join. + """ + enterpriseSlug: String! + + """ + The role for the business member invitation. + """ + role: EnterpriseAdministratorRole! + + """ + The login of the user invited to join the business. + """ + userLogin: String! + ): EnterpriseAdministratorInvitation + + """ + Look up a pending enterprise administrator invitation by invitation token. + """ + enterpriseAdministratorInvitationByToken( + """ + The invitation token sent with the invitation email. + """ + invitationToken: String! + ): EnterpriseAdministratorInvitation + + """ + Look up an open source license by its key + """ + license( + """ + The license's downcased SPDX ID + """ + key: String! + ): License + + """ + Return a list of known open source licenses + """ + licenses: [License]! + + """ + Get alphabetically sorted list of Marketplace categories + """ + marketplaceCategories( + """ + Exclude categories with no listings. + """ + excludeEmpty: Boolean + + """ + Returns top level categories only, excluding any subcategories. + """ + excludeSubcategories: Boolean + + """ + Return only the specified categories. + """ + includeCategories: [String!] + ): [MarketplaceCategory!]! + + """ + Look up a Marketplace category by its slug. + """ + marketplaceCategory( + """ + The URL slug of the category. + """ + slug: String! + + """ + Also check topic aliases for the category slug + """ + useTopicAliases: Boolean + ): MarketplaceCategory + + """ + Look up a single Marketplace listing + """ + marketplaceListing( + """ + Select the listing that matches this slug. It's the short name of the listing used in its URL. + """ + slug: String! + ): MarketplaceListing + + """ + Look up Marketplace listings + """ + marketplaceListings( + """ + Select listings that can be administered by the specified user. + """ + adminId: ID + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Select listings visible to the viewer even if they are not approved. If omitted or + false, only approved listings will be returned. + """ + allStates: Boolean + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Select only listings with the given category. + """ + categorySlug: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Select listings for products owned by the specified organization. + """ + organizationId: ID + + """ + Select only listings where the primary category matches the given category slug. + """ + primaryCategoryOnly: Boolean = false + + """ + Select the listings with these slugs, if they are visible to the viewer. + """ + slugs: [String] + + """ + Also check topic aliases for the category slug + """ + useTopicAliases: Boolean + + """ + Select listings to which user has admin access. If omitted, listings visible to the + viewer are returned. + """ + viewerCanAdmin: Boolean + + """ + Select only listings that offer a free trial. + """ + withFreeTrialsOnly: Boolean = false + ): MarketplaceListingConnection! + + """ + Return information about the GitHub instance + """ + meta: GitHubMetadata! + + """ + Fetches an object given its ID. + """ + node( + """ + ID of the object. + """ + id: ID! + ): Node + + """ + Lookup nodes by a list of IDs. + """ + nodes( + """ + The list of node IDs. + """ + ids: [ID!]! + ): [Node]! + + """ + Lookup a organization by login. + """ + organization( + """ + The organization's login. + """ + login: String! + ): Organization + + """ + The client's rate limit information. + """ + rateLimit( + """ + If true, calculate the cost for the query without evaluating it + """ + dryRun: Boolean = false + ): RateLimit + + """ + Hack to workaround https://github.com/facebook/relay/issues/112 re-exposing the root query object + """ + relay: Query! + + """ + Lookup a given repository by the owner and repository name. + """ + repository( + """ + Follow repository renames. If disabled, a repository referenced by its old name will return an error. + """ + followRenames: Boolean = true + + """ + The name of the repository + """ + name: String! + + """ + The login field of a user or organization + """ + owner: String! + ): Repository + + """ + Lookup a repository owner (ie. either a User or an Organization) by login. + """ + repositoryOwner( + """ + The username to lookup the owner by. + """ + login: String! + ): RepositoryOwner + + """ + Lookup resource by a URL. + """ + resource( + """ + The URL. + """ + url: URI! + ): UniformResourceLocatable + + """ + Perform a search across resources, returning a maximum of 1,000 results. + """ + search( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The search string to look for. + """ + query: String! + + """ + The types of search items to search within. + """ + type: SearchType! + ): SearchResultItemConnection! + + """ + GitHub Security Advisories + """ + securityAdvisories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + A list of classifications to filter advisories by. + """ + classifications: [SecurityAdvisoryClassification!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter advisories by identifier, e.g. GHSA or CVE. + """ + identifier: SecurityAdvisoryIdentifierFilter + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityAdvisoryOrder = {field: UPDATED_AT, direction: DESC} + + """ + Filter advisories to those published since a time in the past. + """ + publishedSince: DateTime + + """ + Filter advisories to those updated since a time in the past. + """ + updatedSince: DateTime + ): SecurityAdvisoryConnection! + + """ + Fetch a Security Advisory by its GHSA ID + """ + securityAdvisory( + """ + GitHub Security Advisory ID. + """ + ghsaId: String! + ): SecurityAdvisory + + """ + Software Vulnerabilities documented by GitHub Security Advisories + """ + securityVulnerabilities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + A list of advisory classifications to filter vulnerabilities by. + """ + classifications: [SecurityAdvisoryClassification!] + + """ + An ecosystem to filter vulnerabilities by. + """ + ecosystem: SecurityAdvisoryEcosystem + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityVulnerabilityOrder = {field: UPDATED_AT, direction: DESC} + + """ + A package name to filter vulnerabilities by. + """ + package: String + + """ + A list of severities to filter vulnerabilities by. + """ + severities: [SecurityAdvisorySeverity!] + ): SecurityVulnerabilityConnection! + + """ + Users and organizations who can be sponsored via GitHub Sponsors. + """ + sponsorables( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Optional filter for which dependencies should be checked for sponsorable + owners. Only sponsorable owners of dependencies in this ecosystem will be + included. Used when onlyDependencies = true. + + **Upcoming Change on 2022-07-01 UTC** + **Description:** `dependencyEcosystem` will be removed. Use the ecosystem argument instead. + **Reason:** The type is switching from SecurityAdvisoryEcosystem to DependencyGraphEcosystem. + """ + dependencyEcosystem: SecurityAdvisoryEcosystem + + """ + Optional filter for which dependencies should be checked for sponsorable + owners. Only sponsorable owners of dependencies in this ecosystem will be + included. Used when onlyDependencies = true. + """ + ecosystem: DependencyGraphEcosystem + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Whether only sponsorables who own the viewer's dependencies will be + returned. Must be authenticated to use. Can check an organization instead + for their dependencies owned by sponsorables by passing + orgLoginForDependencies. + """ + onlyDependencies: Boolean = false + + """ + Ordering options for users and organizations returned from the connection. + """ + orderBy: SponsorableOrder = {field: LOGIN, direction: ASC} + + """ + Optional organization username for whose dependencies should be checked. + Used when onlyDependencies = true. Omit to check your own dependencies. If + you are not an administrator of the organization, only dependencies from its + public repositories will be considered. + """ + orgLoginForDependencies: String + ): SponsorableItemConnection! + + """ + Look up a topic by name. + """ + topic( + """ + The topic's name. + """ + name: String! + ): Topic + + """ + Lookup a user by login. + """ + user( + """ + The user's login. + """ + login: String! + ): User + + """ + The currently authenticated user. + """ + viewer: User! +} + +""" +Represents the client's rate limit. +""" +type RateLimit { + """ + The point cost for the current query counting against the rate limit. + """ + cost: Int! + + """ + The maximum number of points the client is permitted to consume in a 60 minute window. + """ + limit: Int! + + """ + The maximum number of nodes this query may return + """ + nodeCount: Int! + + """ + The number of points remaining in the current rate limit window. + """ + remaining: Int! + + """ + The time at which the current rate limit window resets in UTC epoch seconds. + """ + resetAt: DateTime! + + """ + The number of points used in the current rate limit window. + """ + used: Int! +} + +""" +Represents a subject that can be reacted on. +""" +interface Reactable { + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! +} + +""" +The connection type for User. +""" +type ReactingUserConnection { + """ + A list of edges. + """ + edges: [ReactingUserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's made a reaction. +""" +type ReactingUserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + The moment when the user made the reaction. + """ + reactedAt: DateTime! +} + +""" +An emoji reaction to a particular piece of content. +""" +type Reaction implements Node { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The reactable piece of content + """ + reactable: Reactable! + + """ + Identifies the user who created this reaction. + """ + user: User +} + +""" +A list of reactions that have been left on the subject. +""" +type ReactionConnection { + """ + A list of edges. + """ + edges: [ReactionEdge] + + """ + A list of nodes. + """ + nodes: [Reaction] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Emojis that can be attached to Issues, Pull Requests and Comments. +""" +enum ReactionContent { + """ + Represents the `:confused:` emoji. + """ + CONFUSED + + """ + Represents the `:eyes:` emoji. + """ + EYES + + """ + Represents the `:heart:` emoji. + """ + HEART + + """ + Represents the `:hooray:` emoji. + """ + HOORAY + + """ + Represents the `:laugh:` emoji. + """ + LAUGH + + """ + Represents the `:rocket:` emoji. + """ + ROCKET + + """ + Represents the `:-1:` emoji. + """ + THUMBS_DOWN + + """ + Represents the `:+1:` emoji. + """ + THUMBS_UP +} + +""" +An edge in a connection. +""" +type ReactionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Reaction +} + +""" +A group of emoji reactions to a particular piece of content. +""" +type ReactionGroup { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies when the reaction was created. + """ + createdAt: DateTime + + """ + Reactors to the reaction subject with the emotion represented by this reaction group. + """ + reactors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReactorConnection! + + """ + The subject that was reacted to. + """ + subject: Reactable! + + """ + Users who have reacted to the reaction subject with the emotion represented by this reaction group + """ + users( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReactingUserConnection! + @deprecated( + reason: "Reactors can now be mannequins, bots, and organizations. Use the `reactors` field instead. Removal on 2021-10-01 UTC." + ) + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Ways in which lists of reactions can be ordered upon return. +""" +input ReactionOrder { + """ + The direction in which to order reactions by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order reactions by. + """ + field: ReactionOrderField! +} + +""" +A list of fields that reactions can be ordered by. +""" +enum ReactionOrderField { + """ + Allows ordering a list of reactions by when they were created. + """ + CREATED_AT +} + +""" +Types that can be assigned to reactions. +""" +union Reactor = Bot | Mannequin | Organization | User + +""" +The connection type for Reactor. +""" +type ReactorConnection { + """ + A list of edges. + """ + edges: [ReactorEdge] + + """ + A list of nodes. + """ + nodes: [Reactor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents an author of a reaction. +""" +type ReactorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The author of the reaction. + """ + node: Reactor! + + """ + The moment when the user made the reaction. + """ + reactedAt: DateTime! +} + +""" +Represents a 'ready_for_review' event on a given pull request. +""" +type ReadyForReviewEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this ready for review event. + """ + resourcePath: URI! + + """ + The HTTP URL for this ready for review event. + """ + url: URI! +} + +""" +Represents a Git reference. +""" +type Ref implements Node { + """ + A list of pull requests with this ref as the head ref. + """ + associatedPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + Branch protection rules for this ref + """ + branchProtectionRule: BranchProtectionRule + + """ + Compares the current ref as a base ref to another head ref, if the comparison can be made. + """ + compare( + """ + The head ref to compare against. + """ + headRef: String! + ): Comparison + id: ID! + + """ + The ref name. + """ + name: String! + + """ + The ref's prefix, such as `refs/heads/` or `refs/tags/`. + """ + prefix: String! + + """ + Branch protection rules that are viewable by non-admins + """ + refUpdateRule: RefUpdateRule + + """ + The repository the ref belongs to. + """ + repository: Repository! + + """ + The object the ref points to. Returns null when object does not exist. + """ + target: GitObject +} + +""" +The connection type for Ref. +""" +type RefConnection { + """ + A list of edges. + """ + edges: [RefEdge] + + """ + A list of nodes. + """ + nodes: [Ref] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RefEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Ref +} + +""" +Ways in which lists of git refs can be ordered upon return. +""" +input RefOrder { + """ + The direction in which to order refs by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order refs by. + """ + field: RefOrderField! +} + +""" +Properties by which ref connections can be ordered. +""" +enum RefOrderField { + """ + Order refs by their alphanumeric name + """ + ALPHABETICAL + + """ + Order refs by underlying commit date if the ref prefix is refs/tags/ + """ + TAG_COMMIT_DATE +} + +""" +A ref update +""" +input RefUpdate @preview(toggledBy: "update-refs-preview") { + """ + The value this ref should be updated to. + """ + afterOid: GitObjectID! + + """ + The value this ref needs to point to before the update. + """ + beforeOid: GitObjectID + + """ + Force a non fast-forward update. + """ + force: Boolean = false + + """ + The fully qualified name of the ref to be update. For example `refs/heads/branch-name` + """ + name: GitRefname! +} + +""" +A ref update rules for a viewer. +""" +type RefUpdateRule { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + Can matching branches be created. + """ + blocksCreations: Boolean! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean! + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean! + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are commits required to be signed. + """ + requiresSignatures: Boolean! + + """ + Is the viewer allowed to dismiss reviews. + """ + viewerAllowedToDismissReviews: Boolean! + + """ + Can the viewer push to the branch + """ + viewerCanPush: Boolean! +} + +""" +Represents a 'referenced' event on a given `ReferencedSubject`. +""" +type ReferencedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the 'referenced' event. + """ + commit: Commit + + """ + Identifies the repository associated with the 'referenced' event. + """ + commitRepository: Repository! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Checks if the commit message itself references the subject. Can be false in the case of a commit comment reference. + """ + isDirectReference: Boolean! + + """ + Object referenced by event. + """ + subject: ReferencedSubject! +} + +""" +Any referencable object +""" +union ReferencedSubject = Issue | PullRequest + +""" +Autogenerated input type of RegenerateEnterpriseIdentityProviderRecoveryCodes +""" +input RegenerateEnterpriseIdentityProviderRecoveryCodesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) +} + +""" +Autogenerated return type of RegenerateEnterpriseIdentityProviderRecoveryCodes +""" +type RegenerateEnterpriseIdentityProviderRecoveryCodesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RegenerateVerifiableDomainToken +""" +input RegenerateVerifiableDomainTokenInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to regenerate the verification token of. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of RegenerateVerifiableDomainToken +""" +type RegenerateVerifiableDomainTokenPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verification token that was generated. + """ + verificationToken: String +} + +""" +Autogenerated input type of RejectDeployments +""" +input RejectDeploymentsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Optional comment for rejecting deployments + """ + comment: String = "" + + """ + The ids of environments to reject deployments + """ + environmentIds: [ID!]! + + """ + The node ID of the workflow run containing the pending deployments. + """ + workflowRunId: ID! @possibleTypes(concreteTypes: ["WorkflowRun"]) +} + +""" +Autogenerated return type of RejectDeployments +""" +type RejectDeploymentsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The affected deployments. + """ + deployments: [Deployment!] +} + +""" +A release contains the content for a release. +""" +type Release implements Node & Reactable & UniformResourceLocatable { + """ + The author of the release + """ + author: User + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the release. + """ + description: String + + """ + The description of this release rendered to HTML. + """ + descriptionHTML: HTML + id: ID! + + """ + Whether or not the release is a draft + """ + isDraft: Boolean! + + """ + Whether or not the release is the latest releast + """ + isLatest: Boolean! + + """ + Whether or not the release is a prerelease + """ + isPrerelease: Boolean! + + """ + A list of users mentioned in the release description + """ + mentions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection + + """ + The title of the release. + """ + name: String + + """ + Identifies the date and time when the release was created. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + List of releases assets which are dependent on this release. + """ + releaseAssets( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A list of names to filter the assets by. + """ + name: String + ): ReleaseAssetConnection! + + """ + The repository that the release belongs to. + """ + repository: Repository! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + A description of the release, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML + + """ + The Git tag the release points to + """ + tag: Ref + + """ + The tag commit for this release. + """ + tagCommit: Commit + + """ + The name of the release's Git tag + """ + tagName: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! +} + +""" +A release asset contains the content for a release asset. +""" +type ReleaseAsset implements Node { + """ + The asset's content-type + """ + contentType: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The number of times this asset was downloaded + """ + downloadCount: Int! + + """ + Identifies the URL where you can download the release asset via the browser. + """ + downloadUrl: URI! + id: ID! + + """ + Identifies the title of the release asset. + """ + name: String! + + """ + Release that the asset is associated with + """ + release: Release + + """ + The size (in bytes) of the asset + """ + size: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user that performed the upload + """ + uploadedBy: User! + + """ + Identifies the URL of the release asset. + """ + url: URI! +} + +""" +The connection type for ReleaseAsset. +""" +type ReleaseAssetConnection { + """ + A list of edges. + """ + edges: [ReleaseAssetEdge] + + """ + A list of nodes. + """ + nodes: [ReleaseAsset] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseAssetEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReleaseAsset +} + +""" +The connection type for Release. +""" +type ReleaseConnection { + """ + A list of edges. + """ + edges: [ReleaseEdge] + + """ + A list of nodes. + """ + nodes: [Release] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Release +} + +""" +Ways in which lists of releases can be ordered upon return. +""" +input ReleaseOrder { + """ + The direction in which to order releases by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order releases by. + """ + field: ReleaseOrderField! +} + +""" +Properties by which release connections can be ordered. +""" +enum ReleaseOrderField { + """ + Order releases by creation time + """ + CREATED_AT + + """ + Order releases alphabetically by name + """ + NAME +} + +""" +Autogenerated input type of RemoveAssigneesFromAssignable +""" +input RemoveAssigneesFromAssignableInput { + """ + The id of the assignable object to remove assignees from. + """ + assignableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Assignable") + + """ + The id of users to remove as assignees. + """ + assigneeIds: [ID!]! @possibleTypes(concreteTypes: ["User"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of RemoveAssigneesFromAssignable +""" +type RemoveAssigneesFromAssignablePayload { + """ + The item that was unassigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of RemoveEnterpriseAdmin +""" +input RemoveEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Enterprise ID from which to remove the administrator. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the user to remove as an administrator. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseAdmin +""" +type RemoveEnterpriseAdminPayload { + """ + The user who was removed as an administrator. + """ + admin: User + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + A message confirming the result of removing an administrator. + """ + message: String + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseIdentityProvider +""" +input RemoveEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which to remove the identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) +} + +""" +Autogenerated return type of RemoveEnterpriseIdentityProvider +""" +type RemoveEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider that was removed from the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RemoveEnterpriseOrganization +""" +input RemoveEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which the organization should be removed. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The ID of the organization to remove from the enterprise. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of RemoveEnterpriseOrganization +""" +type RemoveEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + The organization that was removed from the enterprise. + """ + organization: Organization + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseSupportEntitlement +""" +input RemoveEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a member who will lose the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseSupportEntitlement +""" +type RemoveEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of removing the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of RemoveLabelsFromLabelable +""" +input RemoveLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ids of labels to remove. + """ + labelIds: [ID!]! @possibleTypes(concreteTypes: ["Label"]) + + """ + The id of the Labelable to remove labels from. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Discussion", "Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of RemoveLabelsFromLabelable +""" +type RemoveLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Labelable the labels were removed from. + """ + labelable: Labelable +} + +""" +Autogenerated input type of RemoveOutsideCollaborator +""" +input RemoveOutsideCollaboratorInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization to remove the outside collaborator from. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The ID of the outside collaborator to remove. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of RemoveOutsideCollaborator +""" +type RemoveOutsideCollaboratorPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was removed as an outside collaborator. + """ + removedUser: User +} + +""" +Autogenerated input type of RemoveReaction +""" +input RemoveReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the emoji reaction to remove. + """ + content: ReactionContent! + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + @possibleTypes( + concreteTypes: [ + "CommitComment" + "Discussion" + "DiscussionComment" + "Issue" + "IssueComment" + "PullRequest" + "PullRequestReview" + "PullRequestReviewComment" + "Release" + "TeamDiscussion" + "TeamDiscussionComment" + ] + abstractType: "Reactable" + ) +} + +""" +Autogenerated return type of RemoveReaction +""" +type RemoveReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of RemoveStar +""" +input RemoveStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to unstar. + """ + starrableId: ID! @possibleTypes(concreteTypes: ["Gist", "Repository", "Topic"], abstractType: "Starrable") +} + +""" +Autogenerated return type of RemoveStar +""" +type RemoveStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Autogenerated input type of RemoveUpvote +""" +input RemoveUpvoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion or comment to remove upvote. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["Discussion", "DiscussionComment"], abstractType: "Votable") +} + +""" +Autogenerated return type of RemoveUpvote +""" +type RemoveUpvotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The votable subject. + """ + subject: Votable +} + +""" +Represents a 'removed_from_project' event on a given issue or pull request. +""" +type RemovedFromProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +Represents a 'renamed' event on a given issue or pull request +""" +type RenamedTitleEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the current title of the issue or pull request. + """ + currentTitle: String! + id: ID! + + """ + Identifies the previous title of the issue or pull request. + """ + previousTitle: String! + + """ + Subject that was renamed. + """ + subject: RenamedTitleSubject! +} + +""" +An object which has a renamable title +""" +union RenamedTitleSubject = Issue | PullRequest + +""" +Autogenerated input type of ReopenIssue +""" +input ReopenIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be opened. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of ReopenIssue +""" +type ReopenIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was opened. + """ + issue: Issue +} + +""" +Autogenerated input type of ReopenPullRequest +""" +input ReopenPullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be reopened. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of ReopenPullRequest +""" +type ReopenPullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was reopened. + """ + pullRequest: PullRequest +} + +""" +Represents a 'reopened' event on any `Closable`. +""" +type ReopenedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was reopened. + """ + closable: Closable! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The reason the issue state was changed to open. + """ + stateReason: IssueStateReason +} + +""" +Audit log entry for a repo.access event. +""" +type RepoAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAccessAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAccessAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_member event. +""" +type RepoAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAddMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAddMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_topic event. +""" +type RepoAddTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.archived event. +""" +type RepoArchivedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoArchivedAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoArchivedAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.change_merge_setting event. +""" +type RepoChangeMergeSettingAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the change was to enable (true) or disable (false) the merge type + """ + isEnabled: Boolean + + """ + The merge method affected by the change + """ + mergeType: RepoChangeMergeSettingAuditEntryMergeType + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The merge options available for pull requests to this repository. +""" +enum RepoChangeMergeSettingAuditEntryMergeType { + """ + The pull request is added to the base branch in a merge commit. + """ + MERGE + + """ + Commits from the pull request are added onto the base branch individually without a merge commit. + """ + REBASE + + """ + The pull request's commits are squashed into a single commit before they are merged to the base branch. + """ + SQUASH +} + +""" +Audit log entry for a repo.config.disable_anonymous_git_access event. +""" +type RepoConfigDisableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_collaborators_only event. +""" +type RepoConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_contributors_only event. +""" +type RepoConfigDisableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_sockpuppet_disallowed event. +""" +type RepoConfigDisableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_anonymous_git_access event. +""" +type RepoConfigEnableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_collaborators_only event. +""" +type RepoConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_contributors_only event. +""" +type RepoConfigEnableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_sockpuppet_disallowed event. +""" +type RepoConfigEnableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.lock_anonymous_git_access event. +""" +type RepoConfigLockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.unlock_anonymous_git_access event. +""" +type RepoConfigUnlockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.create event. +""" +type RepoCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The name of the parent repository for this forked repository. + """ + forkParentName: String + + """ + The name of the root repository for this network. + """ + forkSourceName: String + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoCreateAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoCreateAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.destroy event. +""" +type RepoDestroyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoDestroyAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoDestroyAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_member event. +""" +type RepoRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoRemoveMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoRemoveMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_topic event. +""" +type RepoRemoveTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reasons a piece of content can be reported or minimized. +""" +enum ReportedContentClassifiers { + """ + An abusive or harassing piece of content + """ + ABUSE + + """ + A duplicated piece of content + """ + DUPLICATE + + """ + An irrelevant piece of content + """ + OFF_TOPIC + + """ + An outdated piece of content + """ + OUTDATED + + """ + The content has been resolved + """ + RESOLVED + + """ + A spammy piece of content + """ + SPAM +} + +""" +A repository contains the content for a project. +""" +type Repository implements Node & PackageOwner & ProjectOwner & ProjectV2Recent & RepositoryInfo & Starrable & Subscribable & UniformResourceLocatable { + """ + Whether or not a pull request head branch that is behind its base branch can + always be updated even if it is not required to be up to date before merging. + """ + allowUpdateBranch: Boolean! + + """ + A list of users that can be assigned to issues in this repository. + """ + assignableUsers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): UserConnection! + + """ + Whether or not Auto-merge can be enabled on pull requests in this repository. + """ + autoMergeAllowed: Boolean! + + """ + A list of branch protection rules for this repository. + """ + branchProtectionRules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BranchProtectionRuleConnection! + + """ + Returns the code of conduct for this repository + """ + codeOfConduct: CodeOfConduct + + """ + Information extracted from the repository's `CODEOWNERS` file. + """ + codeowners( + """ + The ref name used to return the associated `CODEOWNERS` file. + """ + refName: String + ): RepositoryCodeowners + + """ + A list of collaborators associated with the repository. + """ + collaborators( + """ + Collaborators affiliation level with a repository. + """ + affiliation: CollaboratorAffiliation + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): RepositoryCollaboratorConnection + + """ + A list of commit comments associated with the repository. + """ + commitComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + Returns a list of contact links associated to the repository + """ + contactLinks: [RepositoryContactLink!] + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Ref associated with the repository's default branch. + """ + defaultBranchRef: Ref + + """ + Whether or not branches are automatically deleted when merged in this repository. + """ + deleteBranchOnMerge: Boolean! + + """ + A list of dependency manifests contained in the repository + """ + dependencyGraphManifests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Cursor to paginate dependencies + """ + dependenciesAfter: String + + """ + Number of dependencies to fetch + """ + dependenciesFirst: Int + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Flag to scope to only manifests with dependencies + """ + withDependencies: Boolean + ): DependencyGraphManifestConnection @preview(toggledBy: "hawkgirl-preview") + + """ + A list of deploy keys that are on this repository. + """ + deployKeys( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeployKeyConnection! + + """ + Deployments associated with the repository + """ + deployments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Environments to list deployments for + """ + environments: [String!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for deployments returned from the connection. + """ + orderBy: DeploymentOrder = {field: CREATED_AT, direction: ASC} + ): DeploymentConnection! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + Returns a single discussion from the current repository by number. + """ + discussion( + """ + The number for the discussion to be returned. + """ + number: Int! + ): Discussion + + """ + A list of discussion categories that are available in the repository. + """ + discussionCategories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filter by categories that are assignable by the viewer. + """ + filterByAssignable: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DiscussionCategoryConnection! + + """ + A discussion category by slug. + """ + discussionCategory( + """ + The slug of the discussion category to be returned. + """ + slug: String! + ): DiscussionCategory + + """ + A list of discussions that have been opened in the repository. + """ + discussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Only include discussions that belong to the category with this ID. + """ + categoryId: ID = null + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for discussions returned from the connection. + """ + orderBy: DiscussionOrder = {field: UPDATED_AT, direction: DESC} + ): DiscussionConnection! + + """ + The number of kilobytes this repository occupies on disk. + """ + diskUsage: Int + + """ + Returns a single active environment from the current repository by name. + """ + environment( + """ + The name of the environment to be returned. + """ + name: String! + ): Environment + + """ + A list of environments that are in this repository. + """ + environments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): EnvironmentConnection! + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + Whether this repository allows forks. + """ + forkingAllowed: Boolean! + + """ + A list of direct forked repositories. + """ + forks( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + The funding links for this repository + """ + fundingLinks: [FundingLink!]! + + """ + Indicates if the repository has the Discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + id: ID! + + """ + The interaction ability settings for this repository. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Returns true if blank issue creation is allowed + """ + isBlankIssuesEnabled: Boolean! + + """ + Returns whether or not this repository disabled. + """ + isDisabled: Boolean! + + """ + Returns whether or not this repository is empty. + """ + isEmpty: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + Returns true if this repository has a security policy + """ + isSecurityPolicyEnabled: Boolean + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + Is this repository a user configuration repository? + """ + isUserConfigurationRepository: Boolean! + + """ + Returns a single issue from the current repository by number. + """ + issue( + """ + The number for the issue to be returned. + """ + number: Int! + ): Issue + + """ + Returns a single issue-like object from the current repository by number. + """ + issueOrPullRequest( + """ + The number for the issue to be returned. + """ + number: Int! + ): IssueOrPullRequest + + """ + Returns a list of issue templates associated to the repository + """ + issueTemplates: [IssueTemplate!] + + """ + A list of issues that have been opened in the repository. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Returns a single label by name + """ + label( + """ + Label name + """ + name: String! + ): Label + + """ + A list of labels associated with the repository. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + + """ + If provided, searches labels by name and description. + """ + query: String + ): LabelConnection + + """ + A list containing a breakdown of the language composition of the repository. + """ + languages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: LanguageOrder + ): LanguageConnection + + """ + Get the latest release for the repository if one exists. + """ + latestRelease: Release + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + A list of Users that can be mentioned in the context of the repository. + """ + mentionableUsers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): UserConnection! + + """ + Whether or not PRs are merged with a merge commit on this repository. + """ + mergeCommitAllowed: Boolean! + + """ + How the default commit message will be generated when merging a pull request. + """ + mergeCommitMessage: MergeCommitMessage! + + """ + How the default commit title will be generated when merging a pull request. + """ + mergeCommitTitle: MergeCommitTitle! + + """ + Returns a single milestone from the current repository by number. + """ + milestone( + """ + The number for the milestone to be returned. + """ + number: Int! + ): Milestone + + """ + A list of milestones associated with the repository. + """ + milestones( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for milestones. + """ + orderBy: MilestoneOrder + + """ + Filters milestones with a query on the title + """ + query: String + + """ + Filter by the state of the milestones. + """ + states: [MilestoneState!] + ): MilestoneConnection + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + A Git object in the repository + """ + object( + """ + A Git revision expression suitable for rev-parse + """ + expression: String + + """ + The Git object ID + """ + oid: GitObjectID + ): GitObject + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + The repository parent, if this is a fork. + """ + parent: Repository + + """ + A list of discussions that have been pinned in this repository. + """ + pinnedDiscussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PinnedDiscussionConnection! + + """ + A list of pinned issues for this repository. + """ + pinnedIssues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PinnedIssueConnection + + """ + The primary language of the repository's code. + """ + primaryLanguage: Language + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + Finds and returns the Project (beta) according to the provided Project (beta) number. + """ + projectNext( + """ + The ProjectNext number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Finds and returns the Project according to the provided Project number. + """ + projectV2( + """ + The Project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + List of projects (beta) linked to this repository. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for linked to the repo. + """ + query: String + + """ + How to order the returned project (beta) objects. + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The HTTP path listing the repository's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing the repository's projects + """ + projectsUrl: URI! + + """ + List of projects linked to this repository. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for linked to the repo. + """ + query: String + ): ProjectV2Connection! + + """ + Returns a single pull request from the current repository by number. + """ + pullRequest( + """ + The number for the pull request to be returned. + """ + number: Int! + ): PullRequest + + """ + Returns a list of pull request templates associated to the repository + """ + pullRequestTemplates: [PullRequestTemplate!] + + """ + A list of pull requests that have been opened in the repository. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + Identifies when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + Whether or not rebase-merging is enabled on this repository. + """ + rebaseMergeAllowed: Boolean! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2Connection! + + """ + Fetch a given ref from the repository + """ + ref( + """ + The ref to retrieve. Fully qualified matches are checked in order + (`refs/heads/master`) before falling back onto checks for short name matches (`master`). + """ + qualifiedName: String! + ): Ref + + """ + Fetch a list of refs from the repository + """ + refs( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + DEPRECATED: use orderBy. The ordering direction. + """ + direction: OrderDirection + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for refs returned from the connection. + """ + orderBy: RefOrder + + """ + Filters refs with query on name + """ + query: String + + """ + A ref name prefix like `refs/heads/`, `refs/tags/`, etc. + """ + refPrefix: String! + ): RefConnection + + """ + Lookup a single release given various criteria. + """ + release( + """ + The name of the Tag the Release was created from + """ + tagName: String! + ): Release + + """ + List of releases which are dependent on this repository. + """ + releases( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: ReleaseOrder + ): ReleaseConnection! + + """ + A list of applied repository-topic associations for this repository. + """ + repositoryTopics( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RepositoryTopicConnection! + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + The security policy URL. + """ + securityPolicyUrl: URI + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML! + + """ + Whether or not squash-merging is enabled on this repository. + """ + squashMergeAllowed: Boolean! + + """ + How the default commit message will be generated when squash merging a pull request. + """ + squashMergeCommitMessage: SquashMergeCommitMessage! + + """ + How the default commit title will be generated when squash merging a pull request. + """ + squashMergeCommitTitle: SquashMergeCommitTitle! + + """ + Whether a squash merge commit can use the pull request title as default. + """ + squashPrTitleUsedAsDefault: Boolean! + @deprecated( + reason: "`squashPrTitleUsedAsDefault` will be removed. Use `Repository.squashMergeCommitTitle` instead. Removal on 2023-04-01 UTC." + ) + + """ + The SSH URL to clone this repository + """ + sshUrl: GitSSHRemote! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a list of all submodules in this repository parsed from the + .gitmodules file as of the default branch's HEAD commit. + """ + submodules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): SubmoduleConnection! + + """ + Temporary authentication token for cloning this repository. + """ + tempCloneToken: String + + """ + The repository from which this repository was generated, if any. + """ + templateRepository: Repository + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! + + """ + Indicates whether the viewer has admin permissions on this repository. + """ + viewerCanAdminister: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Indicates whether the viewer can update the topics of this repository. + """ + viewerCanUpdateTopics: Boolean! + + """ + The last commit email for the viewer. + """ + viewerDefaultCommitEmail: String + + """ + The last used merge method by the viewer or the default for the repository. + """ + viewerDefaultMergeMethod: PullRequestMergeMethod! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! + + """ + The users permission level on the repository. Will return null if authenticated as an GitHub App. + """ + viewerPermission: RepositoryPermission + + """ + A list of emails this viewer can commit with. + """ + viewerPossibleCommitEmails: [String!] + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! + + """ + A list of vulnerability alerts that are on this repository. + """ + vulnerabilityAlerts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filter by the scope of the alert's dependency + """ + dependencyScopes: [RepositoryVulnerabilityAlertDependencyScope!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter by the state of the alert + """ + states: [RepositoryVulnerabilityAlertState!] + ): RepositoryVulnerabilityAlertConnection + + """ + A list of users watching the repository. + """ + watchers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + Whether contributors are required to sign off on web-based commits in this repository. + """ + webCommitSignoffRequired: Boolean! +} + +""" +The affiliation of a user to a repository +""" +enum RepositoryAffiliation { + """ + Repositories that the user has been added to as a collaborator. + """ + COLLABORATOR + + """ + Repositories that the user has access to through being a member of an + organization. This includes every repository on every team that the user is on. + """ + ORGANIZATION_MEMBER + + """ + Repositories that are owned by the authenticated user. + """ + OWNER +} + +""" +Metadata for an audit entry with action repo.* +""" +interface RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +Information extracted from a repository's `CODEOWNERS` file. +""" +type RepositoryCodeowners { + """ + Any problems that were encountered while parsing the `CODEOWNERS` file. + """ + errors: [RepositoryCodeownersError!]! +} + +""" +An error in a `CODEOWNERS` file. +""" +type RepositoryCodeownersError { + """ + The column number where the error occurs. + """ + column: Int! + + """ + A short string describing the type of error. + """ + kind: String! + + """ + The line number where the error occurs. + """ + line: Int! + + """ + A complete description of the error, combining information from other fields. + """ + message: String! + + """ + The path to the file when the error occurs. + """ + path: String! + + """ + The content of the line where the error occurs. + """ + source: String! + + """ + A suggestion of how to fix the error. + """ + suggestion: String +} + +""" +The connection type for User. +""" +type RepositoryCollaboratorConnection { + """ + A list of edges. + """ + edges: [RepositoryCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a collaborator of a repository. +""" +type RepositoryCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + The permission the user has on the repository. + """ + permission: RepositoryPermission! + + """ + A list of sources for the user's access to the repository. + """ + permissionSources: [PermissionSource!] +} + +""" +A list of repositories owned by the subject. +""" +type RepositoryConnection { + """ + A list of edges. + """ + edges: [RepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in kilobytes of all repositories in the connection. + """ + totalDiskUsage: Int! +} + +""" +A repository contact link. +""" +type RepositoryContactLink { + """ + The contact link purpose. + """ + about: String! + + """ + The contact link name. + """ + name: String! + + """ + The contact link URL. + """ + url: URI! +} + +""" +The reason a repository is listed as 'contributed'. +""" +enum RepositoryContributionType { + """ + Created a commit + """ + COMMIT + + """ + Created an issue + """ + ISSUE + + """ + Created a pull request + """ + PULL_REQUEST + + """ + Reviewed a pull request + """ + PULL_REQUEST_REVIEW + + """ + Created the repository + """ + REPOSITORY +} + +""" +Represents an author of discussions in repositories. +""" +interface RepositoryDiscussionAuthor { + """ + Discussions this user has started. + """ + repositoryDiscussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Filter discussions to only those that have been answered or not. Defaults to + including both answered and unanswered discussions. + """ + answered: Boolean = null + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for discussions returned from the connection. + """ + orderBy: DiscussionOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter discussions to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionConnection! +} + +""" +Represents an author of discussion comments in repositories. +""" +interface RepositoryDiscussionCommentAuthor { + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter discussion comments to only those that were marked as the answer + """ + onlyAnswers: Boolean = false + + """ + Filter discussion comments to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionCommentConnection! +} + +""" +An edge in a connection. +""" +type RepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Repository +} + +""" +A subset of repository info. +""" +interface RepositoryInfo { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + Indicates if the repository has the Discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + Identifies when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Repository interaction limit that applies to this object. +""" +type RepositoryInteractionAbility { + """ + The time the currently active limit expires. + """ + expiresAt: DateTime + + """ + The current limit that is enabled on this object. + """ + limit: RepositoryInteractionLimit! + + """ + The origin of the currently active interaction limit. + """ + origin: RepositoryInteractionLimitOrigin! +} + +""" +A repository interaction limit. +""" +enum RepositoryInteractionLimit { + """ + Users that are not collaborators will not be able to interact with the repository. + """ + COLLABORATORS_ONLY + + """ + Users that have not previously committed to a repository’s default branch will be unable to interact with the repository. + """ + CONTRIBUTORS_ONLY + + """ + Users that have recently created their account will be unable to interact with the repository. + """ + EXISTING_USERS + + """ + No interaction limits are enabled. + """ + NO_LIMIT +} + +""" +The length for a repository interaction limit to be enabled for. +""" +enum RepositoryInteractionLimitExpiry { + """ + The interaction limit will expire after 1 day. + """ + ONE_DAY + + """ + The interaction limit will expire after 1 month. + """ + ONE_MONTH + + """ + The interaction limit will expire after 1 week. + """ + ONE_WEEK + + """ + The interaction limit will expire after 6 months. + """ + SIX_MONTHS + + """ + The interaction limit will expire after 3 days. + """ + THREE_DAYS +} + +""" +Indicates where an interaction limit is configured. +""" +enum RepositoryInteractionLimitOrigin { + """ + A limit that is configured at the organization level. + """ + ORGANIZATION + + """ + A limit that is configured at the repository level. + """ + REPOSITORY + + """ + A limit that is configured at the user-wide level. + """ + USER +} + +""" +An invitation for a user to be added to a repository. +""" +type RepositoryInvitation implements Node { + """ + The email address that received the invitation. + """ + email: String + id: ID! + + """ + The user who received the invitation. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! + + """ + The permalink for this repository invitation. + """ + permalink: URI! + + """ + The permission granted on this repository by this invitation. + """ + permission: RepositoryPermission! + + """ + The Repository the user is invited to. + """ + repository: RepositoryInfo +} + +""" +A list of repository invitations. +""" +type RepositoryInvitationConnection { + """ + A list of edges. + """ + edges: [RepositoryInvitationEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryInvitation +} + +""" +Ordering options for repository invitation connections. +""" +input RepositoryInvitationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repository invitations by. + """ + field: RepositoryInvitationOrderField! +} + +""" +Properties by which repository invitation connections can be ordered. +""" +enum RepositoryInvitationOrderField { + """ + Order repository invitations by creation time + """ + CREATED_AT +} + +""" +The possible reasons a given repository could be in a locked state. +""" +enum RepositoryLockReason { + """ + The repository is locked due to a billing related reason. + """ + BILLING + + """ + The repository is locked due to a migration. + """ + MIGRATING + + """ + The repository is locked due to a move. + """ + MOVING + + """ + The repository is locked due to a rename. + """ + RENAME + + """ + The repository is locked due to a trade controls related reason. + """ + TRADE_RESTRICTION +} + +""" +An Octoshift repository migration. +""" +type RepositoryMigration implements Migration & Node { + """ + The Octoshift migration flag to continue on error. + """ + continueOnError: Boolean! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: String + + """ + The reason the migration failed. + """ + failureReason: String + id: ID! + + """ + The URL for the migration log (expires 1 day after migration completes). + """ + migrationLogUrl: URI + + """ + The Octoshift migration source. + """ + migrationSource: MigrationSource! + + """ + The target repository name. + """ + repositoryName: String! + + """ + The Octoshift migration source URL. + """ + sourceUrl: URI! + + """ + The Octoshift migration state. + """ + state: MigrationState! +} + +""" +The connection type for RepositoryMigration. +""" +type RepositoryMigrationConnection { + """ + A list of edges. + """ + edges: [RepositoryMigrationEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryMigration] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a repository migration. +""" +type RepositoryMigrationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryMigration +} + +""" +Ordering options for repository migrations. +""" +input RepositoryMigrationOrder { + """ + The ordering direction. + """ + direction: RepositoryMigrationOrderDirection! + + """ + The field to order repository migrations by. + """ + field: RepositoryMigrationOrderField! +} + +""" +Possible directions in which to order a list of repository migrations when provided an `orderBy` argument. +""" +enum RepositoryMigrationOrderDirection { + """ + Specifies an ascending order for a given `orderBy` argument. + """ + ASC + + """ + Specifies a descending order for a given `orderBy` argument. + """ + DESC +} + +""" +Properties by which repository migrations can be ordered. +""" +enum RepositoryMigrationOrderField { + """ + Order mannequins why when they were created. + """ + CREATED_AT +} + +""" +Represents a object that belongs to a repository. +""" +interface RepositoryNode { + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +Ordering options for repository connections +""" +input RepositoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: RepositoryOrderField! +} + +""" +Properties by which repository connections can be ordered. +""" +enum RepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by number of stargazers + """ + STARGAZERS + + """ + Order repositories by update time + """ + UPDATED_AT +} + +""" +Represents an owner of a Repository. +""" +interface RepositoryOwner { + """ + A URL pointing to the owner's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + id: ID! + + """ + The username used to login. + """ + login: String! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Follow repository renames. If disabled, a repository referenced by its old name will return an error. + """ + followRenames: Boolean = true + + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + The HTTP URL for the owner. + """ + resourcePath: URI! + + """ + The HTTP URL for the owner. + """ + url: URI! +} + +""" +The access level to a repository +""" +enum RepositoryPermission { + """ + Can read, clone, and push to this repository. Can also manage issues, pull + requests, and repository settings, including adding collaborators + """ + ADMIN + + """ + Can read, clone, and push to this repository. They can also manage issues, pull requests, and some repository settings + """ + MAINTAIN + + """ + Can read and clone this repository. Can also open and comment on issues and pull requests + """ + READ + + """ + Can read and clone this repository. Can also manage issues and pull requests + """ + TRIAGE + + """ + Can read, clone, and push to this repository. Can also manage issues and pull requests + """ + WRITE +} + +""" +The privacy of a repository +""" +enum RepositoryPrivacy { + """ + Private + """ + PRIVATE + + """ + Public + """ + PUBLIC +} + +""" +A repository-topic connects a repository to a topic. +""" +type RepositoryTopic implements Node & UniformResourceLocatable { + id: ID! + + """ + The HTTP path for this repository-topic. + """ + resourcePath: URI! + + """ + The topic. + """ + topic: Topic! + + """ + The HTTP URL for this repository-topic. + """ + url: URI! +} + +""" +The connection type for RepositoryTopic. +""" +type RepositoryTopicConnection { + """ + A list of edges. + """ + edges: [RepositoryTopicEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryTopic] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryTopicEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryTopic +} + +""" +The repository's visibility level. +""" +enum RepositoryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repository_visibility_change.disable event. +""" +type RepositoryVisibilityChangeDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repository_visibility_change.enable event. +""" +type RepositoryVisibilityChangeEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A Dependabot alert for a repository with a dependency affected by a security vulnerability. +""" +type RepositoryVulnerabilityAlert implements Node & RepositoryNode { + """ + When was the alert created? + """ + createdAt: DateTime! + + """ + The associated Dependabot update + """ + dependabotUpdate: DependabotUpdate + + """ + The scope of an alert's dependency + """ + dependencyScope: RepositoryVulnerabilityAlertDependencyScope + + """ + Comment explaining the reason the alert was dismissed + """ + dismissComment: String + + """ + The reason the alert was dismissed + """ + dismissReason: String + + """ + When was the alert dismissed? + """ + dismissedAt: DateTime + + """ + The user who dismissed the alert + """ + dismisser: User + + """ + The reason the alert was marked as fixed. + """ + fixReason: String + @deprecated( + reason: "The `fixReason` field is being removed. You can still use `fixedAt` and `dismissReason`. Removal on 2022-10-01 UTC." + ) + + """ + When was the alert fixed? + """ + fixedAt: DateTime + id: ID! + + """ + Identifies the alert number. + """ + number: Int! + + """ + The associated repository + """ + repository: Repository! + + """ + The associated security advisory + """ + securityAdvisory: SecurityAdvisory + + """ + The associated security vulnerability + """ + securityVulnerability: SecurityVulnerability + + """ + Identifies the state of the alert. + """ + state: RepositoryVulnerabilityAlertState! + + """ + The vulnerable manifest filename + """ + vulnerableManifestFilename: String! + + """ + The vulnerable manifest path + """ + vulnerableManifestPath: String! + + """ + The vulnerable requirements + """ + vulnerableRequirements: String +} + +""" +The connection type for RepositoryVulnerabilityAlert. +""" +type RepositoryVulnerabilityAlertConnection { + """ + A list of edges. + """ + edges: [RepositoryVulnerabilityAlertEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryVulnerabilityAlert] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible scopes of an alert's dependency. +""" +enum RepositoryVulnerabilityAlertDependencyScope { + """ + A dependency that is only used in development + """ + DEVELOPMENT + + """ + A dependency that is leveraged during application runtime + """ + RUNTIME +} + +""" +An edge in a connection. +""" +type RepositoryVulnerabilityAlertEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryVulnerabilityAlert +} + +""" +The possible states of an alert +""" +enum RepositoryVulnerabilityAlertState { + """ + An alert that has been manually closed by a user. + """ + DISMISSED + + """ + An alert that has been resolved by a code change. + """ + FIXED + + """ + An alert that is still open. + """ + OPEN +} + +""" +Autogenerated input type of RequestReviews +""" +input RequestReviewsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node IDs of the team to request. + """ + teamIds: [ID!] @possibleTypes(concreteTypes: ["Team"]) + + """ + Add users to the set rather than replace. + """ + union: Boolean + + """ + The Node IDs of the user to request. + """ + userIds: [ID!] @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of RequestReviews +""" +type RequestReviewsPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is getting requests. + """ + pullRequest: PullRequest + + """ + The edge from the pull request to the requested reviewers. + """ + requestedReviewersEdge: UserEdge +} + +""" +The possible states that can be requested when creating a check run. +""" +enum RequestableCheckStatusState { + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run is in pending state. + """ + PENDING + + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run is in waiting state. + """ + WAITING +} + +""" +Types that can be requested reviewers. +""" +union RequestedReviewer = Mannequin | Team | User + +""" +The connection type for RequestedReviewer. +""" +type RequestedReviewerConnection { + """ + A list of edges. + """ + edges: [RequestedReviewerEdge] + + """ + A list of nodes. + """ + nodes: [RequestedReviewer] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RequestedReviewerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RequestedReviewer +} + +""" +Represents a type that can be required by a pull request for merging. +""" +interface RequirableByPullRequest { + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired( + """ + The id of the pull request this is required for + """ + pullRequestId: ID + + """ + The number of the pull request this is required for + """ + pullRequestNumber: Int + ): Boolean! +} + +""" +Represents a required status check for a protected branch, but not any specific run of that check. +""" +type RequiredStatusCheckDescription { + """ + The App that must provide this status in order for it to be accepted. + """ + app: App + + """ + The name of this status. + """ + context: String! +} + +""" +Specifies the attributes for a new or updated required status check. +""" +input RequiredStatusCheckInput { + """ + The ID of the App that must set the status in order for it to be accepted. + Omit this value to use whichever app has recently been setting this status, or + use "any" to allow any app to set the status. + """ + appId: ID + + """ + Status check context that must pass for commits to be accepted to the matching branch. + """ + context: String! +} + +""" +Autogenerated input type of RerequestCheckSuite +""" +input RerequestCheckSuiteInput { + """ + The Node ID of the check suite. + """ + checkSuiteId: ID! @possibleTypes(concreteTypes: ["CheckSuite"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of RerequestCheckSuite +""" +type RerequestCheckSuitePayload { + """ + The requested check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of ResolveReviewThread +""" +input ResolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to resolve + """ + threadId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewThread"]) +} + +""" +Autogenerated return type of ResolveReviewThread +""" +type ResolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Represents a private contribution a user made on GitHub. +""" +type RestrictedContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A user, team, or app who has the ability to dismiss a review on a protected branch. +""" +type ReviewDismissalAllowance implements Node { + """ + The actor that can dismiss. + """ + actor: ReviewDismissalAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +Types that can be an actor. +""" +union ReviewDismissalAllowanceActor = App | Team | User + +""" +The connection type for ReviewDismissalAllowance. +""" +type ReviewDismissalAllowanceConnection { + """ + A list of edges. + """ + edges: [ReviewDismissalAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [ReviewDismissalAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewDismissalAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewDismissalAllowance +} + +""" +Represents a 'review_dismissed' event on a given issue or pull request. +""" +type ReviewDismissedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Identifies the optional message associated with the 'review_dismissed' event. + """ + dismissalMessage: String + + """ + Identifies the optional message associated with the event, rendered to HTML. + """ + dismissalMessageHTML: String + id: ID! + + """ + Identifies the previous state of the review with the 'review_dismissed' event. + """ + previousReviewState: PullRequestReviewState! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the commit which caused the review to become stale. + """ + pullRequestCommit: PullRequestCommit + + """ + The HTTP path for this review dismissed event. + """ + resourcePath: URI! + + """ + Identifies the review associated with the 'review_dismissed' event. + """ + review: PullRequestReview + + """ + The HTTP URL for this review dismissed event. + """ + url: URI! +} + +""" +A request for a user to review a pull request. +""" +type ReviewRequest implements Node { + """ + Whether this request was created for a code owner + """ + asCodeOwner: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Identifies the pull request associated with this review request. + """ + pullRequest: PullRequest! + + """ + The reviewer that is requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +The connection type for ReviewRequest. +""" +type ReviewRequestConnection { + """ + A list of edges. + """ + edges: [ReviewRequestEdge] + + """ + A list of nodes. + """ + nodes: [ReviewRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewRequest +} + +""" +Represents an 'review_request_removed' event on a given pull request. +""" +type ReviewRequestRemovedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review request was removed. + """ + requestedReviewer: RequestedReviewer +} + +""" +Represents an 'review_requested' event on a given pull request. +""" +type ReviewRequestedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review was requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +A hovercard context with a message describing the current code review state of the pull +request. +""" +type ReviewStatusHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + The current status of the pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision +} + +""" +Autogenerated input type of RevokeEnterpriseOrganizationsMigratorRole +""" +input RevokeEnterpriseOrganizationsMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which all organizations managed by it will be granted the migrator role. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the user to revoke the migrator role + """ + login: String! +} + +""" +Autogenerated return type of RevokeEnterpriseOrganizationsMigratorRole +""" +type RevokeEnterpriseOrganizationsMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organizations that had the migrator role revoked for the given user. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection +} + +""" +Autogenerated input type of RevokeMigratorRole +""" +input RevokeMigratorRoleInput { + """ + The user login or Team slug to revoke the migrator role from. + """ + actor: String! + + """ + Specifies the type of the actor, can be either USER or TEAM. + """ + actorType: ActorType! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that the user/team belongs to. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of RevokeMigratorRole +""" +type RevokeMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Possible roles a user may have in relation to an organization. +""" +enum RoleInOrganization { + """ + A user who is a direct member of the organization. + """ + DIRECT_MEMBER + + """ + A user with full administrative access to the organization. + """ + OWNER + + """ + A user who is unaffiliated with the organization. + """ + UNAFFILIATED +} + +""" +The possible digest algorithms used to sign SAML requests for an identity provider. +""" +enum SamlDigestAlgorithm { + """ + SHA1 + """ + SHA1 + + """ + SHA256 + """ + SHA256 + + """ + SHA384 + """ + SHA384 + + """ + SHA512 + """ + SHA512 +} + +""" +The possible signature algorithms used to sign SAML requests for a Identity Provider. +""" +enum SamlSignatureAlgorithm { + """ + RSA-SHA1 + """ + RSA_SHA1 + + """ + RSA-SHA256 + """ + RSA_SHA256 + + """ + RSA-SHA384 + """ + RSA_SHA384 + + """ + RSA-SHA512 + """ + RSA_SHA512 +} + +""" +A Saved Reply is text a user can use to reply quickly. +""" +type SavedReply implements Node { + """ + The body of the saved reply. + """ + body: String! + + """ + The saved reply body rendered to HTML. + """ + bodyHTML: HTML! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The title of the saved reply. + """ + title: String! + + """ + The user that saved this reply. + """ + user: Actor +} + +""" +The connection type for SavedReply. +""" +type SavedReplyConnection { + """ + A list of edges. + """ + edges: [SavedReplyEdge] + + """ + A list of nodes. + """ + nodes: [SavedReply] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SavedReplyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SavedReply +} + +""" +Ordering options for saved reply connections. +""" +input SavedReplyOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order saved replies by. + """ + field: SavedReplyOrderField! +} + +""" +Properties by which saved reply connections can be ordered. +""" +enum SavedReplyOrderField { + """ + Order saved reply by when they were updated. + """ + UPDATED_AT +} + +""" +The results of a search. +""" +union SearchResultItem = App | Discussion | Issue | MarketplaceListing | Organization | PullRequest | Repository | User + +""" +A list of results that matched against a search query. Regardless of the number +of matches, a maximum of 1,000 results will be available across all types, +potentially split across many pages. +""" +type SearchResultItemConnection { + """ + The total number of pieces of code that matched the search query. Regardless + of the total number of matches, a maximum of 1,000 results will be available + across all types. + """ + codeCount: Int! + + """ + The total number of discussions that matched the search query. Regardless of + the total number of matches, a maximum of 1,000 results will be available + across all types. + """ + discussionCount: Int! + + """ + A list of edges. + """ + edges: [SearchResultItemEdge] + + """ + The total number of issues that matched the search query. Regardless of the + total number of matches, a maximum of 1,000 results will be available across all types. + """ + issueCount: Int! + + """ + A list of nodes. + """ + nodes: [SearchResultItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The total number of repositories that matched the search query. Regardless of + the total number of matches, a maximum of 1,000 results will be available + across all types. + """ + repositoryCount: Int! + + """ + The total number of users that matched the search query. Regardless of the + total number of matches, a maximum of 1,000 results will be available across all types. + """ + userCount: Int! + + """ + The total number of wiki pages that matched the search query. Regardless of + the total number of matches, a maximum of 1,000 results will be available + across all types. + """ + wikiCount: Int! +} + +""" +An edge in a connection. +""" +type SearchResultItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SearchResultItem + + """ + Text matches on the result found. + """ + textMatches: [TextMatch] +} + +""" +Represents the individual results of a search. +""" +enum SearchType { + """ + Returns matching discussions in repositories. + """ + DISCUSSION + + """ + Returns results matching issues in repositories. + """ + ISSUE + + """ + Returns results matching repositories. + """ + REPOSITORY + + """ + Returns results matching users and organizations on GitHub. + """ + USER +} + +""" +A GitHub Security Advisory +""" +type SecurityAdvisory implements Node { + """ + The classification of the advisory + """ + classification: SecurityAdvisoryClassification! + + """ + The CVSS associated with this advisory + """ + cvss: CVSS! + + """ + CWEs associated with this Advisory + """ + cwes( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CWEConnection! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + This is a long plaintext description of the advisory + """ + description: String! + + """ + The GitHub Security Advisory ID + """ + ghsaId: String! + id: ID! + + """ + A list of identifiers for this advisory + """ + identifiers: [SecurityAdvisoryIdentifier!]! + + """ + The permalink for the advisory's dependabot alerts page + """ + notificationsPermalink: URI + + """ + The organization that originated the advisory + """ + origin: String! + + """ + The permalink for the advisory + """ + permalink: URI + + """ + When the advisory was published + """ + publishedAt: DateTime! + + """ + A list of references for this advisory + """ + references: [SecurityAdvisoryReference!]! + + """ + The severity of the advisory + """ + severity: SecurityAdvisorySeverity! + + """ + A short plaintext summary of the advisory + """ + summary: String! + + """ + When the advisory was last updated + """ + updatedAt: DateTime! + + """ + Vulnerabilities associated with this Advisory + """ + vulnerabilities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + A list of advisory classifications to filter vulnerabilities by. + """ + classifications: [SecurityAdvisoryClassification!] + + """ + An ecosystem to filter vulnerabilities by. + """ + ecosystem: SecurityAdvisoryEcosystem + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityVulnerabilityOrder = {field: UPDATED_AT, direction: DESC} + + """ + A package name to filter vulnerabilities by. + """ + package: String + + """ + A list of severities to filter vulnerabilities by. + """ + severities: [SecurityAdvisorySeverity!] + ): SecurityVulnerabilityConnection! + + """ + When the advisory was withdrawn, if it has been withdrawn + """ + withdrawnAt: DateTime +} + +""" +Classification of the advisory. +""" +enum SecurityAdvisoryClassification { + """ + Classification of general advisories. + """ + GENERAL + + """ + Classification of malware advisories. + """ + MALWARE +} + +""" +The connection type for SecurityAdvisory. +""" +type SecurityAdvisoryConnection { + """ + A list of edges. + """ + edges: [SecurityAdvisoryEdge] + + """ + A list of nodes. + """ + nodes: [SecurityAdvisory] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible ecosystems of a security vulnerability's package. +""" +enum SecurityAdvisoryEcosystem { + """ + GitHub Actions + """ + ACTIONS + + """ + PHP packages hosted at packagist.org + """ + COMPOSER + + """ + Erlang/Elixir packages hosted at hex.pm + """ + ERLANG + + """ + Go modules + """ + GO + + """ + Java artifacts hosted at the Maven central repository + """ + MAVEN + + """ + JavaScript packages hosted at npmjs.com + """ + NPM + + """ + .NET packages hosted at the NuGet Gallery + """ + NUGET + + """ + Python packages hosted at PyPI.org + """ + PIP + + """ + Dart packages hosted at pub.dev + """ + PUB + + """ + Ruby gems hosted at RubyGems.org + """ + RUBYGEMS + + """ + Rust crates + """ + RUST +} + +""" +An edge in a connection. +""" +type SecurityAdvisoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityAdvisory +} + +""" +A GitHub Security Advisory Identifier +""" +type SecurityAdvisoryIdentifier { + """ + The identifier type, e.g. GHSA, CVE + """ + type: String! + + """ + The identifier + """ + value: String! +} + +""" +An advisory identifier to filter results on. +""" +input SecurityAdvisoryIdentifierFilter { + """ + The identifier type. + """ + type: SecurityAdvisoryIdentifierType! + + """ + The identifier string. Supports exact or partial matching. + """ + value: String! +} + +""" +Identifier formats available for advisories. +""" +enum SecurityAdvisoryIdentifierType { + """ + Common Vulnerabilities and Exposures Identifier. + """ + CVE + + """ + GitHub Security Advisory ID. + """ + GHSA +} + +""" +Ordering options for security advisory connections +""" +input SecurityAdvisoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order security advisories by. + """ + field: SecurityAdvisoryOrderField! +} + +""" +Properties by which security advisory connections can be ordered. +""" +enum SecurityAdvisoryOrderField { + """ + Order advisories by publication time + """ + PUBLISHED_AT + + """ + Order advisories by update time + """ + UPDATED_AT +} + +""" +An individual package +""" +type SecurityAdvisoryPackage { + """ + The ecosystem the package belongs to, e.g. RUBYGEMS, NPM + """ + ecosystem: SecurityAdvisoryEcosystem! + + """ + The package name + """ + name: String! +} + +""" +An individual package version +""" +type SecurityAdvisoryPackageVersion { + """ + The package name or version + """ + identifier: String! +} + +""" +A GitHub Security Advisory Reference +""" +type SecurityAdvisoryReference { + """ + A publicly accessible reference + """ + url: URI! +} + +""" +Severity of the vulnerability. +""" +enum SecurityAdvisorySeverity { + """ + Critical. + """ + CRITICAL + + """ + High. + """ + HIGH + + """ + Low. + """ + LOW + + """ + Moderate. + """ + MODERATE +} + +""" +An individual vulnerability within an Advisory +""" +type SecurityVulnerability { + """ + The Advisory associated with this Vulnerability + """ + advisory: SecurityAdvisory! + + """ + The first version containing a fix for the vulnerability + """ + firstPatchedVersion: SecurityAdvisoryPackageVersion + + """ + A description of the vulnerable package + """ + package: SecurityAdvisoryPackage! + + """ + The severity of the vulnerability within this package + """ + severity: SecurityAdvisorySeverity! + + """ + When the vulnerability was last updated + """ + updatedAt: DateTime! + + """ + A string that describes the vulnerable package versions. + This string follows a basic syntax with a few forms. + + `= 0.2.0` denotes a single vulnerable version. + + `<= 1.0.8` denotes a version range up to and including the specified version + + `< 0.1.11` denotes a version range up to, but excluding, the specified version + + `>= 4.3.0, < 4.3.5` denotes a version range with a known minimum and maximum version. + + `>= 0.0.1` denotes a version range with a known minimum, but no known maximum + """ + vulnerableVersionRange: String! +} + +""" +The connection type for SecurityVulnerability. +""" +type SecurityVulnerabilityConnection { + """ + A list of edges. + """ + edges: [SecurityVulnerabilityEdge] + + """ + A list of nodes. + """ + nodes: [SecurityVulnerability] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SecurityVulnerabilityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityVulnerability +} + +""" +Ordering options for security vulnerability connections +""" +input SecurityVulnerabilityOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order security vulnerabilities by. + """ + field: SecurityVulnerabilityOrderField! +} + +""" +Properties by which security vulnerability connections can be ordered. +""" +enum SecurityVulnerabilityOrderField { + """ + Order vulnerability by update time + """ + UPDATED_AT +} + +""" +Autogenerated input type of SetEnterpriseIdentityProvider +""" +input SetEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm! + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: String! + + """ + The Issuer Entity ID for the SAML identity provider + """ + issuer: String + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm! + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI! +} + +""" +Autogenerated return type of SetEnterpriseIdentityProvider +""" +type SetEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of SetOrganizationInteractionLimit +""" +input SetOrganizationInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the organization to set a limit for. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of SetOrganizationInteractionLimit +""" +type SetOrganizationInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that the interaction limit was set for. + """ + organization: Organization +} + +""" +Autogenerated input type of SetRepositoryInteractionLimit +""" +input SetRepositoryInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the repository to set a limit for. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of SetRepositoryInteractionLimit +""" +type SetRepositoryInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that the interaction limit was set for. + """ + repository: Repository +} + +""" +Autogenerated input type of SetUserInteractionLimit +""" +input SetUserInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the user to set a limit for. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of SetUserInteractionLimit +""" +type SetUserInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that the interaction limit was set for. + """ + user: User +} + +""" +Represents an S/MIME signature on a Commit or Tag. +""" +type SmimeSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Represents a sort by field and direction. +""" +type SortBy { + """ + The direction of the sorting. Possible values are ASC and DESC. + """ + direction: OrderDirection! + + """ + The id of the field by which the column is sorted. + """ + field: Int! +} + +""" +Entities that can sponsor others via GitHub Sponsors +""" +union Sponsor = Organization | User + +""" +The connection type for Sponsor. +""" +type SponsorConnection { + """ + A list of edges. + """ + edges: [SponsorEdge] + + """ + A list of nodes. + """ + nodes: [Sponsor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user or organization who is sponsoring someone in GitHub Sponsors. +""" +type SponsorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Sponsor +} + +""" +Ordering options for connections to get sponsor entities for GitHub Sponsors. +""" +input SponsorOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order sponsor entities by. + """ + field: SponsorOrderField! +} + +""" +Properties by which sponsor connections can be ordered. +""" +enum SponsorOrderField { + """ + Order sponsorable entities by login (username). + """ + LOGIN + + """ + Order sponsors by their relevance to the viewer. + """ + RELEVANCE +} + +""" +Entities that can be sponsored through GitHub Sponsors +""" +interface Sponsorable { + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + Check if the given account is sponsoring this user/organization. + """ + isSponsoredBy( + """ + The target account's login. + """ + accountLogin: String! + ): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the users and organizations returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsors returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + + """ + If given, will filter for sponsors at the given tier. Will only return + sponsors whose tier the viewer is permitted to see. + """ + tierId: ID + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities( + """ + Filter activities to only the specified actions. + """ + actions: [SponsorsActivityAction!] = [] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether to include those events where this sponsorable acted as the sponsor. + Defaults to only including events where this sponsorable was the recipient + of a sponsorship. + """ + includeAsSponsor: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for activity returned from the connection. + """ + orderBy: SponsorsActivityOrder = {field: TIMESTAMP, direction: DESC} + + """ + Filter activities returned to only those that occurred in the most recent + specified time period. Set to ALL to avoid filtering by when the activity + occurred. Will be ignored if `since` or `until` is given. + """ + period: SponsorsActivityPeriod = MONTH + + """ + Filter activities to those that occurred on or after this time. + """ + since: DateTime + + """ + Filter activities to those that occurred before this time. + """ + until: DateTime + ): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the + sponsorship where you're the sponsor. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsor: Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the + sponsorship you're receiving. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsorable: Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorship updates returned from the connection. + """ + orderBy: SponsorshipNewsletterOrder = {field: CREATED_AT, direction: DESC} + ): SponsorshipNewsletterConnection! + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter sponsorships returned to those for the specified maintainers. That + is, the recipient of the sponsorship is a user or organization with one of + the given logins. + """ + maintainerLogins: [String!] + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! +} + +""" +Entities that can be sponsored via GitHub Sponsors +""" +union SponsorableItem = Organization | User + +""" +The connection type for SponsorableItem. +""" +type SponsorableItemConnection { + """ + A list of edges. + """ + edges: [SponsorableItemEdge] + + """ + A list of nodes. + """ + nodes: [SponsorableItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorableItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorableItem +} + +""" +Ordering options for connections to get sponsorable entities for GitHub Sponsors. +""" +input SponsorableOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order sponsorable entities by. + """ + field: SponsorableOrderField! +} + +""" +Properties by which sponsorable connections can be ordered. +""" +enum SponsorableOrderField { + """ + Order sponsorable entities by login (username). + """ + LOGIN +} + +""" +An event related to sponsorship activity. +""" +type SponsorsActivity implements Node { + """ + What action this activity indicates took place. + """ + action: SponsorsActivityAction! + id: ID! + + """ + The tier that the sponsorship used to use, for tier change events. + """ + previousSponsorsTier: SponsorsTier + + """ + The user or organization who triggered this activity and was/is sponsoring the sponsorable. + """ + sponsor: Sponsor + + """ + The user or organization that is being sponsored, the maintainer. + """ + sponsorable: Sponsorable! + + """ + The associated sponsorship tier. + """ + sponsorsTier: SponsorsTier + + """ + The timestamp of this event. + """ + timestamp: DateTime +} + +""" +The possible actions that GitHub Sponsors activities can represent. +""" +enum SponsorsActivityAction { + """ + The activity was cancelling a sponsorship. + """ + CANCELLED_SPONSORSHIP + + """ + The activity was starting a sponsorship. + """ + NEW_SPONSORSHIP + + """ + The activity was scheduling a downgrade or cancellation. + """ + PENDING_CHANGE + + """ + The activity was funds being refunded to the sponsor or GitHub. + """ + REFUND + + """ + The activity was disabling matching for a previously matched sponsorship. + """ + SPONSOR_MATCH_DISABLED + + """ + The activity was changing the sponsorship tier, either directly by the sponsor or by a scheduled/pending change. + """ + TIER_CHANGE +} + +""" +The connection type for SponsorsActivity. +""" +type SponsorsActivityConnection { + """ + A list of edges. + """ + edges: [SponsorsActivityEdge] + + """ + A list of nodes. + """ + nodes: [SponsorsActivity] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorsActivityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorsActivity +} + +""" +Ordering options for GitHub Sponsors activity connections. +""" +input SponsorsActivityOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order activity by. + """ + field: SponsorsActivityOrderField! +} + +""" +Properties by which GitHub Sponsors activity connections can be ordered. +""" +enum SponsorsActivityOrderField { + """ + Order activities by when they happened. + """ + TIMESTAMP +} + +""" +The possible time periods for which Sponsors activities can be requested. +""" +enum SponsorsActivityPeriod { + """ + Don't restrict the activity to any date range, include all activity. + """ + ALL + + """ + The previous calendar day. + """ + DAY + + """ + The previous thirty days. + """ + MONTH + + """ + The previous seven days. + """ + WEEK +} + +""" +A goal associated with a GitHub Sponsors listing, representing a target the sponsored maintainer would like to attain. +""" +type SponsorsGoal { + """ + A description of the goal from the maintainer. + """ + description: String + + """ + What the objective of this goal is. + """ + kind: SponsorsGoalKind! + + """ + The percentage representing how complete this goal is, between 0-100. + """ + percentComplete: Int! + + """ + What the goal amount is. Represents an amount in USD for monthly sponsorship + amount goals. Represents a count of unique sponsors for total sponsors count goals. + """ + targetValue: Int! + + """ + A brief summary of the kind and target value of this goal. + """ + title: String! +} + +""" +The different kinds of goals a GitHub Sponsors member can have. +""" +enum SponsorsGoalKind { + """ + The goal is about getting a certain amount in USD from sponsorships each month. + """ + MONTHLY_SPONSORSHIP_AMOUNT + + """ + The goal is about reaching a certain number of sponsors. + """ + TOTAL_SPONSORS_COUNT +} + +""" +A GitHub Sponsors listing. +""" +type SponsorsListing implements Node { + """ + The current goal the maintainer is trying to reach with GitHub Sponsors, if any. + """ + activeGoal: SponsorsGoal + + """ + The name of the country or region with the maintainer's bank account or fiscal + host. Will only return a value when queried by the maintainer themselves, or + by an admin of the sponsorable organization. + """ + billingCountryOrRegion: String + + """ + The email address used by GitHub to contact the sponsorable about their GitHub + Sponsors profile. Will only return a value when queried by the maintainer + themselves, or by an admin of the sponsorable organization. + """ + contactEmailAddress: String + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The HTTP path for the Sponsors dashboard for this Sponsors listing. + """ + dashboardResourcePath: URI! + + """ + The HTTP URL for the Sponsors dashboard for this Sponsors listing. + """ + dashboardUrl: URI! + + """ + The records featured on the GitHub Sponsors profile. + """ + featuredItems( + """ + The types of featured items to return. + """ + featureableTypes: [SponsorsListingFeaturedItemFeatureableType!] = [REPOSITORY, USER] + ): [SponsorsListingFeaturedItem!]! + + """ + The full description of the listing. + """ + fullDescription: String! + + """ + The full description of the listing rendered to HTML. + """ + fullDescriptionHTML: HTML! + id: ID! + + """ + Whether this listing is publicly visible. + """ + isPublic: Boolean! + + """ + The listing's full name. + """ + name: String! + + """ + A future date on which this listing is eligible to receive a payout. + """ + nextPayoutDate: Date + + """ + The name of the country or region where the maintainer resides. Will only + return a value when queried by the maintainer themselves, or by an admin of + the sponsorable organization. + """ + residenceCountryOrRegion: String + + """ + The HTTP path for this Sponsors listing. + """ + resourcePath: URI! + + """ + The short description of the listing. + """ + shortDescription: String! + + """ + The short name of the listing. + """ + slug: String! + + """ + The entity this listing represents who can be sponsored on GitHub Sponsors. + """ + sponsorable: Sponsorable! + + """ + The published tiers for this GitHub Sponsors listing. + """ + tiers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Sponsors tiers returned from the connection. + """ + orderBy: SponsorsTierOrder = {field: MONTHLY_PRICE_IN_CENTS, direction: ASC} + ): SponsorsTierConnection + + """ + The HTTP URL for this Sponsors listing. + """ + url: URI! +} + +""" +A record that can be featured on a GitHub Sponsors profile. +""" +union SponsorsListingFeatureableItem = Repository | User + +""" +A record that is promoted on a GitHub Sponsors profile. +""" +type SponsorsListingFeaturedItem implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Will either be a description from the sponsorable maintainer about why they + featured this item, or the item's description itself, such as a user's bio + from their GitHub profile page. + """ + description: String + + """ + The record that is featured on the GitHub Sponsors profile. + """ + featureable: SponsorsListingFeatureableItem! + id: ID! + + """ + The position of this featured item on the GitHub Sponsors profile with a lower + position indicating higher precedence. Starts at 1. + """ + position: Int! + + """ + The GitHub Sponsors profile that features this record. + """ + sponsorsListing: SponsorsListing! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The different kinds of records that can be featured on a GitHub Sponsors profile page. +""" +enum SponsorsListingFeaturedItemFeatureableType { + """ + A repository owned by the user or organization with the GitHub Sponsors profile. + """ + REPOSITORY + + """ + A user who belongs to the organization with the GitHub Sponsors profile. + """ + USER +} + +""" +A GitHub Sponsors tier associated with a GitHub Sponsors listing. +""" +type SponsorsTier implements Node { + """ + SponsorsTier information only visible to users that can administer the associated Sponsors listing. + """ + adminInfo: SponsorsTierAdminInfo + + """ + Get a different tier for this tier's maintainer that is at the same frequency + as this tier but with an equal or lesser cost. Returns the published tier with + the monthly price closest to this tier's without going over. + """ + closestLesserValueTier: SponsorsTier + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the tier. + """ + description: String! + + """ + The tier description rendered to HTML + """ + descriptionHTML: HTML! + id: ID! + + """ + Whether this tier was chosen at checkout time by the sponsor rather than + defined ahead of time by the maintainer who manages the Sponsors listing. + """ + isCustomAmount: Boolean! + + """ + Whether this tier is only for use with one-time sponsorships. + """ + isOneTime: Boolean! + + """ + How much this tier costs per month in cents. + """ + monthlyPriceInCents: Int! + + """ + How much this tier costs per month in USD. + """ + monthlyPriceInDollars: Int! + + """ + The name of the tier. + """ + name: String! + + """ + The sponsors listing that this tier belongs to. + """ + sponsorsListing: SponsorsListing! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +SponsorsTier information only visible to users that can administer the associated Sponsors listing. +""" +type SponsorsTierAdminInfo { + """ + The sponsorships associated with this tier. + """ + sponsorships( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! +} + +""" +The connection type for SponsorsTier. +""" +type SponsorsTierConnection { + """ + A list of edges. + """ + edges: [SponsorsTierEdge] + + """ + A list of nodes. + """ + nodes: [SponsorsTier] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorsTierEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorsTier +} + +""" +Ordering options for Sponsors tiers connections. +""" +input SponsorsTierOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order tiers by. + """ + field: SponsorsTierOrderField! +} + +""" +Properties by which Sponsors tiers connections can be ordered. +""" +enum SponsorsTierOrderField { + """ + Order tiers by creation time. + """ + CREATED_AT + + """ + Order tiers by their monthly price in cents + """ + MONTHLY_PRICE_IN_CENTS +} + +""" +A sponsorship relationship between a sponsor and a maintainer +""" +type Sponsorship implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Whether this sponsorship represents a one-time payment versus a recurring sponsorship. + """ + isOneTimePayment: Boolean! + + """ + Check if the sponsor has chosen to receive sponsorship update emails sent from + the sponsorable. Only returns a non-null value when the viewer has permission to know this. + """ + isSponsorOptedIntoEmail: Boolean + + """ + The entity that is being sponsored + """ + maintainer: User! + @deprecated( + reason: "`Sponsorship.maintainer` will be removed. Use `Sponsorship.sponsorable` instead. Removal on 2020-04-01 UTC." + ) + + """ + The privacy level for this sponsorship. + """ + privacyLevel: SponsorshipPrivacy! + + """ + The user that is sponsoring. Returns null if the sponsorship is private or if sponsor is not a user. + """ + sponsor: User + @deprecated( + reason: "`Sponsorship.sponsor` will be removed. Use `Sponsorship.sponsorEntity` instead. Removal on 2020-10-01 UTC." + ) + + """ + The user or organization that is sponsoring, if you have permission to view them. + """ + sponsorEntity: Sponsor + + """ + The entity that is being sponsored + """ + sponsorable: Sponsorable! + + """ + The associated sponsorship tier + """ + tier: SponsorsTier + + """ + Identifies the date and time when the current tier was chosen for this sponsorship. + """ + tierSelectedAt: DateTime +} + +""" +The connection type for Sponsorship. +""" +type SponsorshipConnection { + """ + A list of edges. + """ + edges: [SponsorshipEdge] + + """ + A list of nodes. + """ + nodes: [Sponsorship] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total amount in cents of all recurring sponsorships in the connection + whose amount you can view. Does not include one-time sponsorships. + """ + totalRecurringMonthlyPriceInCents: Int! + + """ + The total amount in USD of all recurring sponsorships in the connection whose + amount you can view. Does not include one-time sponsorships. + """ + totalRecurringMonthlyPriceInDollars: Int! +} + +""" +An edge in a connection. +""" +type SponsorshipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Sponsorship +} + +""" +An update sent to sponsors of a user or organization on GitHub Sponsors. +""" +type SponsorshipNewsletter implements Node { + """ + The contents of the newsletter, the message the sponsorable wanted to give. + """ + body: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Indicates if the newsletter has been made available to sponsors. + """ + isPublished: Boolean! + + """ + The user or organization this newsletter is from. + """ + sponsorable: Sponsorable! + + """ + The subject of the newsletter, what it's about. + """ + subject: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for SponsorshipNewsletter. +""" +type SponsorshipNewsletterConnection { + """ + A list of edges. + """ + edges: [SponsorshipNewsletterEdge] + + """ + A list of nodes. + """ + nodes: [SponsorshipNewsletter] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorshipNewsletterEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorshipNewsletter +} + +""" +Ordering options for sponsorship newsletter connections. +""" +input SponsorshipNewsletterOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order sponsorship newsletters by. + """ + field: SponsorshipNewsletterOrderField! +} + +""" +Properties by which sponsorship update connections can be ordered. +""" +enum SponsorshipNewsletterOrderField { + """ + Order sponsorship newsletters by when they were created. + """ + CREATED_AT +} + +""" +Ordering options for sponsorship connections. +""" +input SponsorshipOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order sponsorship by. + """ + field: SponsorshipOrderField! +} + +""" +Properties by which sponsorship connections can be ordered. +""" +enum SponsorshipOrderField { + """ + Order sponsorship by creation time. + """ + CREATED_AT +} + +""" +The privacy of a sponsorship +""" +enum SponsorshipPrivacy { + """ + Private + """ + PRIVATE + + """ + Public + """ + PUBLIC +} + +""" +The possible default commit messages for squash merges. +""" +enum SquashMergeCommitMessage { + """ + Default to a blank commit message. + """ + BLANK + + """ + Default to the branch's commit messages. + """ + COMMIT_MESSAGES + + """ + Default to the pull request's body. + """ + PR_BODY +} + +""" +The possible default commit titles for squash merges. +""" +enum SquashMergeCommitTitle { + """ + Default to the commit's title (if only one commit) or the pull request's title (when more than one commit). + """ + COMMIT_OR_PR_TITLE + + """ + Default to the pull request's title. + """ + PR_TITLE +} + +""" +Represents an SSH signature on a Commit or Tag. +""" +type SshSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Hex-encoded fingerprint of the key that signed this object. + """ + keyFingerprint: String + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Ways in which star connections can be ordered. +""" +input StarOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field in which to order nodes by. + """ + field: StarOrderField! +} + +""" +Properties by which star connections can be ordered. +""" +enum StarOrderField { + """ + Allows ordering a list of stars by when they were created. + """ + STARRED_AT +} + +""" +The connection type for User. +""" +type StargazerConnection { + """ + A list of edges. + """ + edges: [StargazerEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's starred a repository. +""" +type StargazerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Things that can be starred. +""" +interface Starrable { + id: ID! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +The connection type for Repository. +""" +type StarredRepositoryConnection { + """ + A list of edges. + """ + edges: [StarredRepositoryEdge] + + """ + Is the list of stars for this user truncated? This is true for users that have many stars. + """ + isOverLimit: Boolean! + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a starred repository. +""" +type StarredRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: Repository! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Autogenerated input type of StartRepositoryMigration +""" +input StartRepositoryMigrationInput { + """ + The Octoshift migration source access token. + """ + accessToken: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether to continue the migration on error + """ + continueOnError: Boolean + + """ + The signed URL to access the user-uploaded git archive + """ + gitArchiveUrl: String + + """ + The GitHub personal access token of the user importing to the target repository. + """ + githubPat: String + + """ + Whether to lock the source repository. + """ + lockSource: Boolean + + """ + The signed URL to access the user-uploaded metadata archive + """ + metadataArchiveUrl: String + + """ + The ID of the organization that will own the imported repository. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The name of the imported repository. + """ + repositoryName: String! + + """ + Whether to skip migrating releases for the repository. + """ + skipReleases: Boolean + + """ + The ID of the Octoshift migration source. + """ + sourceId: ID! @possibleTypes(concreteTypes: ["MigrationSource"]) + + """ + The Octoshift migration source repository URL. + """ + sourceRepositoryUrl: URI! + + """ + The visibility of the imported repository. + """ + targetRepoVisibility: String +} + +""" +Autogenerated return type of StartRepositoryMigration +""" +type StartRepositoryMigrationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new Octoshift repository migration. + """ + repositoryMigration: RepositoryMigration +} + +""" +Represents a commit status. +""" +type Status implements Node { + """ + A list of status contexts and check runs for this commit. + """ + combinedContexts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): StatusCheckRollupContextConnection! + + """ + The commit this status is attached to. + """ + commit: Commit + + """ + Looks up an individual status context by context name. + """ + context( + """ + The context name. + """ + name: String! + ): StatusContext + + """ + The individual status contexts for this commit. + """ + contexts: [StatusContext!]! + id: ID! + + """ + The combined commit status. + """ + state: StatusState! +} + +""" +Represents the rollup for both the check runs and status for a commit. +""" +type StatusCheckRollup implements Node { + """ + The commit the status and check runs are attached to. + """ + commit: Commit + + """ + A list of status contexts and check runs for this commit. + """ + contexts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): StatusCheckRollupContextConnection! + id: ID! + + """ + The combined status for the commit. + """ + state: StatusState! +} + +""" +Types that can be inside a StatusCheckRollup context. +""" +union StatusCheckRollupContext = CheckRun | StatusContext + +""" +The connection type for StatusCheckRollupContext. +""" +type StatusCheckRollupContextConnection { + """ + The number of check runs in this rollup. + """ + checkRunCount: Int! + + """ + Counts of check runs by state. + """ + checkRunCountsByState: [CheckRunStateCount!] + + """ + A list of edges. + """ + edges: [StatusCheckRollupContextEdge] + + """ + A list of nodes. + """ + nodes: [StatusCheckRollupContext] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The number of status contexts in this rollup. + """ + statusContextCount: Int! + + """ + Counts of status contexts by state. + """ + statusContextCountsByState: [StatusContextStateCount!] + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type StatusCheckRollupContextEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: StatusCheckRollupContext +} + +""" +Represents an individual commit status context +""" +type StatusContext implements Node & RequirableByPullRequest { + """ + The avatar of the OAuth application or the user that created the status + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int = 40 + ): URI + + """ + This commit this status context is attached to. + """ + commit: Commit + + """ + The name of this status context. + """ + context: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this status context. + """ + creator: Actor + + """ + The description for this status context. + """ + description: String + id: ID! + + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired( + """ + The id of the pull request this is required for + """ + pullRequestId: ID + + """ + The number of the pull request this is required for + """ + pullRequestNumber: Int + ): Boolean! + + """ + The state of this status context. + """ + state: StatusState! + + """ + The URL for this status context. + """ + targetUrl: URI +} + +""" +Represents a count of the state of a status context. +""" +type StatusContextStateCount { + """ + The number of statuses with this state. + """ + count: Int! + + """ + The state of a status context. + """ + state: StatusState! +} + +""" +The possible commit status states. +""" +enum StatusState { + """ + Status is errored. + """ + ERROR + + """ + Status is expected. + """ + EXPECTED + + """ + Status is failing. + """ + FAILURE + + """ + Status is pending. + """ + PENDING + + """ + Status is successful. + """ + SUCCESS +} + +""" +Autogenerated input type of SubmitPullRequestReview +""" +input SubmitPullRequestReviewInput { + """ + The text field to set on the Pull Request Review. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The event to send to the Pull Request Review. + """ + event: PullRequestReviewEvent! + + """ + The Pull Request ID to submit any pending reviews. + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Pull Request Review ID to submit. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of SubmitPullRequestReview +""" +type SubmitPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The submitted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +A pointer to a repository at a specific revision embedded inside another repository. +""" +type Submodule { + """ + The branch of the upstream submodule for tracking updates + """ + branch: String + + """ + The git URL of the submodule repository + """ + gitUrl: URI! + + """ + The name of the submodule in .gitmodules + """ + name: String! + + """ + The name of the submodule in .gitmodules (Base64-encoded) + """ + nameRaw: Base64String! + + """ + The path in the superproject that this submodule is located in + """ + path: String! + + """ + The path in the superproject that this submodule is located in (Base64-encoded) + """ + pathRaw: Base64String! + + """ + The commit revision of the subproject repository being tracked by the submodule + """ + subprojectCommitOid: GitObjectID +} + +""" +The connection type for Submodule. +""" +type SubmoduleConnection { + """ + A list of edges. + """ + edges: [SubmoduleEdge] + + """ + A list of nodes. + """ + nodes: [Submodule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SubmoduleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Submodule +} + +""" +Entities that can be subscribed to for web and email notifications. +""" +interface Subscribable { + id: ID! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Represents a 'subscribed' event on a given `Subscribable`. +""" +type SubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +The possible states of a subscription. +""" +enum SubscriptionState { + """ + The User is never notified. + """ + IGNORED + + """ + The User is notified of all conversations. + """ + SUBSCRIBED + + """ + The User is only notified when participating or @mentioned. + """ + UNSUBSCRIBED +} + +""" +A suggestion to review a pull request based on a user's commit history and review comments. +""" +type SuggestedReviewer { + """ + Is this suggestion based on past commits? + """ + isAuthor: Boolean! + + """ + Is this suggestion based on past review comments? + """ + isCommenter: Boolean! + + """ + Identifies the user suggested to review the pull request. + """ + reviewer: User! +} + +""" +Represents a Git tag. +""" +type Tag implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + The Git tag message. + """ + message: String + + """ + The Git tag name. + """ + name: String! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + Details about the tag author. + """ + tagger: GitActor + + """ + The Git object the tag points to. + """ + target: GitObject! +} + +""" +A team of users in an organization. +""" +type Team implements MemberStatusable & Node & Subscribable { + """ + A list of teams that are ancestors of this team. + """ + ancestors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + A URL pointing to the team's avatar. + """ + avatarUrl( + """ + The size in pixels of the resulting square image. + """ + size: Int = 400 + ): URI + + """ + List of child teams belonging to this team + """ + childTeams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether to list immediate child teams or all descendant child teams. + """ + immediateOnly: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamOrder + + """ + User logins to filter by + """ + userLogins: [String!] + ): TeamConnection! + + """ + The slug corresponding to the organization and team. + """ + combinedSlug: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the team. + """ + description: String + + """ + Find a team discussion by its number. + """ + discussion( + """ + The sequence number of the discussion to find. + """ + number: Int! + ): TeamDiscussion + + """ + A list of team discussions. + """ + discussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If provided, filters discussions according to whether or not they are pinned. + """ + isPinned: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamDiscussionOrder + ): TeamDiscussionConnection! + + """ + The HTTP path for team discussions + """ + discussionsResourcePath: URI! + + """ + The HTTP URL for team discussions + """ + discussionsUrl: URI! + + """ + The HTTP path for editing this team + """ + editTeamResourcePath: URI! + + """ + The HTTP URL for editing this team + """ + editTeamUrl: URI! + id: ID! + + """ + A list of pending invitations for users to this team + """ + invitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationInvitationConnection + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! + + """ + A list of users who are members of this team. + """ + members( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter by membership type + """ + membership: TeamMembershipType = ALL + + """ + Order for the connection. + """ + orderBy: TeamMemberOrder + + """ + The search string to look for. + """ + query: String + + """ + Filter by team member role + """ + role: TeamMemberRole + ): TeamMemberConnection! + + """ + The HTTP path for the team' members + """ + membersResourcePath: URI! + + """ + The HTTP URL for the team' members + """ + membersUrl: URI! + + """ + The name of the team. + """ + name: String! + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + The organization that owns this team. + """ + organization: Organization! + + """ + The parent team of the team. + """ + parentTeam: Team + + """ + The level of privacy the team has. + """ + privacy: TeamPrivacy! + + """ + Finds and returns the project according to the provided project number. + """ + projectV2( + """ + The Project number. + """ + number: Int! + ): ProjectV2 + + """ + List of projects this team has collaborator access to. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for projects returned from this connection + """ + filterBy: ProjectV2Filters = {} + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + The query to search projects by. + """ + query: String = "" + ): ProjectV2Connection! + + """ + A list of repositories this team has access to. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for the connection. + """ + orderBy: TeamRepositoryOrder + + """ + The search string to look for. Repositories will be returned where the name contains your search string. + """ + query: String + ): TeamRepositoryConnection! + + """ + The HTTP path for this team's repositories + """ + repositoriesResourcePath: URI! + + """ + The HTTP URL for this team's repositories + """ + repositoriesUrl: URI! + + """ + The HTTP path for this team + """ + resourcePath: URI! + + """ + What algorithm is used for review assignment for this team + """ + reviewRequestDelegationAlgorithm: TeamReviewAssignmentAlgorithm @preview(toggledBy: "stone-crop-preview") + + """ + True if review assignment is enabled for this team + """ + reviewRequestDelegationEnabled: Boolean! @preview(toggledBy: "stone-crop-preview") + + """ + How many team members are required for review assignment for this team + """ + reviewRequestDelegationMemberCount: Int @preview(toggledBy: "stone-crop-preview") + + """ + When assigning team members via delegation, whether the entire team should be notified as well. + """ + reviewRequestDelegationNotifyTeam: Boolean! @preview(toggledBy: "stone-crop-preview") + + """ + The slug corresponding to the team. + """ + slug: String! + + """ + The HTTP path for this team's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL for this team's teams + """ + teamsUrl: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this team + """ + url: URI! + + """ + Team is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Audit log entry for a team.add_member event. +""" +type TeamAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.add_repository event. +""" +type TeamAddRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Metadata for an audit entry with action team.* +""" +interface TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a team.change_parent_team event. +""" +type TeamChangeParentTeamAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new parent team. + """ + parentTeam: Team + + """ + The name of the new parent team + """ + parentTeamName: String + + """ + The name of the former parent team + """ + parentTeamNameWas: String + + """ + The HTTP path for the parent team + """ + parentTeamResourcePath: URI + + """ + The HTTP URL for the parent team + """ + parentTeamUrl: URI + + """ + The former parent team. + """ + parentTeamWas: Team + + """ + The HTTP path for the previous parent team + """ + parentTeamWasResourcePath: URI + + """ + The HTTP URL for the previous parent team + """ + parentTeamWasUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Team. +""" +type TeamConnection { + """ + A list of edges. + """ + edges: [TeamEdge] + + """ + A list of nodes. + """ + nodes: [Team] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A team discussion. +""" +type TeamDiscussion implements Comment & Deletable & Node & Reactable & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the discussion's team. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the discussion body hash. + """ + bodyVersion: String! + + """ + A list of comments on this discussion. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + When provided, filters the connection such that results begin with the comment with this number. + """ + fromComment: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamDiscussionCommentOrder + ): TeamDiscussionCommentConnection! + + """ + The HTTP path for discussion comments + """ + commentsResourcePath: URI! + + """ + The HTTP URL for discussion comments + """ + commentsUrl: URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Whether or not the discussion is pinned. + """ + isPinned: Boolean! + + """ + Whether or not the discussion is only visible to team members and org admins. + """ + isPrivate: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the discussion within its team. + """ + number: Int! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The HTTP path for this discussion + """ + resourcePath: URI! + + """ + The team that defines the context of this discussion. + """ + team: Team! + + """ + The title of the discussion + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this discussion + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Whether or not the current viewer can pin this discussion. + """ + viewerCanPin: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A comment on a team discussion. +""" +type TeamDiscussionComment implements Comment & Deletable & Node & Reactable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the comment's team. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + The current version of the body content. + """ + bodyVersion: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The discussion this comment is about. + """ + discussion: TeamDiscussion! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the comment number. + """ + number: Int! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The HTTP path for this comment + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this comment + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for TeamDiscussionComment. +""" +type TeamDiscussionCommentConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionCommentEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussionComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussionComment +} + +""" +Ways in which team discussion comment connections can be ordered. +""" +input TeamDiscussionCommentOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field by which to order nodes. + """ + field: TeamDiscussionCommentOrderField! +} + +""" +Properties by which team discussion comment connections can be ordered. +""" +enum TeamDiscussionCommentOrderField { + """ + Allows sequential ordering of team discussion comments (which is equivalent to chronological ordering). + """ + NUMBER +} + +""" +The connection type for TeamDiscussion. +""" +type TeamDiscussionConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussion +} + +""" +Ways in which team discussion connections can be ordered. +""" +input TeamDiscussionOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field by which to order nodes. + """ + field: TeamDiscussionOrderField! +} + +""" +Properties by which team discussion connections can be ordered. +""" +enum TeamDiscussionOrderField { + """ + Allows chronological ordering of team discussions. + """ + CREATED_AT +} + +""" +An edge in a connection. +""" +type TeamEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Team +} + +""" +The connection type for User. +""" +type TeamMemberConnection { + """ + A list of edges. + """ + edges: [TeamMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a member of a team. +""" +type TeamMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The HTTP path to the organization's member access page. + """ + memberAccessResourcePath: URI! + + """ + The HTTP URL to the organization's member access page. + """ + memberAccessUrl: URI! + node: User! + + """ + The role the member has on the team. + """ + role: TeamMemberRole! +} + +""" +Ordering options for team member connections +""" +input TeamMemberOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order team members by. + """ + field: TeamMemberOrderField! +} + +""" +Properties by which team member connections can be ordered. +""" +enum TeamMemberOrderField { + """ + Order team members by creation time + """ + CREATED_AT + + """ + Order team members by login + """ + LOGIN +} + +""" +The possible team member roles; either 'maintainer' or 'member'. +""" +enum TeamMemberRole { + """ + A team maintainer has permission to add and remove team members. + """ + MAINTAINER + + """ + A team member has no administrative permissions on the team. + """ + MEMBER +} + +""" +Defines which types of team members are included in the returned list. Can be one of IMMEDIATE, CHILD_TEAM or ALL. +""" +enum TeamMembershipType { + """ + Includes immediate and child team members for the team. + """ + ALL + + """ + Includes only child team members for the team. + """ + CHILD_TEAM + + """ + Includes only immediate members of the team. + """ + IMMEDIATE +} + +""" +Ways in which team connections can be ordered. +""" +input TeamOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field in which to order nodes by. + """ + field: TeamOrderField! +} + +""" +Properties by which team connections can be ordered. +""" +enum TeamOrderField { + """ + Allows ordering a list of teams by name. + """ + NAME +} + +""" +The possible team privacy values. +""" +enum TeamPrivacy { + """ + A secret team can only be seen by its members. + """ + SECRET + + """ + A visible team can be seen and @mentioned by every member of the organization. + """ + VISIBLE +} + +""" +Audit log entry for a team.remove_member event. +""" +type TeamRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.remove_repository event. +""" +type TeamRemoveRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Repository. +""" +type TeamRepositoryConnection { + """ + A list of edges. + """ + edges: [TeamRepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a team repository. +""" +type TeamRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: Repository! + + """ + The permission level the team has on the repository + """ + permission: RepositoryPermission! +} + +""" +Ordering options for team repository connections +""" +input TeamRepositoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: TeamRepositoryOrderField! +} + +""" +Properties by which team repository connections can be ordered. +""" +enum TeamRepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by permission + """ + PERMISSION + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by number of stargazers + """ + STARGAZERS + + """ + Order repositories by update time + """ + UPDATED_AT +} + +""" +The possible team review assignment algorithms +""" +enum TeamReviewAssignmentAlgorithm @preview(toggledBy: "stone-crop-preview") { + """ + Balance review load across the entire team + """ + LOAD_BALANCE + + """ + Alternate reviews between each team member + """ + ROUND_ROBIN +} + +""" +The role of a user on a team. +""" +enum TeamRole { + """ + User has admin rights on the team. + """ + ADMIN + + """ + User is a member of the team. + """ + MEMBER +} + +""" +A text match within a search result. +""" +type TextMatch { + """ + The specific text fragment within the property matched on. + """ + fragment: String! + + """ + Highlights within the matched fragment. + """ + highlights: [TextMatchHighlight!]! + + """ + The property matched on. + """ + property: String! +} + +""" +Represents a single highlight in a search result match. +""" +type TextMatchHighlight { + """ + The indice in the fragment where the matched text begins. + """ + beginIndice: Int! + + """ + The indice in the fragment where the matched text ends. + """ + endIndice: Int! + + """ + The text matched. + """ + text: String! +} + +""" +A topic aggregates entities that are related to a subject. +""" +type Topic implements Node & Starrable { + id: ID! + + """ + The topic's name. + """ + name: String! + + """ + A list of related topics, including aliases of this topic, sorted with the most relevant + first. Returns up to 10 Topics. + """ + relatedTopics( + """ + How many topics to return. + """ + first: Int = 3 + ): [Topic!]! + + """ + A list of repositories. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + + """ + If true, only repositories whose owner can be sponsored via GitHub Sponsors will be returned. + """ + sponsorableOnly: Boolean = false + ): RepositoryConnection! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Metadata for an audit entry with a topic. +""" +interface TopicAuditEntryData { + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String +} + +""" +Reason that the suggested topic is declined. +""" +enum TopicSuggestionDeclineReason { + """ + The suggested topic is not relevant to the repository. + """ + NOT_RELEVANT + + """ + The viewer does not like the suggested topic. + """ + PERSONAL_PREFERENCE + + """ + The suggested topic is too general for the repository. + """ + TOO_GENERAL + + """ + The suggested topic is too specific for the repository (e.g. #ruby-on-rails-version-4-2-1). + """ + TOO_SPECIFIC +} + +""" +The possible states of a tracked issue. +""" +enum TrackedIssueStates { + """ + The tracked issue is closed + """ + CLOSED + + """ + The tracked issue is open + """ + OPEN +} + +""" +Autogenerated input type of TransferEnterpriseOrganization +""" +input TransferEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise where the organization should be transferred. + """ + destinationEnterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The ID of the organization to transfer. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of TransferEnterpriseOrganization +""" +type TransferEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization for which a transfer was initiated. + """ + organization: Organization +} + +""" +Autogenerated input type of TransferIssue +""" +input TransferIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether to create labels if they don't exist in the target repository (matched by name) + """ + createLabelsIfMissing: Boolean = false + + """ + The Node ID of the issue to be transferred + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + The Node ID of the repository the issue should be transferred to + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of TransferIssue +""" +type TransferIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was transferred + """ + issue: Issue +} + +""" +Represents a 'transferred' event on a given issue or pull request. +""" +type TransferredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The repository this came from + """ + fromRepository: Repository + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Represents a Git tree. +""" +type Tree implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + A list of tree entries. + """ + entries: [TreeEntry!] + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +Represents a Git tree entry. +""" +type TreeEntry { + """ + The extension of the file + """ + extension: String + + """ + Whether or not this tree entry is generated + """ + isGenerated: Boolean! + + """ + The programming language this file is written in. + """ + language: Language + + """ + Number of lines in the file. + """ + lineCount: Int + + """ + Entry file mode. + """ + mode: Int! + + """ + Entry file name. + """ + name: String! + + """ + Entry file name. (Base64-encoded) + """ + nameRaw: Base64String! + + """ + Entry file object. + """ + object: GitObject + + """ + Entry file Git object ID. + """ + oid: GitObjectID! + + """ + The full path of the file. + """ + path: String + + """ + The full path of the file. (Base64-encoded) + """ + pathRaw: Base64String + + """ + The Repository the tree entry belongs to + """ + repository: Repository! + + """ + Entry byte size + """ + size: Int! + + """ + If the TreeEntry is for a directory occupied by a submodule project, this returns the corresponding submodule + """ + submodule: Submodule + + """ + Entry file type. + """ + type: String! +} + +""" +An RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI string. +""" +scalar URI + +""" +Autogenerated input type of UnarchiveProjectV2Item +""" +input UnarchiveProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the ProjectV2Item to unarchive. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project to archive the item from. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of UnarchiveProjectV2Item +""" +type UnarchiveProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item unarchived from the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of UnarchiveRepository +""" +input UnarchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to unarchive. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UnarchiveRepository +""" +type UnarchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was unarchived. + """ + repository: Repository +} + +""" +Represents an 'unassigned' event on any assignable object. +""" +type UnassignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was unassigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the subject (user) who was unassigned. + """ + user: User + @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Autogenerated input type of UnfollowOrganization +""" +input UnfollowOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the organization to unfollow. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of UnfollowOrganization +""" +type UnfollowOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that was unfollowed. + """ + organization: Organization +} + +""" +Autogenerated input type of UnfollowUser +""" +input UnfollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to unfollow. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of UnfollowUser +""" +type UnfollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was unfollowed. + """ + user: User +} + +""" +Represents a type that can be retrieved by a URL. +""" +interface UniformResourceLocatable { + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +Represents an unknown signature on a Commit or Tag. +""" +type UnknownSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Represents an 'unlabeled' event on a given issue or pull request. +""" +type UnlabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the label associated with the 'unlabeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Autogenerated input type of UnlinkProjectV2FromRepository +""" +input UnlinkProjectV2FromRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to unlink from the repository. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The ID of the repository to unlink from the project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UnlinkProjectV2FromRepository +""" +type UnlinkProjectV2FromRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the project is no longer linked to. + """ + repository: Repository +} + +""" +Autogenerated input type of UnlinkProjectV2FromTeam +""" +input UnlinkProjectV2FromTeamInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to unlink from the team. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The ID of the team to unlink from the project. + """ + teamId: ID! @possibleTypes(concreteTypes: ["Team"]) +} + +""" +Autogenerated return type of UnlinkProjectV2FromTeam +""" +type UnlinkProjectV2FromTeamPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team the project is unlinked from + """ + team: Team +} + +""" +Autogenerated input type of UnlinkRepositoryFromProject +""" +input UnlinkRepositoryFromProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project linked to the Repository. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The ID of the Repository linked to the Project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UnlinkRepositoryFromProject +""" +type UnlinkRepositoryFromProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UnlockLockable +""" +input UnlockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the item to be unlocked. + """ + lockableId: ID! @possibleTypes(concreteTypes: ["Discussion", "Issue", "PullRequest"], abstractType: "Lockable") +} + +""" +Autogenerated return type of UnlockLockable +""" +type UnlockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlocked. + """ + unlockedRecord: Lockable +} + +""" +Represents an 'unlocked' event on a given issue or pull request. +""" +type UnlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object that was unlocked. + """ + lockable: Lockable! +} + +""" +Autogenerated input type of UnmarkDiscussionCommentAsAnswer +""" +input UnmarkDiscussionCommentAsAnswerInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to unmark as an answer. + """ + id: ID! @possibleTypes(concreteTypes: ["DiscussionComment"]) +} + +""" +Autogenerated return type of UnmarkDiscussionCommentAsAnswer +""" +type UnmarkDiscussionCommentAsAnswerPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that includes the comment. + """ + discussion: Discussion +} + +""" +Autogenerated input type of UnmarkFileAsViewed +""" +input UnmarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The path of the file to mark as unviewed + """ + path: String! + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of UnmarkFileAsViewed +""" +type UnmarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UnmarkIssueAsDuplicate +""" +input UnmarkIssueAsDuplicateInput { + """ + ID of the issue or pull request currently considered canonical/authoritative/original. + """ + canonicalId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue or pull request currently marked as a duplicate. + """ + duplicateId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") +} + +""" +Autogenerated return type of UnmarkIssueAsDuplicate +""" +type UnmarkIssueAsDuplicatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue or pull request that was marked as a duplicate. + """ + duplicate: IssueOrPullRequest +} + +""" +Represents an 'unmarked_as_duplicate' event on a given issue or pull request. +""" +type UnmarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +Autogenerated input type of UnminimizeComment +""" +input UnminimizeCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + @possibleTypes( + concreteTypes: ["CommitComment", "DiscussionComment", "GistComment", "IssueComment", "PullRequestReviewComment"] + abstractType: "Minimizable" + ) +} + +""" +Autogenerated return type of UnminimizeComment +""" +type UnminimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was unminimized. + """ + unminimizedComment: Minimizable +} + +""" +Autogenerated input type of UnpinIssue +""" +input UnpinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be unpinned + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of UnpinIssue +""" +type UnpinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was unpinned + """ + issue: Issue +} + +""" +Represents an 'unpinned' event on a given issue or pull request. +""" +type UnpinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Autogenerated input type of UnresolveReviewThread +""" +input UnresolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to unresolve + """ + threadId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewThread"]) +} + +""" +Autogenerated return type of UnresolveReviewThread +""" +type UnresolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Represents an 'unsubscribed' event on a given `Subscribable`. +""" +type UnsubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +Entities that can be updated. +""" +interface Updatable { + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +Comments that can be updated. +""" +interface UpdatableComment { + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! +} + +""" +Autogenerated input type of UpdateBranchProtectionRule +""" +input UpdateBranchProtectionRuleInput { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean + + """ + The global relay id of the branch protection rule to be updated. + """ + branchProtectionRuleId: ID! @possibleTypes(concreteTypes: ["BranchProtectionRule"]) + + """ + A list of User, Team, or App IDs allowed to bypass force push targeting matching branches. + """ + bypassForcePushActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass pull requests targeting matching branches. + """ + bypassPullRequestActorIds: [ID!] + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean + + """ + Whether users can pull changes from upstream when the branch is locked. Set to + `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String + + """ + A list of User, Team, or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + The list of required status checks + """ + requiredStatusChecks: [RequiredStatusCheckInput!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User, Team, or App IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] +} + +""" +Autogenerated return type of UpdateBranchProtectionRule +""" +type UpdateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckRun +""" +input UpdateCheckRunInput { + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] + + """ + The node of the check. + """ + checkRunId: ID! @possibleTypes(concreteTypes: ["CheckRun"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The name of the check. + """ + name: String + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The current status. + """ + status: RequestableCheckStatusState +} + +""" +Autogenerated return type of UpdateCheckRun +""" +type UpdateCheckRunPayload { + """ + The updated check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckSuitePreferences +""" +input UpdateCheckSuitePreferencesInput { + """ + The check suite preferences to modify. + """ + autoTriggerPreferences: [CheckSuiteAutoTriggerPreference!]! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UpdateCheckSuitePreferences +""" +type UpdateCheckSuitePreferencesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateDiscussionComment +""" +input UpdateDiscussionCommentInput { + """ + The new contents of the comment body. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to update. + """ + commentId: ID! @possibleTypes(concreteTypes: ["DiscussionComment"]) +} + +""" +Autogenerated return type of UpdateDiscussionComment +""" +type UpdateDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The modified discussion comment. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of UpdateDiscussion +""" +input UpdateDiscussionInput { + """ + The new contents of the discussion body. + """ + body: String + + """ + The Node ID of a discussion category within the same repository to change this discussion to. + """ + categoryId: ID @possibleTypes(concreteTypes: ["DiscussionCategory"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to update. + """ + discussionId: ID! @possibleTypes(concreteTypes: ["Discussion"]) + + """ + The new discussion title. + """ + title: String +} + +""" +Autogenerated return type of UpdateDiscussion +""" +type UpdateDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The modified discussion. + """ + discussion: Discussion +} + +""" +Autogenerated input type of UpdateEnterpriseAdministratorRole +""" +input UpdateEnterpriseAdministratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a administrator whose role is being changed. + """ + login: String! + + """ + The new role for the Enterprise administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +Autogenerated return type of UpdateEnterpriseAdministratorRole +""" +type UpdateEnterpriseAdministratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of changing the administrator's role. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting +""" +input UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the allow private repository forking setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the allow private repository forking policy on the enterprise. + """ + policyValue: EnterpriseAllowPrivateRepositoryForkingPolicyValue + + """ + The value for the allow private repository forking setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting +""" +type UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated allow private repository forking setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the allow private repository forking setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseDefaultRepositoryPermissionSetting +""" +input UpdateEnterpriseDefaultRepositoryPermissionSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the base repository permission setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the base repository permission setting on the enterprise. + """ + settingValue: EnterpriseDefaultRepositoryPermissionSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseDefaultRepositoryPermissionSetting +""" +type UpdateEnterpriseDefaultRepositoryPermissionSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated base repository permission setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the base repository permission setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting +""" +input UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can change repository visibility setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can change repository visibility setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting +""" +type UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can change repository visibility setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can change repository visibility setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanCreateRepositoriesSetting +""" +input UpdateEnterpriseMembersCanCreateRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can create repositories setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + Allow members to create internal repositories. Defaults to current value. + """ + membersCanCreateInternalRepositories: Boolean + + """ + Allow members to create private repositories. Defaults to current value. + """ + membersCanCreatePrivateRepositories: Boolean + + """ + Allow members to create public repositories. Defaults to current value. + """ + membersCanCreatePublicRepositories: Boolean + + """ + When false, allow member organizations to set their own repository creation member privileges. + """ + membersCanCreateRepositoriesPolicyEnabled: Boolean + + """ + Value for the members can create repositories setting on the enterprise. This + or the granular public/private/internal allowed fields (but not both) must be provided. + """ + settingValue: EnterpriseMembersCanCreateRepositoriesSettingValue +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanCreateRepositoriesSetting +""" +type UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can create repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can create repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteIssuesSetting +""" +input UpdateEnterpriseMembersCanDeleteIssuesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete issues setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can delete issues setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteIssuesSetting +""" +type UpdateEnterpriseMembersCanDeleteIssuesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete issues setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete issues setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting +""" +input UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete repositories setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can delete repositories setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting +""" +type UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting +""" +input UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can invite collaborators setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can invite collaborators setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting +""" +type UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can invite collaborators setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can invite collaborators setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanMakePurchasesSetting +""" +input UpdateEnterpriseMembersCanMakePurchasesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can make purchases setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can make purchases setting on the enterprise. + """ + settingValue: EnterpriseMembersCanMakePurchasesSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanMakePurchasesSetting +""" +type UpdateEnterpriseMembersCanMakePurchasesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can make purchases setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can make purchases setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting +""" +input UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can update protected branches setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can update protected branches setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting +""" +type UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can update protected branches setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can update protected branches setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting +""" +input UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can view dependency insights setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can view dependency insights setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting +""" +type UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can view dependency insights setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can view dependency insights setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseOrganizationProjectsSetting +""" +input UpdateEnterpriseOrganizationProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the organization projects setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the organization projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseOrganizationProjectsSetting +""" +type UpdateEnterpriseOrganizationProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated organization projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the organization projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseOwnerOrganizationRole +""" +input UpdateEnterpriseOwnerOrganizationRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the owner belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The ID of the organization for membership change. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The role to assume in the organization. + """ + organizationRole: RoleInOrganization! +} + +""" +Autogenerated return type of UpdateEnterpriseOwnerOrganizationRole +""" +type UpdateEnterpriseOwnerOrganizationRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of changing the owner's organization role. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseProfile +""" +input UpdateEnterpriseProfileInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The description of the enterprise. + """ + description: String + + """ + The Enterprise ID to update. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The location of the enterprise. + """ + location: String + + """ + The name of the enterprise. + """ + name: String + + """ + The URL of the enterprise's website. + """ + websiteUrl: String +} + +""" +Autogenerated return type of UpdateEnterpriseProfile +""" +type UpdateEnterpriseProfilePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise +} + +""" +Autogenerated input type of UpdateEnterpriseRepositoryProjectsSetting +""" +input UpdateEnterpriseRepositoryProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the repository projects setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the repository projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseRepositoryProjectsSetting +""" +type UpdateEnterpriseRepositoryProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated repository projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the repository projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTeamDiscussionsSetting +""" +input UpdateEnterpriseTeamDiscussionsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the team discussions setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the team discussions setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTeamDiscussionsSetting +""" +type UpdateEnterpriseTeamDiscussionsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated team discussions setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the team discussions setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting +""" +input UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the two factor authentication required setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the two factor authentication required setting on the enterprise. + """ + settingValue: EnterpriseEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting +""" +type UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated two factor authentication required setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the two factor authentication required setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnvironment +""" +input UpdateEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the environment. + """ + environmentId: ID! @possibleTypes(concreteTypes: ["Environment"]) + + """ + The ids of users or teams that can approve deployments to this environment + """ + reviewers: [ID!] + + """ + The wait timer in minutes. + """ + waitTimer: Int +} + +""" +Autogenerated return type of UpdateEnvironment +""" +type UpdateEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated environment. + """ + environment: Environment +} + +""" +Autogenerated input type of UpdateIpAllowListEnabledSetting +""" +input UpdateIpAllowListEnabledSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner on which to set the IP allow list enabled setting. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["App", "Enterprise", "Organization"], abstractType: "IpAllowListOwner") + + """ + The value for the IP allow list enabled setting. + """ + settingValue: IpAllowListEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateIpAllowListEnabledSetting +""" +type UpdateIpAllowListEnabledSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list owner on which the setting was updated. + """ + owner: IpAllowListOwner +} + +""" +Autogenerated input type of UpdateIpAllowListEntry +""" +input UpdateIpAllowListEntryInput { + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to update. + """ + ipAllowListEntryId: ID! @possibleTypes(concreteTypes: ["IpAllowListEntry"]) + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! + + """ + An optional name for the IP allow list entry. + """ + name: String +} + +""" +Autogenerated return type of UpdateIpAllowListEntry +""" +type UpdateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was updated. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of UpdateIpAllowListForInstalledAppsEnabledSetting +""" +input UpdateIpAllowListForInstalledAppsEnabledSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["App", "Enterprise", "Organization"], abstractType: "IpAllowListOwner") + + """ + The value for the IP allow list configuration for installed GitHub Apps setting. + """ + settingValue: IpAllowListForInstalledAppsEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateIpAllowListForInstalledAppsEnabledSetting +""" +type UpdateIpAllowListForInstalledAppsEnabledSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list owner on which the setting was updated. + """ + owner: IpAllowListOwner +} + +""" +Autogenerated input type of UpdateIssueComment +""" +input UpdateIssueCommentInput { + """ + The updated text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IssueComment to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["IssueComment"]) +} + +""" +Autogenerated return type of UpdateIssueComment +""" +type UpdateIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + issueComment: IssueComment +} + +""" +Autogenerated input type of UpdateIssue +""" +input UpdateIssueInput { + """ + An array of Node IDs of users for this issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body for the issue description. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Issue to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] + + """ + The desired issue state. + """ + state: IssueState + + """ + The title for the issue. + """ + title: String +} + +""" +Autogenerated return type of UpdateIssue +""" +type UpdateIssuePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue. + """ + issue: Issue +} + +""" +Autogenerated input type of UpdateLabel +""" +input UpdateLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A 6 character hex code, without the leading #, identifying the updated color of the label. + """ + color: String + + """ + A brief description of the label, such as its purpose. + """ + description: String + + """ + The Node ID of the label to be updated. + """ + id: ID! @possibleTypes(concreteTypes: ["Label"]) + + """ + The updated name of the label. + """ + name: String +} + +""" +Autogenerated return type of UpdateLabel +""" +type UpdateLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated label. + """ + label: Label +} + +""" +Autogenerated input type of UpdateNotificationRestrictionSetting +""" +input UpdateNotificationRestrictionSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner on which to set the restrict notifications setting. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Enterprise", "Organization"], abstractType: "VerifiableDomainOwner") + + """ + The value for the restrict notifications setting. + """ + settingValue: NotificationRestrictionSettingValue! +} + +""" +Autogenerated return type of UpdateNotificationRestrictionSetting +""" +type UpdateNotificationRestrictionSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner on which the setting was updated. + """ + owner: VerifiableDomainOwner +} + +""" +Autogenerated input type of UpdateOrganizationAllowPrivateRepositoryForkingSetting +""" +input UpdateOrganizationAllowPrivateRepositoryForkingSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Enable forking of private repositories in the organization? + """ + forkingEnabled: Boolean! + + """ + The ID of the organization on which to set the allow private repository forking setting. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of UpdateOrganizationAllowPrivateRepositoryForkingSetting +""" +type UpdateOrganizationAllowPrivateRepositoryForkingSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the allow private repository forking setting. + """ + message: String + + """ + The organization with the updated allow private repository forking setting. + """ + organization: Organization +} + +""" +Autogenerated input type of UpdateOrganizationWebCommitSignoffSetting +""" +input UpdateOrganizationWebCommitSignoffSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization on which to set the web commit signoff setting. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + Enable signoff on web-based commits for repositories in the organization? + """ + webCommitSignoffRequired: Boolean! +} + +""" +Autogenerated return type of UpdateOrganizationWebCommitSignoffSetting +""" +type UpdateOrganizationWebCommitSignoffSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the web commit signoff setting. + """ + message: String + + """ + The organization with the updated web commit signoff setting. + """ + organization: Organization +} + +""" +Autogenerated input type of UpdateProjectCard +""" +input UpdateProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not the ProjectCard should be archived + """ + isArchived: Boolean + + """ + The note of ProjectCard. + """ + note: String + + """ + The ProjectCard ID to update. + """ + projectCardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) +} + +""" +Autogenerated return type of UpdateProjectCard +""" +type UpdateProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Autogenerated input type of UpdateProjectColumn +""" +input UpdateProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project column. + """ + name: String! + + """ + The ProjectColumn ID to update. + """ + projectColumnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of UpdateProjectColumn +""" +type UpdateProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project column. + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of UpdateProjectDraftIssue +""" +input UpdateProjectDraftIssueInput { + """ + The IDs of the assignees of the draft issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body of the draft issue. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the draft issue to update. + """ + draftIssueId: ID! @possibleTypes(concreteTypes: ["DraftIssue"]) + + """ + The title of the draft issue. + """ + title: String +} + +""" +Autogenerated return type of UpdateProjectDraftIssue +""" +type UpdateProjectDraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue updated in the project. + """ + draftIssue: DraftIssue +} + +""" +Autogenerated input type of UpdateProject +""" +input UpdateProjectInput { + """ + The description of project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project. + """ + name: String + + """ + The Project ID to update. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + Whether the project is public or not. + """ + public: Boolean + + """ + Whether the project is open or closed. + """ + state: ProjectState +} + +""" +Autogenerated input type of UpdateProjectNext +""" +input UpdateProjectNextInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Set the project to closed or open. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `closed` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + closed: Boolean + + """ + Set the readme description of the project. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `description` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + description: String + + """ + The ID of the Project to update. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `projectId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + projectId: ID @possibleTypes(concreteTypes: ["ProjectNext"]) + + """ + Set the project to public or private. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `public` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + public: Boolean + + """ + Set the short description of the project. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `shortDescription` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + shortDescription: String + + """ + Set the title of the project. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `title` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + title: String +} + +""" +Autogenerated input type of UpdateProjectNextItemField +""" +input UpdateProjectNextItemFieldInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the field to be updated. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `fieldId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + fieldId: ID @possibleTypes(concreteTypes: ["ProjectNextField"]) + + """ + The id of the item to be updated. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `itemId` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + itemId: ID @possibleTypes(concreteTypes: ["ProjectNextItem"]) + + """ + The ID of the Project. This field is required. + """ + projectId: ID @possibleTypes(concreteTypes: ["ProjectNext"]) + + """ + The value which will be set on the field. This field is required. + + **Upcoming Change on 2023-01-01 UTC** + **Description:** `value` will be removed. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, + to find a suitable replacement. + **Reason:** The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. + """ + value: String +} + +""" +Autogenerated return type of UpdateProjectNextItemField +""" +type UpdateProjectNextItemFieldPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated item. + """ + projectNextItem: ProjectNextItem + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Autogenerated return type of UpdateProjectNext +""" +type UpdateProjectNextPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Project. + """ + projectNext: ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) +} + +""" +Autogenerated return type of UpdateProject +""" +type UpdateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project. + """ + project: Project +} + +""" +Autogenerated input type of UpdateProjectV2DraftIssue +""" +input UpdateProjectV2DraftIssueInput { + """ + The IDs of the assignees of the draft issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body of the draft issue. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the draft issue to update. + """ + draftIssueId: ID! @possibleTypes(concreteTypes: ["DraftIssue"]) + + """ + The title of the draft issue. + """ + title: String +} + +""" +Autogenerated return type of UpdateProjectV2DraftIssue +""" +type UpdateProjectV2DraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue updated in the project. + """ + draftIssue: DraftIssue +} + +""" +Autogenerated input type of UpdateProjectV2 +""" +input UpdateProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Set the project to closed or open. + """ + closed: Boolean + + """ + The ID of the Project to update. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + Set the project to public or private. + """ + public: Boolean + + """ + Set the readme description of the project. + """ + readme: String + + """ + Set the short description of the project. + """ + shortDescription: String + + """ + Set the title of the project. + """ + title: String +} + +""" +Autogenerated input type of UpdateProjectV2ItemFieldValue +""" +input UpdateProjectV2ItemFieldValueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the field to be updated. + """ + fieldId: ID! + @possibleTypes( + concreteTypes: ["ProjectV2Field", "ProjectV2IterationField", "ProjectV2SingleSelectField"] + abstractType: "ProjectV2FieldConfiguration" + ) + + """ + The ID of the item to be updated. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) + + """ + The value which will be set on the field. + """ + value: ProjectV2FieldValue! +} + +""" +Autogenerated return type of UpdateProjectV2ItemFieldValue +""" +type UpdateProjectV2ItemFieldValuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated item. + """ + projectV2Item: ProjectV2Item +} + +""" +Autogenerated input type of UpdateProjectV2ItemPosition +""" +input UpdateProjectV2ItemPositionInput { + """ + The ID of the item to position this item after. If omitted or set to null the item will be moved to top. + """ + afterId: ID @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the item to be moved. + """ + itemId: ID! @possibleTypes(concreteTypes: ["ProjectV2Item"]) + + """ + The ID of the Project. + """ + projectId: ID! @possibleTypes(concreteTypes: ["ProjectV2"]) +} + +""" +Autogenerated return type of UpdateProjectV2ItemPosition +""" +type UpdateProjectV2ItemPositionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The items in the new order + """ + items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2ItemConnection +} + +""" +Autogenerated return type of UpdateProjectV2 +""" +type UpdateProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of UpdatePullRequestBranch +""" +input UpdatePullRequestBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The head ref oid for the upstream branch. + """ + expectedHeadOid: GitObjectID + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of UpdatePullRequestBranch +""" +type UpdatePullRequestBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UpdatePullRequest +""" +input UpdatePullRequestInput { + """ + An array of Node IDs of users for this pull request. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. + """ + baseRefName: String + + """ + The contents of the pull request. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + An array of Node IDs of labels for this pull request. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean + + """ + The Node ID of the milestone for this pull request. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this pull request. + """ + projectIds: [ID!] + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The target state of the pull request. + """ + state: PullRequestUpdateState + + """ + The title of the pull request. + """ + title: String +} + +""" +Autogenerated return type of UpdatePullRequest +""" +type UpdatePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UpdatePullRequestReviewComment +""" +input UpdatePullRequestReviewCommentInput { + """ + The text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the comment to modify. + """ + pullRequestReviewCommentId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) +} + +""" +Autogenerated return type of UpdatePullRequestReviewComment +""" +type UpdatePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + pullRequestReviewComment: PullRequestReviewComment +} + +""" +Autogenerated input type of UpdatePullRequestReview +""" +input UpdatePullRequestReviewInput { + """ + The contents of the pull request review body. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of UpdatePullRequestReview +""" +type UpdatePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of UpdateRef +""" +input UpdateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Permit updates of branch Refs that are not fast-forwards? + """ + force: Boolean = false + + """ + The GitObjectID that the Ref shall be updated to target. + """ + oid: GitObjectID! + + """ + The Node ID of the Ref to be updated. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) +} + +""" +Autogenerated return type of UpdateRef +""" +type UpdateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Ref. + """ + ref: Ref +} + +""" +Autogenerated input type of UpdateRefs +""" +input UpdateRefsInput @preview(toggledBy: "update-refs-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A list of ref updates. + """ + refUpdates: [RefUpdate!]! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UpdateRefs +""" +type UpdateRefsPayload @preview(toggledBy: "update-refs-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateRepository +""" +input UpdateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A new description for the repository. Pass an empty string to erase the existing description. + """ + description: String + + """ + Indicates if the repository should have the discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean + + """ + Indicates if the repository should have the project boards feature enabled. + """ + hasProjectsEnabled: Boolean + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean + + """ + The URL for a web page about this repository. Pass an empty string to erase the existing URL. + """ + homepageUrl: URI + + """ + The new name of the repository. + """ + name: String + + """ + The ID of the repository to update. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Whether this repository should be marked as a template such that anyone who + can access it can create new repositories with the same files and directory structure. + """ + template: Boolean +} + +""" +Autogenerated return type of UpdateRepository +""" +type UpdateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateRepositoryWebCommitSignoffSetting +""" +input UpdateRepositoryWebCommitSignoffSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to update. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Indicates if the repository should require signoff on web-based commits. + """ + webCommitSignoffRequired: Boolean! +} + +""" +Autogenerated return type of UpdateRepositoryWebCommitSignoffSetting +""" +type UpdateRepositoryWebCommitSignoffSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the web commit signoff setting. + """ + message: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateSponsorshipPreferences +""" +input UpdateSponsorshipPreferencesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Specify whether others should be able to see that the sponsor is sponsoring + the sponsorable. Public visibility still does not reveal which tier is used. + """ + privacyLevel: SponsorshipPrivacy = PUBLIC + + """ + Whether the sponsor should receive email updates from the sponsorable. + """ + receiveEmails: Boolean = true + + """ + The ID of the user or organization who is acting as the sponsor, paying for + the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsor") + + """ + The username of the user or organization who is acting as the sponsor, paying + for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "Sponsorable") + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String +} + +""" +Autogenerated return type of UpdateSponsorshipPreferences +""" +type UpdateSponsorshipPreferencesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The sponsorship that was updated. + """ + sponsorship: Sponsorship +} + +""" +Autogenerated input type of UpdateSubscription +""" +input UpdateSubscriptionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new state of the subscription. + """ + state: SubscriptionState! + + """ + The Node ID of the subscribable object to modify. + """ + subscribableId: ID! + @possibleTypes( + concreteTypes: ["Commit", "Discussion", "Issue", "PullRequest", "Repository", "Team", "TeamDiscussion"] + abstractType: "Subscribable" + ) +} + +""" +Autogenerated return type of UpdateSubscription +""" +type UpdateSubscriptionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The input subscribable entity. + """ + subscribable: Subscribable +} + +""" +Autogenerated input type of UpdateTeamDiscussionComment +""" +input UpdateTeamDiscussionCommentInput { + """ + The updated text of the comment. + """ + body: String! + + """ + The current version of the body content. + """ + bodyVersion: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussionComment"]) +} + +""" +Autogenerated return type of UpdateTeamDiscussionComment +""" +type UpdateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + teamDiscussionComment: TeamDiscussionComment +} + +""" +Autogenerated input type of UpdateTeamDiscussion +""" +input UpdateTeamDiscussionInput { + """ + The updated text of the discussion. + """ + body: String + + """ + The current version of the body content. If provided, this update operation + will be rejected if the given version does not match the latest version on the server. + """ + bodyVersion: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) + + """ + If provided, sets the pinned state of the updated discussion. + """ + pinned: Boolean + + """ + The updated title of the discussion. + """ + title: String +} + +""" +Autogenerated return type of UpdateTeamDiscussion +""" +type UpdateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated discussion. + """ + teamDiscussion: TeamDiscussion +} + +""" +Autogenerated input type of UpdateTeamReviewAssignment +""" +input UpdateTeamReviewAssignmentInput @preview(toggledBy: "stone-crop-preview") { + """ + The algorithm to use for review assignment + """ + algorithm: TeamReviewAssignmentAlgorithm = ROUND_ROBIN + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Turn on or off review assignment + """ + enabled: Boolean! + + """ + An array of team member IDs to exclude + """ + excludedTeamMemberIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The Node ID of the team to update review assignments of + """ + id: ID! @possibleTypes(concreteTypes: ["Team"]) + + """ + Notify the entire team of the PR if it is delegated + """ + notifyTeam: Boolean = true + + """ + The number of team members to assign + """ + teamMemberCount: Int = 1 +} + +""" +Autogenerated return type of UpdateTeamReviewAssignment +""" +type UpdateTeamReviewAssignmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team that was modified + """ + team: Team +} + +""" +Autogenerated input type of UpdateTeamsRepository +""" +input UpdateTeamsRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Permission that should be granted to the teams. + """ + permission: RepositoryPermission! + + """ + Repository ID being granted access to. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + A list of teams being granted access. Limit: 10 + """ + teamIds: [ID!]! @possibleTypes(concreteTypes: ["Team"]) +} + +""" +Autogenerated return type of UpdateTeamsRepository +""" +type UpdateTeamsRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was updated. + """ + repository: Repository + + """ + The teams granted permission on the repository. + """ + teams: [Team!] +} + +""" +Autogenerated input type of UpdateTopics +""" +input UpdateTopicsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + An array of topic names. + """ + topicNames: [String!]! +} + +""" +Autogenerated return type of UpdateTopics +""" +type UpdateTopicsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Names of the provided topics that are not valid. + """ + invalidTopicNames: [String!] + + """ + The updated repository. + """ + repository: Repository +} + +""" +A user is an individual's account on GitHub that owns repositories and can make new content. +""" +type User implements Actor & Node & PackageOwner & ProfileOwner & ProjectNextOwner & ProjectOwner & ProjectV2Owner & ProjectV2Recent & RepositoryDiscussionAuthor & RepositoryDiscussionCommentAuthor & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + A URL pointing to the user's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The user's public profile bio. + """ + bio: String + + """ + The user's public profile bio as HTML. + """ + bioHTML: HTML! + + """ + Could this user receive email notifications, if the organization had notification restrictions enabled? + """ + canReceiveOrganizationEmailsWhenNotificationsRestricted( + """ + The login of the organization to check. + """ + login: String! + ): Boolean! + + """ + A list of commit comments made by this user. + """ + commitComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The user's public profile company. + """ + company: String + + """ + The user's public profile company as HTML. + """ + companyHTML: HTML! + + """ + The collection of contributions this user has made to different repositories. + """ + contributionsCollection( + """ + Only contributions made at this time or later will be counted. If omitted, defaults to a year ago. + """ + from: DateTime + + """ + The ID of the organization used to filter contributions. + """ + organizationID: ID + + """ + Only contributions made before and up to (including) this time will be + counted. If omitted, defaults to the current time or one year from the + provided from argument. + """ + to: DateTime + ): ContributionsCollection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user's publicly visible profile email. + """ + email: String! + + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + A list of users the given user is followed by. + """ + followers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): FollowerConnection! + + """ + A list of users the given user is following. + """ + following( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): FollowingConnection! + + """ + Find gist by repo name. + """ + gist( + """ + The gist name to find. + """ + name: String! + ): Gist + + """ + A list of gist comments made by this user. + """ + gistComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GistCommentConnection! + + """ + A list of the Gists the user has created. + """ + gists( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for gists returned from the connection + """ + orderBy: GistOrder + + """ + Filters Gists according to privacy. + """ + privacy: GistPrivacy + ): GistConnection! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + The hovercard information for this user in a given context + """ + hovercard( + """ + The ID of the subject to get the hovercard in the context of + """ + primarySubjectId: ID + ): Hovercard! + id: ID! + + """ + The interaction ability settings for this user. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Whether or not this user is a participant in the GitHub Security Bug Bounty. + """ + isBountyHunter: Boolean! + + """ + Whether or not this user is a participant in the GitHub Campus Experts Program. + """ + isCampusExpert: Boolean! + + """ + Whether or not this user is a GitHub Developer Program member. + """ + isDeveloperProgramMember: Boolean! + + """ + Whether or not this user is a GitHub employee. + """ + isEmployee: Boolean! + + """ + Whether or not this user is following the viewer. Inverse of viewer_is_following + """ + isFollowingViewer: Boolean! + + """ + Whether or not this user is a member of the GitHub Stars Program. + """ + isGitHubStar: Boolean! + + """ + Whether or not the user has marked themselves as for hire. + """ + isHireable: Boolean! + + """ + Whether or not this user is a site administrator. + """ + isSiteAdmin: Boolean! + + """ + Check if the given account is sponsoring this user/organization. + """ + isSponsoredBy( + """ + The target account's login. + """ + accountLogin: String! + ): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether or not this user is the viewing user. + """ + isViewer: Boolean! + + """ + A list of issue comments made by this user. + """ + issueComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + A list of issues associated with this user. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The user's public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + The user's public profile name. + """ + name: String + + """ + Find an organization by its login that the user belongs to. + """ + organization( + """ + The login of the organization to find. + """ + login: String! + ): Organization + + """ + Verified email addresses that match verified domains for a specified organization the user is a member of. + """ + organizationVerifiedDomainEmails( + """ + The login of the organization to match verified domains from. + """ + login: String! + ): [String!]! + + """ + A list of organizations the user belongs to. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + Find a project by project (beta) number. + """ + projectNext( + """ + The project (beta) number. + """ + number: Int! + ): ProjectNext + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + Find a project by number. + """ + projectV2( + """ + The project number. + """ + number: Int! + ): ProjectV2 + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + A list of projects (beta) under the owner. + """ + projectsNext( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A project (beta) to search for under the the owner. + """ + query: String + + """ + How to order the returned projects (beta). + """ + sortBy: ProjectNextOrderField = TITLE + ): ProjectNextConnection! + @deprecated( + reason: "The `ProjectNext` API is deprecated in favour of the more capable `ProjectV2` API. Follow the ProjectV2 guide at https://github.blog/changelog/2022-06-23-the-new-github-issues-june-23rd-update/, to find a suitable replacement. Removal on 2023-01-01 UTC." + ) + + """ + The HTTP path listing user's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing user's projects + """ + projectsUrl: URI! + + """ + A list of projects under the owner. + """ + projectsV2( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + How to order the returned projects. + """ + orderBy: ProjectV2Order = {field: NUMBER, direction: DESC} + + """ + A project to search for under the the owner. + """ + query: String + ): ProjectV2Connection! + + """ + A list of public keys associated with this user. + """ + publicKeys( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PublicKeyConnection! + + """ + A list of pull requests associated with this user. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectV2Connection! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + A list of repositories that the user recently contributed to. + """ + repositoriesContributedTo( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + If non-null, include only the specified types of contributions. The + GitHub.com UI uses [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY] + """ + contributionTypes: [RepositoryContributionType] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If true, include user repositories + """ + includeUserRepositories: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Follow repository renames. If disabled, a repository referenced by its old name will return an error. + """ + followRenames: Boolean = true + + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter discussion comments to only those that were marked as the answer + """ + onlyAnswers: Boolean = false + + """ + Filter discussion comments to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionCommentConnection! + + """ + Discussions this user has started. + """ + repositoryDiscussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Filter discussions to only those that have been answered or not. Defaults to + including both answered and unanswered discussions. + """ + answered: Boolean = null + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for discussions returned from the connection. + """ + orderBy: DiscussionOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter discussions to only those in a specific repository. + """ + repositoryId: ID + ): DiscussionConnection! + + """ + The HTTP path for this user + """ + resourcePath: URI! + + """ + Replies this user has saved + """ + savedReplies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The field to order saved replies by. + """ + orderBy: SavedReplyOrder = {field: UPDATED_AT, direction: DESC} + ): SavedReplyConnection + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the users and organizations returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsors returned from the connection. + """ + orderBy: SponsorOrder = {field: RELEVANCE, direction: DESC} + + """ + If given, will filter for sponsors at the given tier. Will only return + sponsors whose tier the viewer is permitted to see. + """ + tierId: ID + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities( + """ + Filter activities to only the specified actions. + """ + actions: [SponsorsActivityAction!] = [] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether to include those events where this sponsorable acted as the sponsor. + Defaults to only including events where this sponsorable was the recipient + of a sponsorship. + """ + includeAsSponsor: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for activity returned from the connection. + """ + orderBy: SponsorsActivityOrder = {field: TIMESTAMP, direction: DESC} + + """ + Filter activities returned to only those that occurred in the most recent + specified time period. Set to ALL to avoid filtering by when the activity + occurred. Will be ignored if `since` or `until` is given. + """ + period: SponsorsActivityPeriod = MONTH + + """ + Filter activities to those that occurred on or after this time. + """ + since: DateTime + + """ + Filter activities to those that occurred before this time. + """ + until: DateTime + ): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the + sponsorship where you're the sponsor. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsor: Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the + sponsorship you're receiving. Only returns a sponsorship if it is active. + """ + sponsorshipForViewerAsSponsorable: Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorship updates returned from the connection. + """ + orderBy: SponsorshipNewsletterOrder = {field: CREATED_AT, direction: DESC} + ): SponsorshipNewsletterConnection! + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter sponsorships returned to those for the specified maintainers. That + is, the recipient of the sponsorship is a user or organization with one of + the given logins. + """ + maintainerLogins: [String!] + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Repositories the user has starred. + """ + starredRepositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + + """ + Filters starred repositories to only return repositories owned by the viewer. + """ + ownedByViewer: Boolean + ): StarredRepositoryConnection! + + """ + The user's description of what they're currently doing. + """ + status: UserStatus + + """ + Repositories the user has contributed to, ordered by contribution rank, plus repositories the user has created + """ + topRepositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder! + + """ + How far back in time to fetch contributed repositories + """ + since: DateTime + ): RepositoryConnection! + + """ + The user's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user + """ + url: URI! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Whether or not the viewer is able to follow the user. + """ + viewerCanFollow: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Whether or not this user is followed by the viewer. Inverse of is_following_viewer. + """ + viewerIsFollowing: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + A list of repositories the given user is watching. + """ + watching( + """ + Affiliation options for repositories returned from the connection. If none + specified, the results will include repositories for which the current + viewer is an owner or collaborator, or member. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + A URL pointing to the user's public website/blog. + """ + websiteUrl: URI +} + +""" +The possible durations that a user can be blocked for. +""" +enum UserBlockDuration { + """ + The user was blocked for 1 day + """ + ONE_DAY + + """ + The user was blocked for 30 days + """ + ONE_MONTH + + """ + The user was blocked for 7 days + """ + ONE_WEEK + + """ + The user was blocked permanently + """ + PERMANENT + + """ + The user was blocked for 3 days + """ + THREE_DAYS +} + +""" +Represents a 'user_blocked' event on a given user. +""" +type UserBlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Number of days that the user was blocked for. + """ + blockDuration: UserBlockDuration! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The user who was blocked. + """ + subject: User +} + +""" +The connection type for User. +""" +type UserConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edit on user content +""" +type UserContentEdit implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the date and time when the object was deleted. + """ + deletedAt: DateTime + + """ + The actor who deleted this content + """ + deletedBy: Actor + + """ + A summary of the changes for this edit + """ + diff: String + + """ + When this content was edited + """ + editedAt: DateTime! + + """ + The actor who edited this content + """ + editor: Actor + id: ID! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +A list of edits to content. +""" +type UserContentEditConnection { + """ + A list of edges. + """ + edges: [UserContentEditEdge] + + """ + A list of nodes. + """ + nodes: [UserContentEdit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserContentEditEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserContentEdit +} + +""" +Represents a user. +""" +type UserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User +} + +""" +Email attributes from External Identity +""" +type UserEmailMetadata { + """ + Boolean to identify primary emails + """ + primary: Boolean + + """ + Type of email + """ + type: String + + """ + Email id + """ + value: String! +} + +""" +The user's description of what they're currently doing. +""" +type UserStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + An emoji summarizing the user's status. + """ + emoji: String + + """ + The status emoji as HTML. + """ + emojiHTML: HTML + + """ + If set, the status will not be shown after this date. + """ + expiresAt: DateTime + id: ID! + + """ + Whether this status indicates the user is not fully available on GitHub. + """ + indicatesLimitedAvailability: Boolean! + + """ + A brief message describing what the user is doing. + """ + message: String + + """ + The organization whose members can see this status. If null, this status is publicly visible. + """ + organization: Organization + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user who has this status. + """ + user: User! +} + +""" +The connection type for UserStatus. +""" +type UserStatusConnection { + """ + A list of edges. + """ + edges: [UserStatusEdge] + + """ + A list of nodes. + """ + nodes: [UserStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserStatus +} + +""" +Ordering options for user status connections. +""" +input UserStatusOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user statuses by. + """ + field: UserStatusOrderField! +} + +""" +Properties by which user status connections can be ordered. +""" +enum UserStatusOrderField { + """ + Order user statuses by when they were updated. + """ + UPDATED_AT +} + +""" +A domain that can be verified or approved for an organization or an enterprise. +""" +type VerifiableDomain implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The DNS host name that should be used for verification. + """ + dnsHostName: URI + + """ + The unicode encoded domain. + """ + domain: URI! + + """ + Whether a TXT record for verification with the expected host name was found. + """ + hasFoundHostName: Boolean! + + """ + Whether a TXT record for verification with the expected verification token was found. + """ + hasFoundVerificationToken: Boolean! + id: ID! + + """ + Whether or not the domain is approved. + """ + isApproved: Boolean! + + """ + Whether this domain is required to exist for an organization or enterprise policy to be enforced. + """ + isRequiredForPolicyEnforcement: Boolean! + + """ + Whether or not the domain is verified. + """ + isVerified: Boolean! + + """ + The owner of the domain. + """ + owner: VerifiableDomainOwner! + + """ + The punycode encoded domain. + """ + punycodeEncodedDomain: URI! + + """ + The time that the current verification token will expire. + """ + tokenExpirationTime: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The current verification token for the domain. + """ + verificationToken: String +} + +""" +The connection type for VerifiableDomain. +""" +type VerifiableDomainConnection { + """ + A list of edges. + """ + edges: [VerifiableDomainEdge] + + """ + A list of nodes. + """ + nodes: [VerifiableDomain] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type VerifiableDomainEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: VerifiableDomain +} + +""" +Ordering options for verifiable domain connections. +""" +input VerifiableDomainOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order verifiable domains by. + """ + field: VerifiableDomainOrderField! +} + +""" +Properties by which verifiable domain connections can be ordered. +""" +enum VerifiableDomainOrderField { + """ + Order verifiable domains by their creation date. + """ + CREATED_AT + + """ + Order verifiable domains by the domain name. + """ + DOMAIN +} + +""" +Types that can own a verifiable domain. +""" +union VerifiableDomainOwner = Enterprise | Organization + +""" +Autogenerated input type of VerifyVerifiableDomain +""" +input VerifyVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to verify. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of VerifyVerifiableDomain +""" +type VerifyVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was verified. + """ + domain: VerifiableDomain +} + +""" +A hovercard context with a message describing how the viewer is related. +""" +type ViewerHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Identifies the user who is related to this context. + """ + viewer: User! +} + +""" +A subject that may be upvoted. +""" +interface Votable { + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! +} + +""" +A workflow contains meta information about an Actions workflow file. +""" +type Workflow implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The name of the workflow. + """ + name: String! + + """ + The runs of the workflow. + """ + runs( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the connection + """ + orderBy: WorkflowRunOrder = {field: CREATED_AT, direction: DESC} + ): WorkflowRunConnection! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +A workflow run. +""" +type WorkflowRun implements Node { + """ + The check suite this workflow run belongs to. + """ + checkSuite: CheckSuite! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The log of deployment reviews + """ + deploymentReviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentReviewConnection! + id: ID! + + """ + The pending deployment requests of all check runs in this workflow run + """ + pendingDeploymentRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentRequestConnection! + + """ + The HTTP path for this workflow run + """ + resourcePath: URI! + + """ + A number that uniquely identifies this workflow run in its parent workflow. + """ + runNumber: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this workflow run + """ + url: URI! + + """ + The workflow executed in this workflow run. + """ + workflow: Workflow! +} + +""" +The connection type for WorkflowRun. +""" +type WorkflowRunConnection { + """ + A list of edges. + """ + edges: [WorkflowRunEdge] + + """ + A list of nodes. + """ + nodes: [WorkflowRun] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type WorkflowRunEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: WorkflowRun +} + +""" +Ways in which lists of workflow runs can be ordered upon return. +""" +input WorkflowRunOrder { + """ + The direction in which to order workflow runs by the specified field. + """ + direction: OrderDirection! + + """ + The field by which to order workflows. + """ + field: WorkflowRunOrderField! +} + +""" +Properties by which workflow run connections can be ordered. +""" +enum WorkflowRunOrderField { + """ + Order workflow runs by most recently created + """ + CREATED_AT +} + +""" +A valid x509 certificate string +""" +scalar X509Certificate diff --git a/src/utils/types.ts b/src/utils/types.ts index 2219f33..ebe3701 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,6 +1,6 @@ import { Logger } from "@utils/logger"; -export type CombineFn any> = (...args: Parameters) => ReturnType; +export type Shift = ((...t: T) => any) extends (first: any, ...rest: infer Rest) => any ? Rest : never; export type TypeMap = { [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; }; diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts new file mode 100644 index 0000000..de14e11 --- /dev/null +++ b/src/watchers/github/index.ts @@ -0,0 +1,117 @@ +import { Client, createClient } from "@urql/core"; + +import { UserData } from "@repositories/models/user"; + +import { GitHubWatcherOptions } from "@watchers/github/types"; +import { BaseWatcher } from "@watchers/base"; +import { FollowersQuery, FollowersQueryVariables, MeQuery } from "@root/queries.data"; +import { FollowersDocument, MeDocument } from "@watchers/github/queries"; +import { Nullable } from "@utils/types"; + +export class GitHubWatcher extends BaseWatcher<"GitHub"> { + private client: Client; + + public constructor(private readonly options: GitHubWatcherOptions) { + super("GitHub"); + + this.client = createClient({ + url: "https://api.github.com/graphql", + fetchOptions: () => { + return { + method: "POST", + headers: { + authorization: `token ${this.options.authToken}`, + }, + }; + }, + }); + } + + public async initialize() { + return; + } + public async doWatch(): Promise { + const result: UserData[] = []; + const currentUserId = await this.getCurrentUserId(); + + let cursor: string | undefined = undefined; + while (true) { + const [followers, nextCursor] = await this.getFollowers(currentUserId, cursor); + result.push(...followers); + + if (!nextCursor) { + break; + } + + cursor = nextCursor; + } + + return result; + } + + private async getCurrentUserId() { + const { data, error } = await this.client.query(MeDocument, {}).toPromise(); + if (error) { + throw error; + } + + if (!data) { + throw new Error("No data returned from Me query"); + } + + return data.viewer.login; + } + private async getFollowers(targetUserId: string, cursor?: string): Promise<[UserData[], Nullable]> { + const { data, error } = await this.client + .query(FollowersDocument, { + username: targetUserId, + cursor, + }) + .toPromise(); + + if (error) { + throw error; + } + + if (!data) { + throw new Error("No data returned from Followers query"); + } + + if (!data.user) { + throw new Error("No user returned from Followers query"); + } + + if (!data.user.followers.edges) { + throw new Error("No followers returned from Followers query"); + } + + return [ + data.user.followers.edges + .map(edge => { + if (!edge?.node) { + return null; + } + + return { + from: this.getName(), + uniqueId: edge.node.id, + userId: edge.node.login, + displayName: edge.node.name || edge.node.login, + }; + }) + .filter((item: UserData | null): item is UserData => Boolean(item)), + data.user.followers.pageInfo.endCursor, + ]; + } + + protected getHashData(): any { + return this.options.authToken; + } + + protected hydrate(data: GitHubWatcherOptions): void { + this.options.authToken = data.authToken; + } + protected serialize(): GitHubWatcherOptions { + return this.options; + } +} diff --git a/src/watchers/github/queries.ts b/src/watchers/github/queries.ts new file mode 100644 index 0000000..2423bab --- /dev/null +++ b/src/watchers/github/queries.ts @@ -0,0 +1,37 @@ +import gql from "graphql-tag"; + +export const FollowersDocument = gql` + query followers($username: String!, $cursor: String) { + user(login: $username) { + followers(first: 100, after: $cursor) { + totalCount + edges { + node { + id + login + name + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + rateLimit { + limit + cost + remaining + resetAt + } + } +`; + +export const MeDocument = gql` + query me { + viewer { + id + login + } + } +`; diff --git a/src/watchers/github/types.ts b/src/watchers/github/types.ts new file mode 100644 index 0000000..06b76bd --- /dev/null +++ b/src/watchers/github/types.ts @@ -0,0 +1,5 @@ +import { BaseWatcherOptions } from "@watchers/base"; + +export interface GitHubWatcherOptions extends BaseWatcherOptions<"github"> { + authToken: string; +} diff --git a/src/watchers/index.ts b/src/watchers/index.ts index 72256d1..6a3745a 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,13 +1,16 @@ import { TwitterWatcher } from "@watchers/twitter"; import { TwitterWatcherOptions } from "@watchers/twitter/types"; +import { GitHubWatcher } from "@watchers/github"; +import { GitHubWatcherOptions } from "@watchers/github/types"; + import { TypeMap } from "@utils/types"; import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; -export type WatcherClasses = TwitterWatcher; +export type WatcherClasses = TwitterWatcher | GitHubWatcher; export type WatcherTypes = Lowercase; -export type WatcherOptions = TwitterWatcherOptions; +export type WatcherOptions = TwitterWatcherOptions | GitHubWatcherOptions; export type WatcherOptionMap = TypeMap; export type WatcherMap = { @@ -24,6 +27,7 @@ export type WatcherPair = [WatcherTypes, BaseWatcher]; const AVAILABLE_WATCHERS: Readonly = { twitter: options => new TwitterWatcher(options), + github: options => new GitHubWatcher(options), }; export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { diff --git a/yarn.lock b/yarn.lock index 7f2c850..97b7f1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,36 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ardatan/relay-compiler@12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz#2e4cca43088e807adc63450e8cab037020e91106" + integrity sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q== + dependencies: + "@babel/core" "^7.14.0" + "@babel/generator" "^7.14.0" + "@babel/parser" "^7.14.0" + "@babel/runtime" "^7.0.0" + "@babel/traverse" "^7.14.0" + "@babel/types" "^7.0.0" + babel-preset-fbjs "^3.4.0" + chalk "^4.0.0" + fb-watchman "^2.0.0" + fbjs "^3.0.0" + glob "^7.1.1" + immutable "~3.7.6" + invariant "^2.2.4" + nullthrows "^1.1.1" + relay-runtime "12.0.0" + signedsource "^1.0.0" + yargs "^15.3.1" + +"@ardatan/sync-fetch@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@ardatan/sync-fetch/-/sync-fetch-0.0.1.tgz#3385d3feedceb60a896518a1db857ec1e945348f" + integrity sha512-xhlTqH0m31mnsG0tIP4ETgfSB6gXDaYYsUWTrlUV93fFQPI9dd8hE0Ot6MHLCtqgB32hwJAC3YZMWlXZw7AleA== + dependencies: + node-fetch "^2.6.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -22,6 +52,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== +"@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733" + integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g== + "@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c" @@ -43,6 +78,36 @@ json5 "^2.2.1" semver "^6.3.0" +"@babel/core@^7.14.0": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113" + integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-module-transforms" "^7.20.2" + "@babel/helpers" "^7.20.5" + "@babel/parser" "^7.20.5" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.14.0", "@babel/generator@^7.18.13", "@babel/generator@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" + integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== + dependencies: + "@babel/types" "^7.20.5" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + "@babel/generator@^7.19.3", "@babel/generator@^7.7.2": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59" @@ -52,6 +117,23 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== + dependencies: + "@babel/compat-data" "^7.20.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + "@babel/helper-compilation-targets@^7.19.3": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" @@ -62,12 +144,25 @@ browserslist "^4.21.3" semver "^6.3.0" +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06" + integrity sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-function-name@^7.19.0": +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== @@ -82,6 +177,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" @@ -103,11 +205,48 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" +"@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" + integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== +"@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + "@babel/helper-simple-access@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" @@ -115,6 +254,20 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== + dependencies: + "@babel/types" "^7.20.0" + "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" @@ -127,6 +280,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" @@ -146,6 +304,15 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" +"@babel/helpers@^7.20.5": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763" + integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -160,6 +327,30 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== +"@babel/parser@^7.14.0", "@babel/parser@^7.16.8", "@babel/parser@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== + +"@babel/plugin-proposal-class-properties@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" + integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -174,13 +365,27 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.8.3": +"@babel/plugin-syntax-class-properties@^7.0.0", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" + integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-assertions@7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -195,7 +400,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.7.2": +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.7.2": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== @@ -223,7 +428,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== @@ -258,6 +463,172 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-arrow-functions@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoped-functions@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoping@^7.0.0": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz#401215f9dc13dc5262940e2e527c9536b3d7f237" + integrity sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-classes@^7.0.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" + integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-destructuring@^7.0.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" + integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-flow-strip-types@^7.0.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz#e9e8606633287488216028719638cbbb2f2dde8f" + integrity sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-flow" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.0.0": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-function-name@^7.0.0": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-literals@^7.0.0": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-member-expression-literals@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-commonjs@^7.0.0": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" + integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== + dependencies: + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-simple-access" "^7.19.4" + +"@babel/plugin-transform-object-super@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.1": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz#f8f9186c681d10c3de7620c916156d893c8a019e" + integrity sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-property-literals@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" + +"@babel/plugin-transform-shorthand-properties@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.0.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + +"@babel/plugin-transform-template-literals@^7.0.0": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/runtime@^7.0.0": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" @@ -267,6 +638,22 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" +"@babel/traverse@^7.14.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" + integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.5" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.5" + "@babel/types" "^7.20.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3", "@babel/traverse@^7.7.2": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4" @@ -292,6 +679,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.16.8", "@babel/types@^7.18.13", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" + integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -329,6 +725,427 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@graphql-codegen/cli@2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-2.15.0.tgz#e36dd8cdb32ce58aeeebd546266cd435732e796f" + integrity sha512-o4Wh99VJDX/z0fG3pkdOf0t0fu7SlYn6qlLnqzNhVpZByGPe548gu11GKiOPKVaQ76kkB3dzqzfINRl+v7EA4A== + dependencies: + "@babel/generator" "^7.18.13" + "@babel/template" "^7.18.10" + "@babel/types" "^7.18.13" + "@graphql-codegen/core" "2.6.6" + "@graphql-codegen/plugin-helpers" "^2.7.2" + "@graphql-tools/apollo-engine-loader" "^7.3.6" + "@graphql-tools/code-file-loader" "^7.3.13" + "@graphql-tools/git-loader" "^7.2.13" + "@graphql-tools/github-loader" "^7.3.20" + "@graphql-tools/graphql-file-loader" "^7.5.0" + "@graphql-tools/json-file-loader" "^7.4.1" + "@graphql-tools/load" "7.8.0" + "@graphql-tools/prisma-loader" "^7.2.7" + "@graphql-tools/url-loader" "^7.13.2" + "@graphql-tools/utils" "^8.9.0" + "@whatwg-node/fetch" "^0.3.0" + ansi-escapes "^4.3.1" + chalk "^4.1.0" + chokidar "^3.5.2" + cosmiconfig "^7.0.0" + cosmiconfig-typescript-loader "4.1.1" + debounce "^1.2.0" + detect-indent "^6.0.0" + graphql-config "4.3.6" + inquirer "^8.0.0" + is-glob "^4.0.1" + json-to-pretty-yaml "^1.2.2" + listr2 "^4.0.5" + log-symbols "^4.0.0" + shell-quote "^1.7.3" + string-env-interpolation "^1.0.1" + ts-log "^2.2.3" + tslib "^2.4.0" + yaml "^1.10.0" + yargs "^17.0.0" + +"@graphql-codegen/core@2.6.6": + version "2.6.6" + resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-2.6.6.tgz#e6ea99682230c5bbcf28cb247672da7f17e78578" + integrity sha512-gU2FUxoLGw2GfcPWfBVXuiN3aDODbZ6Z9I+IGxa2u1Rzxlacw4TMmcwr4/IjC6mkiYJEKTvdVspHaby+brhuAg== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.7.2" + "@graphql-tools/schema" "^9.0.0" + "@graphql-tools/utils" "^9.1.1" + tslib "~2.4.0" + +"@graphql-codegen/plugin-helpers@^2.6.2", "@graphql-codegen/plugin-helpers@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz#6544f739d725441c826a8af6a49519f588ff9bed" + integrity sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg== + dependencies: + "@graphql-tools/utils" "^8.8.0" + change-case-all "1.0.14" + common-tags "1.8.2" + import-from "4.0.0" + lodash "~4.17.0" + tslib "~2.4.0" + +"@graphql-codegen/schema-ast@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@graphql-codegen/schema-ast/-/schema-ast-2.5.1.tgz#ce030ae6de5dacd745848009ba0ca18c9c30910c" + integrity sha512-tewa5DEKbglWn7kYyVBkh3J8YQ5ALqAMVmZwiVFIGOao5u66nd+e4HuFqp0u+Jpz4SJGGi0ap/oFrEvlqLjd2A== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.6.2" + "@graphql-tools/utils" "^8.8.0" + tslib "~2.4.0" + +"@graphql-codegen/typescript-operations@^2.5.8": + version "2.5.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-2.5.8.tgz#e9e2022de9f67b0d855d370f20c48c7b2ed24f21" + integrity sha512-Zp27jZjOLkoH0qy5INqrTsut5PI40OEVcKmcQ+TDHr9wDYa3M06/k907z6CuW3PjOgJBtrSTcgAEnrye8jhkJw== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.7.2" + "@graphql-codegen/typescript" "^2.8.3" + "@graphql-codegen/visitor-plugin-common" "2.13.3" + auto-bind "~4.0.0" + tslib "~2.4.0" + +"@graphql-codegen/typescript@2.8.3", "@graphql-codegen/typescript@^2.8.3": + version "2.8.3" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-2.8.3.tgz#83a87586b550b7c5b543d11b8a2da706cca67bda" + integrity sha512-ch8Lzjp8XnN8P70uYBmsjv7FWJQ47qletlShPHk7n4RRsnLkah3J9iSEUIALqM25Wl6EjEmHlxoAgSBILz+sjg== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.7.2" + "@graphql-codegen/schema-ast" "^2.5.1" + "@graphql-codegen/visitor-plugin-common" "2.13.3" + auto-bind "~4.0.0" + tslib "~2.4.0" + +"@graphql-codegen/visitor-plugin-common@2.13.3": + version "2.13.3" + resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.3.tgz#4a2eceee00b4cfdf1eff31a006710a30417ec497" + integrity sha512-5gFDQGuCE5tIBo9KtDPZ8kL6cf1VJwDGj6nO9ERa0HJNk5osT50NhSf6H61LEnM3Gclbo96Ib1GCp3KdLwHoGg== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.7.2" + "@graphql-tools/optimize" "^1.3.0" + "@graphql-tools/relay-operation-optimizer" "^6.5.0" + "@graphql-tools/utils" "^8.8.0" + auto-bind "~4.0.0" + change-case-all "1.0.14" + dependency-graph "^0.11.0" + graphql-tag "^2.11.0" + parse-filepath "^1.0.2" + tslib "~2.4.0" + +"@graphql-tools/apollo-engine-loader@^7.3.6": + version "7.3.19" + resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-7.3.19.tgz#f18e73ba0e2efc9d9b9f30b96f6ce3a842837695" + integrity sha512-at5VaqSVGZDc3Fjr63vWhrKXTb5YdopCuvpRGeC9PALIWAMOLXNdkdPYiFe8crLAz60qhcpADqFoNFR+G2+NIg== + dependencies: + "@ardatan/sync-fetch" "0.0.1" + "@graphql-tools/utils" "9.1.1" + "@whatwg-node/fetch" "^0.5.0" + tslib "^2.4.0" + +"@graphql-tools/batch-execute@8.5.12": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.5.12.tgz#3059646b87e4ea3a196a6c8cbc3faf9b7c410d52" + integrity sha512-eNdN5CirW3ILoBaVyy4GI6JpLoJELeH0A7+uLRjwZuMFxpe4cljSrY8P+id28m43+uvBzB3rvNTv0+mnRjrMRw== + dependencies: + "@graphql-tools/utils" "9.1.1" + dataloader "2.1.0" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/code-file-loader@^7.3.13": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-7.3.13.tgz#439c21c80aed1018f9457d3742b1d51ce60cd3f0" + integrity sha512-6anNQJ/VqseqBGcrZexGsiW40cBWF8Uko9AgvGSuZx2uJl1O8H9a3XMZnkmuI17yoGRCzXkwf52AS0+O5UYFUA== + dependencies: + "@graphql-tools/graphql-tag-pluck" "7.4.0" + "@graphql-tools/utils" "9.1.1" + globby "^11.0.3" + tslib "^2.4.0" + unixify "^1.0.0" + +"@graphql-tools/delegate@9.0.17": + version "9.0.17" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-9.0.17.tgz#8f509a046cee7b37c0cae33a526e4aea903539ab" + integrity sha512-y7h5H+hOhQWEkG67A4wurlphHMYJuMlQIEY7wZPVpmViuV6TuSPB7qkLITsM99XiNQhX+v1VayN2cuaP/8nIhw== + dependencies: + "@graphql-tools/batch-execute" "8.5.12" + "@graphql-tools/executor" "0.0.9" + "@graphql-tools/schema" "9.0.10" + "@graphql-tools/utils" "9.1.1" + dataloader "2.1.0" + tslib "~2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/executor-graphql-ws@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-0.0.3.tgz#5cbc6adca46cf46a3b5e208976a978b26221678b" + integrity sha512-8VATDf82lTaYRE4/BrFm8v6Cz6UHoNTlSkQjPcGtDX4nxbBUYLDfN+Z8ZXl0eZc3tCwsIHkYQunJO0OjmcrP5Q== + dependencies: + "@graphql-tools/utils" "9.1.1" + "@repeaterjs/repeater" "3.0.4" + "@types/ws" "^8.0.0" + graphql-ws "5.11.2" + isomorphic-ws "5.0.0" + tslib "^2.4.0" + ws "8.11.0" + +"@graphql-tools/executor-http@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@graphql-tools/executor-http/-/executor-http-0.0.4.tgz#b0ba436bd37a538ee31f30c9ee6266143a956062" + integrity sha512-m7UwOhzIXLXXisxOD8x+niN3ae38A8bte47eBBfzr8eTrjsSEEQRbwsc6ciUmoys/5iQabnbtJN10rNIaZaU8w== + dependencies: + "@graphql-tools/utils" "9.1.1" + "@repeaterjs/repeater" "3.0.4" + "@whatwg-node/fetch" "0.5.3" + dset "3.1.2" + extract-files "^11.0.0" + meros "1.2.1" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/executor-legacy-ws@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-0.0.3.tgz#6d01d319989b2013c90cb548677272b985fd37c9" + integrity sha512-ulQ3IsxQ9VRA2S+afJefFpMZHedoUDRd8ylz+9DjqAoykYz6CDD2s3pi6Fud52VCq3DP79dRM7a6hjWgt+YPWw== + dependencies: + "@graphql-tools/utils" "9.1.1" + "@types/ws" "^8.0.0" + isomorphic-ws "5.0.0" + tslib "^2.4.0" + ws "8.11.0" + +"@graphql-tools/executor@0.0.9": + version "0.0.9" + resolved "https://registry.yarnpkg.com/@graphql-tools/executor/-/executor-0.0.9.tgz#f55f8cbe12d7989b0ff58cca9a041fbdde3dbd40" + integrity sha512-qLhQWXTxTS6gbL9INAQa4FJIqTd2tccnbs4HswOx35KnyLaLtREuQ8uTfU+5qMrRIBhuzpGdkP2ssqxLyOJ5rA== + dependencies: + "@graphql-tools/utils" "9.1.1" + "@graphql-typed-document-node/core" "3.1.1" + "@repeaterjs/repeater" "3.0.4" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/git-loader@^7.2.13": + version "7.2.13" + resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-7.2.13.tgz#76af2a8e56f66c28562496c48baaafe7d812db43" + integrity sha512-PBAzZWXzKUL+VvlUQOjF++246G1O6TTMzvIlxaecgxvTSlnljEXJcDQlxqXhfFPITc5MP7He0N1UcZPBU/DE7Q== + dependencies: + "@graphql-tools/graphql-tag-pluck" "7.4.0" + "@graphql-tools/utils" "9.1.1" + is-glob "4.0.3" + micromatch "^4.0.4" + tslib "^2.4.0" + unixify "^1.0.0" + +"@graphql-tools/github-loader@^7.3.20": + version "7.3.20" + resolved "https://registry.yarnpkg.com/@graphql-tools/github-loader/-/github-loader-7.3.20.tgz#3b06aedc21607240530084b8f8364c1968d0c132" + integrity sha512-kIgloHb+yJJYR6K47HNBv7vI7IF73eoGsQy77H+2WDA+zwE5PuRXGUTAlJXRQdwiY71/Nvbw44P3l4WWbMRv0Q== + dependencies: + "@ardatan/sync-fetch" "0.0.1" + "@graphql-tools/graphql-tag-pluck" "7.4.0" + "@graphql-tools/utils" "9.1.1" + "@whatwg-node/fetch" "^0.5.0" + tslib "^2.4.0" + +"@graphql-tools/graphql-file-loader@^7.3.7", "@graphql-tools/graphql-file-loader@^7.5.0": + version "7.5.11" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.5.11.tgz#19c44ea9021d3220c7ed1cef6cfa0afbb40fd9a4" + integrity sha512-E4/YYLlM/T/VDYJ3MfQzJSkCpnHck+xMv2R6QTjO3khUeTCWJY4qsLDPFjAWE0+Mbe9NanXi/yL8Bz0yS/usDw== + dependencies: + "@graphql-tools/import" "6.7.12" + "@graphql-tools/utils" "9.1.1" + globby "^11.0.3" + tslib "^2.4.0" + unixify "^1.0.0" + +"@graphql-tools/graphql-tag-pluck@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.4.0.tgz#b8082801164aad0b9b03bc61fb92388d076cc66a" + integrity sha512-f966Z8cMDiPxWuN3ksuHpNgGE8euZtrL/Gcwz9rRarAb13al4CGHKmw2Cb/ZNdt7GbyhdiLT4wbaddrF0xCpdw== + dependencies: + "@babel/parser" "^7.16.8" + "@babel/plugin-syntax-import-assertions" "7.20.0" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" + "@graphql-tools/utils" "9.1.1" + tslib "^2.4.0" + +"@graphql-tools/import@6.7.12": + version "6.7.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/import/-/import-6.7.12.tgz#173d2ce94304d1930e8674857d4ffe3cd52cfdd5" + integrity sha512-3+IV3RHqnpQz0o+0Liw3jkr0HL8LppvsFROKdfXihbnCGO7cIq4S9QYdczZ2DAJ7AosyzSu8m36X5dEmOYY6WA== + dependencies: + "@graphql-tools/utils" "9.1.1" + resolve-from "5.0.0" + tslib "^2.4.0" + +"@graphql-tools/json-file-loader@^7.3.7", "@graphql-tools/json-file-loader@^7.4.1": + version "7.4.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-7.4.12.tgz#ac1d98e90926e0ac0a8530ff464f5a1c9041c16c" + integrity sha512-KuOBJg9ZVrgDsYUaolSXJI90HpwkNiPJviWSc5aqNYSkE+C9DwelBOaKBVQNk1ecEnktqx6Nd+KVsF3m+dupRQ== + dependencies: + "@graphql-tools/utils" "9.1.1" + globby "^11.0.3" + tslib "^2.4.0" + unixify "^1.0.0" + +"@graphql-tools/load@7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.8.0.tgz#bd4d2e2a5117de9a60f9691a218217e96afc2ea7" + integrity sha512-l4FGgqMW0VOqo+NMYizwV8Zh+KtvVqOf93uaLo9wJ3sS3y/egPCgxPMDJJ/ufQZG3oZ/0oWeKt68qop3jY0yZg== + dependencies: + "@graphql-tools/schema" "9.0.4" + "@graphql-tools/utils" "8.12.0" + p-limit "3.1.0" + tslib "^2.4.0" + +"@graphql-tools/load@^7.5.5": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-7.8.6.tgz#f0c5d7852337f8c7e4b88a9a7d63255d29d98395" + integrity sha512-yFDM5hVhV0eOom3SGyc+mjL8FvEb+0PZTZ/OSc4zrs3m/ABiQFHm2ilhzNS+OsMCpOsfGl2kXguEdt86QPp60Q== + dependencies: + "@graphql-tools/schema" "9.0.10" + "@graphql-tools/utils" "9.1.1" + p-limit "3.1.0" + tslib "^2.4.0" + +"@graphql-tools/merge@8.3.12", "@graphql-tools/merge@^8.2.6": + version "8.3.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.12.tgz#e3f2e5d8a7b34fb689cda66799d845cbc919e464" + integrity sha512-BFL8r4+FrqecPnIW0H8UJCBRQ4Y8Ep60aujw9c/sQuFmQTiqgWgpphswMGfaosP2zUinDE3ojU5wwcS2IJnumA== + dependencies: + "@graphql-tools/utils" "9.1.1" + tslib "^2.4.0" + +"@graphql-tools/merge@8.3.6": + version "8.3.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.6.tgz#97a936d4c8e8f935e58a514bb516c476437b5b2c" + integrity sha512-uUBokxXi89bj08P+iCvQk3Vew4vcfL5ZM6NTylWi8PIpoq4r5nJ625bRuN8h2uubEdRiH8ntN9M4xkd/j7AybQ== + dependencies: + "@graphql-tools/utils" "8.12.0" + tslib "^2.4.0" + +"@graphql-tools/optimize@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/optimize/-/optimize-1.3.1.tgz#29407991478dbbedc3e7deb8c44f46acb4e9278b" + integrity sha512-5j5CZSRGWVobt4bgRRg7zhjPiSimk+/zIuColih8E8DxuFOaJ+t0qu7eZS5KXWBkjcd4BPNuhUPpNlEmHPqVRQ== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/prisma-loader@^7.2.7": + version "7.2.42" + resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-7.2.42.tgz#56fc731f8315bb6ee5d59abfb26fb016278fe396" + integrity sha512-sdfoj8oM67okez4JAfJxrRXraCU62YXcCWr2DKQNcZDcVKVqUXfEESd7p5C/2l2ThC1+RtG3bOnvo0FWw3+U+g== + dependencies: + "@graphql-tools/url-loader" "7.16.22" + "@graphql-tools/utils" "9.1.1" + "@types/js-yaml" "^4.0.0" + "@types/json-stable-stringify" "^1.0.32" + "@types/jsonwebtoken" "^8.5.0" + chalk "^4.1.0" + debug "^4.3.1" + dotenv "^16.0.0" + graphql-request "^5.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + isomorphic-fetch "^3.0.0" + js-yaml "^4.0.0" + json-stable-stringify "^1.0.1" + jsonwebtoken "^8.5.1" + lodash "^4.17.20" + scuid "^1.1.0" + tslib "^2.4.0" + yaml-ast-parser "^0.0.43" + +"@graphql-tools/relay-operation-optimizer@^6.5.0": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.5.12.tgz#659af3aff0e70bfed0f45d02776c8c0dd7f5fb03" + integrity sha512-jwcgNK1S8fqDI612uhbZSZTmQ0aJrLjtOSEcelwZ6Ec7o29I3NlOMBGnjvnBr4Y2tUFWZhBKfx0aEn6EJlhiGA== + dependencies: + "@ardatan/relay-compiler" "12.0.0" + "@graphql-tools/utils" "9.1.1" + tslib "^2.4.0" + +"@graphql-tools/schema@9.0.10", "@graphql-tools/schema@^9.0.0": + version "9.0.10" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.10.tgz#77ba3dfab241f0232dc0d3ba03201663b63714e2" + integrity sha512-lV0o4df9SpPiaeeDAzgdCJ2o2N9Wvsp0SMHlF2qDbh9aFCFQRsXuksgiDm2yTgT3TG5OtUes/t0D6uPjPZFUbQ== + dependencies: + "@graphql-tools/merge" "8.3.12" + "@graphql-tools/utils" "9.1.1" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/schema@9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.4.tgz#1a74608b57abf90fae6fd929d25e5482c57bc05d" + integrity sha512-B/b8ukjs18fq+/s7p97P8L1VMrwapYc3N2KvdG/uNThSazRRn8GsBK0Nr+FH+mVKiUfb4Dno79e3SumZVoHuOQ== + dependencies: + "@graphql-tools/merge" "8.3.6" + "@graphql-tools/utils" "8.12.0" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/url-loader@7.16.22", "@graphql-tools/url-loader@^7.13.2", "@graphql-tools/url-loader@^7.9.7": + version "7.16.22" + resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.16.22.tgz#3c210d8755082f2686cbdb1024fa5f7fb0e49a5f" + integrity sha512-JA0+7w2eidPmsVFRFgzZQ+RQKiS9WE0T5R/wKudxLdC8NnCKdEw0hdA7wKmhhIXNhOs5UtWGfwQ1O2CrvStxWw== + dependencies: + "@ardatan/sync-fetch" "0.0.1" + "@graphql-tools/delegate" "9.0.17" + "@graphql-tools/executor-graphql-ws" "0.0.3" + "@graphql-tools/executor-http" "0.0.4" + "@graphql-tools/executor-legacy-ws" "0.0.3" + "@graphql-tools/utils" "9.1.1" + "@graphql-tools/wrap" "9.2.18" + "@types/ws" "^8.0.0" + "@whatwg-node/fetch" "^0.5.0" + isomorphic-ws "5.0.0" + tslib "^2.4.0" + value-or-promise "^1.0.11" + ws "8.11.0" + +"@graphql-tools/utils@8.12.0": + version "8.12.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.12.0.tgz#243bc4f5fc2edbc9e8fd1038189e57d837cbe31f" + integrity sha512-TeO+MJWGXjUTS52qfK4R8HiPoF/R7X+qmgtOYd8DTH0l6b+5Y/tlg5aGeUJefqImRq7nvi93Ms40k/Uz4D5CWw== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/utils@9.1.1", "@graphql-tools/utils@^9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.1.1.tgz#b47ea8f0d18c038c5c1c429e72caa5c25039fbab" + integrity sha512-DXKLIEDbihK24fktR2hwp/BNIVwULIHaSTNTNhXS+19vgT50eX9wndx1bPxGwHnVBOONcwjXy0roQac49vdt/w== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/utils@^8.6.5", "@graphql-tools/utils@^8.8.0", "@graphql-tools/utils@^8.9.0": + version "8.13.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.13.1.tgz#b247607e400365c2cd87ff54654d4ad25a7ac491" + integrity sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/wrap@9.2.18": + version "9.2.18" + resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-9.2.18.tgz#be19f164dad8c855081b190db9953e6e15b0343c" + integrity sha512-ocdwRM2lDjqXiu/1tpvegmxumYuwHZVLLnzLFuch5i5S10y+EmTqcfgalG/2CbMrPV6BS9t4R7/w6p6+ZbppVg== + dependencies: + "@graphql-tools/delegate" "9.0.17" + "@graphql-tools/schema" "9.0.10" + "@graphql-tools/utils" "9.1.1" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-typed-document-node/core@3.1.1", "@graphql-typed-document-node/core@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" + integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== + "@humanwhocodes/config-array@^0.10.5": version "0.10.7" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc" @@ -353,6 +1170,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@iarna/toml@^2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" + integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== + "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" @@ -876,6 +1698,19 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" +"@octokit/core@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" + integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^7.0.0": version "7.0.2" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.2.tgz#11ee868406ba7bb1642e61bbe676d641f79f02be" @@ -899,6 +1734,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-13.13.1.tgz#a783bacb1817c9f61a2a0c3f81ea22ad62340fdf" integrity sha512-4EuKSk3N95UBWFau3Bz9b3pheQ8jQYbKmBL5+GSuY8YDPDwu03J4BjI+66yNi8aaX/3h1qDpb0mbBkLdr+cfGQ== +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== + "@octokit/plugin-paginate-rest@^4.0.0": version "4.3.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.3.1.tgz#553e653ee0318605acd23bf3a799c8bfafdedae3" @@ -906,6 +1746,13 @@ dependencies: "@octokit/types" "^7.5.0" +"@octokit/plugin-paginate-rest@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz#93d7e74f1f69d68ba554fa6b888c2a9cf1f99a83" + integrity sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw== + dependencies: + "@octokit/types" "^8.0.0" + "@octokit/plugin-request-log@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" @@ -919,6 +1766,14 @@ "@octokit/types" "^7.5.0" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@^6.7.0": + version "6.7.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" + integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== + dependencies: + "@octokit/types" "^8.0.0" + deprecation "^2.3.1" + "@octokit/request-error@^3.0.0": version "3.0.1" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.1.tgz#3fd747913c06ab2195e52004a521889dadb4b295" @@ -950,6 +1805,16 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^6.0.0" +"@octokit/rest@^19.0.5": + version "19.0.5" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.5.tgz#4dbde8ae69b27dca04b5f1d8119d282575818f6c" + integrity sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow== + dependencies: + "@octokit/core" "^4.1.0" + "@octokit/plugin-paginate-rest" "^5.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.7.0" + "@octokit/types@^7.0.0", "@octokit/types@^7.5.0": version "7.5.1" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-7.5.1.tgz#4e8b182933c17e1f41cc25d44757dbdb7bd76c1b" @@ -957,11 +1822,50 @@ dependencies: "@octokit/openapi-types" "^13.11.0" +"@octokit/types@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.0.0.tgz#93f0b865786c4153f0f6924da067fe0bb7426a9f" + integrity sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg== + dependencies: + "@octokit/openapi-types" "^14.0.0" + +"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777" + integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ== + dependencies: + asn1js "^3.0.5" + pvtsutils "^1.3.2" + tslib "^2.4.0" + +"@peculiar/json-schema@^1.1.12": + version "1.1.12" + resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339" + integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w== + dependencies: + tslib "^2.0.0" + +"@peculiar/webcrypto@^1.4.0": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.1.tgz#821493bd5ad0f05939bd5f53b28536f68158360a" + integrity sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw== + dependencies: + "@peculiar/asn1-schema" "^2.3.0" + "@peculiar/json-schema" "^1.1.12" + pvtsutils "^1.3.2" + tslib "^2.4.1" + webcrypto-core "^1.7.4" + "@phc/format@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4" integrity sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ== +"@repeaterjs/repeater@3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" + integrity sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA== + "@semantic-release/commit-analyzer@^9.0.2": version "9.0.2" resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz#a78e54f9834193b55f1073fa6258eecc9a545e03" @@ -1201,11 +2105,28 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/js-yaml@^4.0.0": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== + "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-stable-stringify@^1.0.32": + version "1.0.34" + resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75" + integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw== + +"@types/jsonwebtoken@^8.5.0": + version "8.5.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz#2c064ecb0b3128d837d2764aa0b117b0ff6e4586" + integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== + dependencies: + "@types/node" "*" + "@types/listr@^0.14.4": version "0.14.4" resolved "https://registry.yarnpkg.com/@types/listr/-/listr-0.14.4.tgz#6ba2a4206615cf80d79d10f9ba9701c303cd57b6" @@ -1321,6 +2242,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/ws@^8.0.0": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -1420,6 +2348,43 @@ "@typescript-eslint/types" "5.39.0" eslint-visitor-keys "^3.3.0" +"@urql/core@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@urql/core/-/core-3.0.5.tgz#a26c326dd788d6d6abb839493bce86147f5a45c9" + integrity sha512-6/1HG+WEAcPs+hXSFnxWBTWkNUwa8dj2cHysWokMaFIbAioGtUaSdxp2q9FDMtWAIGdc640NFSt2B8itGLdoAA== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + wonka "^6.0.0" + +"@whatwg-node/fetch@0.5.3", "@whatwg-node/fetch@^0.5.0": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@whatwg-node/fetch/-/fetch-0.5.3.tgz#afbd38a2e5392d91318845b967529076ca654b9e" + integrity sha512-cuAKL3Z7lrJJuUrfF1wxkQTb24Qd1QO/lsjJpM5ZSZZzUMms5TPnbGeGUKWA3hVKNHh30lVfr2MyRCT5Jfkucw== + dependencies: + "@peculiar/webcrypto" "^1.4.0" + abort-controller "^3.0.0" + busboy "^1.6.0" + form-data-encoder "^1.7.1" + formdata-node "^4.3.1" + node-fetch "^2.6.7" + undici "^5.12.0" + web-streams-polyfill "^3.2.0" + +"@whatwg-node/fetch@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@whatwg-node/fetch/-/fetch-0.3.2.tgz#da4323795c26c135563ba01d49dc16037bec4287" + integrity sha512-Bs5zAWQs0tXsLa4mRmLw7Psps1EN78vPtgcLpw3qPY8s6UYPUM67zFZ9cy+7tZ64PXhfwzxJn+m7RH2Lq48RNQ== + dependencies: + "@peculiar/webcrypto" "^1.4.0" + abort-controller "^3.0.0" + busboy "^1.6.0" + event-target-polyfill "^0.0.3" + form-data-encoder "^1.7.1" + formdata-node "^4.3.1" + node-fetch "^2.6.7" + undici "^5.8.0" + web-streams-polyfill "^3.2.0" + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -1433,6 +2398,13 @@ abbrev@1, abbrev@^1.0.0, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1499,7 +2471,7 @@ ansi-align@^3.0.0, ansi-align@^3.0.1: dependencies: string-width "^4.1.0" -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1650,16 +2622,35 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -asap@^2.0.0: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1js@^3.0.1, asn1js@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" + integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ== + dependencies: + pvtsutils "^1.3.2" + pvutils "^1.1.3" + tslib "^2.4.0" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +auto-bind@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" + integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== + babel-jest@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" @@ -1694,6 +2685,11 @@ babel-plugin-jest-hoist@^29.2.0: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" +babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" + integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ== + babel-preset-current-node-syntax@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" @@ -1712,6 +2708,39 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" +babel-preset-fbjs@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" + integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-member-expression-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-property-literals" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" + babel-preset-jest@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc" @@ -1763,7 +2792,7 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bl@^4.0.3: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -1856,6 +2885,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +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" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -1884,6 +2918,13 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + cacache@^15.2.0: version "15.3.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" @@ -1950,6 +2991,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -1959,7 +3008,7 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" -camelcase@^5.3.1: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -1979,6 +3028,15 @@ caniuse-lite@^1.0.30001400: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6" integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== +capital-case@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" + integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + upper-case-first "^2.0.2" + cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" @@ -1996,7 +3054,7 @@ chalk@^2.0.0, chalk@^2.3.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2014,12 +3072,51 @@ chalk@^5.0.1: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.1.2.tgz#d957f370038b75ac572471e83be4c5ca9f8e8c45" integrity sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ== +change-case-all@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1" + integrity sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA== + dependencies: + change-case "^4.1.2" + is-lower-case "^2.0.2" + is-upper-case "^2.0.2" + lower-case "^2.0.2" + lower-case-first "^2.0.2" + sponge-case "^1.0.1" + swap-case "^2.0.2" + title-case "^3.0.3" + upper-case "^2.0.2" + upper-case-first "^2.0.2" + +change-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12" + integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A== + dependencies: + camel-case "^4.1.2" + capital-case "^1.0.4" + constant-case "^3.0.4" + dot-case "^3.0.4" + header-case "^2.0.4" + no-case "^3.0.4" + param-case "^3.0.4" + pascal-case "^3.1.2" + path-case "^3.0.4" + sentence-case "^3.0.4" + snake-case "^3.0.4" + tslib "^2.0.3" + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chokidar@^3.5.3: +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -2089,6 +3186,13 @@ cli-columns@^4.0.0: string-width "^4.2.3" strip-ansi "^6.0.1" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-highlight@^2.1.11: version "2.1.11" resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" @@ -2101,6 +3205,11 @@ cli-highlight@^2.1.11: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^16.0.0" +cli-spinners@^2.5.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" + integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== + cli-table3@^0.6.1, cli-table3@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" @@ -2110,6 +3219,28 @@ cli-table3@^0.6.1, cli-table3@^0.6.2: optionalDependencies: "@colors/colors" "1.5.0" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -2186,6 +3317,11 @@ color-support@^1.1.2, color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colorette@^2.0.16: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + columnify@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" @@ -2211,6 +3347,11 @@ common-ancestor-path@^1.0.1: resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== +common-tags@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" @@ -2241,6 +3382,15 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +constant-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" + integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + upper-case "^2.0.2" + conventional-changelog-angular@^5.0.0: version "5.0.13" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" @@ -2310,6 +3460,23 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig-toml-loader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz#0681383651cceff918177debe9084c0d3769509b" + integrity sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA== + dependencies: + "@iarna/toml" "^2.2.5" + +cosmiconfig-typescript-loader@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.1.1.tgz#38dd3578344038dae40fdf09792bc2e9df529f78" + integrity sha512-9DHpa379Gp0o0Zefii35fcmuuin6q92FnLDffzdZ0l9tVd3nEobG3O+MZ06+kuBvFTSVScvNb/oHA13Nd4iipg== + +cosmiconfig-typescript-loader@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.2.0.tgz#a3cfd0dd9dac86be7dbe5f53eb46ad03abdf417b" + integrity sha512-NkANeMnaHrlaSSlpKGyvn2R4rqUDeE/9E5YHx+b4nwo0R8dZyAqcih8/gxpCZvqWP9Vf6xuLpMSzSgdVEIM78g== + cosmiconfig@7.0.1, cosmiconfig@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" @@ -2331,7 +3498,7 @@ cronstrue@^2.20.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.20.0.tgz#709ef674ccf385f3c0e77c40a5141911e38b5d05" integrity sha512-cGGxqXGt+yf46nQw2nipLpQLaqlXbeAdcR2HoHYar91bpWRFViw16XCwbSLs0uwhzzYjHwN9BVw5SYIXNqIFwg== -cross-fetch@3.1.5: +cross-fetch@3.1.5, cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -2357,6 +3524,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +dataloader@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.1.0.tgz#c69c538235e85e7ac6c6c444bae8ecabf5de9df7" + integrity sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ== + date-fns@^2.28.0: version "2.29.3" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" @@ -2372,7 +3544,12 @@ dayjs@^1.11.6: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debounce@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2392,7 +3569,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0: +decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -2465,11 +3642,21 @@ depd@^1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +dependency-graph@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" + integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +detect-indent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -2527,6 +3714,14 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + dot-prop@^5.1.0, dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2539,6 +3734,11 @@ dotenv@^16.0.0, dotenv@^16.0.3: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +dset@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a" + integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q== + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -2556,6 +3756,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + electron-to-chromium@^1.4.251: version "1.4.276" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520" @@ -2773,6 +3980,16 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-polyfill@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/event-target-polyfill/-/event-target-polyfill-0.0.3.tgz#ed373295f3b257774b5d75afb2599331d9f3406c" + integrity sha512-ZMc6UuvmbinrCk4RzGyVmRyIsAyxMRlp4CqSrcQRO8Dy0A9ldbiRy5kdtBj4OtP7EClGdqGfIqo9JmOClMsGLQ== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2815,6 +4032,25 @@ expect@^29.3.1: jest-message-util "^29.3.1" jest-util "^29.3.1" +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extract-files@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-11.0.0.tgz#b72d428712f787eef1f5193aff8ab5351ca8469a" + integrity sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ== + +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" @@ -2876,6 +4112,24 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -2954,6 +4208,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +form-data-encoder@^1.7.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -2963,6 +4222,14 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-node@^4.3.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -3055,7 +4322,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -3110,7 +4377,7 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: +glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3152,7 +4419,7 @@ globals@^13.15.0: dependencies: type-fest "^0.20.2" -globby@^11.0.0, globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -3191,6 +4458,52 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphql-config@4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-4.3.6.tgz#908ef03d6670c3068e51fe2e84e10e3e0af220b6" + integrity sha512-i7mAPwc0LAZPnYu2bI8B6yXU5820Wy/ArvmOseDLZIu0OU1UTULEuexHo6ZcHXeT9NvGGaUPQZm8NV3z79YydA== + dependencies: + "@graphql-tools/graphql-file-loader" "^7.3.7" + "@graphql-tools/json-file-loader" "^7.3.7" + "@graphql-tools/load" "^7.5.5" + "@graphql-tools/merge" "^8.2.6" + "@graphql-tools/url-loader" "^7.9.7" + "@graphql-tools/utils" "^8.6.5" + cosmiconfig "7.0.1" + cosmiconfig-toml-loader "1.0.0" + cosmiconfig-typescript-loader "^4.0.0" + minimatch "4.2.1" + string-env-interpolation "1.0.1" + ts-node "^10.8.1" + tslib "^2.4.0" + +graphql-request@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.0.0.tgz#7504a807d0e11be11a3c448e900f0cc316aa18ef" + integrity sha512-SpVEnIo2J5k2+Zf76cUkdvIRaq5FMZvGQYnA4lUWYbc99m+fHh4CZYRRO/Ff4tCLQ613fzCm3SiDT64ubW5Gyw== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql-tag@^2.11.0, graphql-tag@^2.12.6: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" + +graphql-ws@5.11.2: + version "5.11.2" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.11.2.tgz#d5e0acae8b4d4a4cf7be410a24135cfcefd7ddc0" + integrity sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w== + +graphql@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -3235,6 +4548,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +header-case@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" + integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q== + dependencies: + capital-case "^1.0.4" + tslib "^2.0.3" + highlight.js@^10.7.1: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" @@ -3312,6 +4633,13 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -3336,6 +4664,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +immutable@~3.7.6: + version "3.7.6" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -3344,7 +4677,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^4.0.0: +import-from@4.0.0, import-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2" integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ== @@ -3418,6 +4751,27 @@ init-package-json@^3.0.2: validate-npm-package-license "^3.0.4" validate-npm-package-name "^4.0.0" +inquirer@^8.0.0: + version "8.2.5" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + into-stream@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" @@ -3426,6 +4780,13 @@ into-stream@^6.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -3436,6 +4797,14 @@ ip@^2.0.0: resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -3484,7 +4853,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@4.0.3, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -3499,11 +4868,23 @@ is-installed-globally@^0.4.0: global-dirs "^3.0.0" is-path-inside "^3.0.2" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a" + integrity sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ== + dependencies: + tslib "^2.0.3" + is-npm@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" @@ -3539,6 +4920,13 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -3556,6 +4944,30 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-upper-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-2.0.2.tgz#f1105ced1fe4de906a5f39553e7d3803fd804649" + integrity sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ== + dependencies: + tslib "^2.0.3" + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" @@ -3571,6 +4983,19 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== + dependencies: + node-fetch "^2.6.1" + whatwg-fetch "^3.4.1" + +isomorphic-ws@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + issue-parser@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-6.0.0.tgz#b1edd06315d4f2044a9755daf85fdafde9b4014a" @@ -4047,7 +5472,7 @@ js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== -js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -4060,7 +5485,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.0.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -4102,6 +5527,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz#e06f23128e0bbe342dc996ed5a19e28b57b580e0" + integrity sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g== + dependencies: + jsonify "^0.0.1" + json-stringify-nice@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" @@ -4112,6 +5544,14 @@ json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== +json-to-pretty-yaml@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz#f4cd0bd0a5e8fe1df25aaf5ba118b099fd992d5b" + integrity sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A== + dependencies: + remedial "^1.0.7" + remove-trailing-spaces "^1.0.6" + json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" @@ -4126,6 +5566,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -4136,6 +5581,22 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + just-diff-apply@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" @@ -4146,6 +5607,23 @@ just-diff@^5.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -4301,6 +5779,20 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listr2@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" + integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.5" + through "^2.3.8" + wrap-ansi "^7.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -4343,11 +5835,31 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -4368,16 +5880,60 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.uniqby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== -lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.0.0, log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case-first@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-2.0.2.tgz#64c2324a2250bf7c37c5901e76a5b5309301160b" + integrity sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg== + dependencies: + tslib "^2.0.3" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -4463,6 +6019,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +map-cache@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -4517,6 +6078,11 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +meros@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/meros/-/meros-1.2.1.tgz#056f7a76e8571d0aaf3c7afcbe7eb6407ff7329e" + integrity sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g== + micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -4557,6 +6123,13 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimatch@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" + integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== + dependencies: + brace-expansion "^1.1.7" + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -4699,12 +6272,12 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.2: +ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mute-stream@~0.0.4: +mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== @@ -4743,6 +6316,14 @@ nerf-dart@^1.0.0: resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" integrity sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g== +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + node-addon-api@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" @@ -4760,6 +6341,11 @@ node-cron@^3.0.2: dependencies: uuid "8.3.2" +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-emoji@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -4767,7 +6353,7 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.7, node-fetch@^2.6.7: +node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -4860,6 +6446,13 @@ normalize-package-data@^4.0.0: semver "^7.3.5" validate-npm-package-license "^3.0.4" +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -5073,7 +6666,12 @@ npmlog@^6.0.0, npmlog@^6.0.2: gauge "^4.0.3" set-blocking "^2.0.0" -object-assign@^4.0.1, object-assign@^4.1.1: +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -5085,7 +6683,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -5109,6 +6707,26 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -5131,6 +6749,13 @@ p-is-promise@^3.0.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== +p-limit@3.1.0, p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -5145,13 +6770,6 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -5245,6 +6863,14 @@ pacote@^13.0.3, pacote@^13.6.1, pacote@^13.6.2: ssri "^9.0.0" tar "^6.1.11" +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -5261,6 +6887,15 @@ parse-conflict-json@^2.0.1, parse-conflict-json@^2.0.2: just-diff "^5.0.1" just-diff-apply "^5.2.0" +parse-filepath@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -5301,6 +6936,22 @@ parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" + integrity sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5326,6 +6977,18 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== + dependencies: + path-root-regex "^0.1.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -5476,6 +7139,13 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -5544,6 +7214,18 @@ puppeteer@^19.3.0: proxy-from-env "1.1.0" puppeteer-core "19.3.0" +pvtsutils@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de" + integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ== + dependencies: + tslib "^2.4.0" + +pvutils@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" + integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== + q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -5692,6 +7374,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -5711,6 +7398,30 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +relay-runtime@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-12.0.0.tgz#1e039282bdb5e0c1b9a7dc7f6b9a09d4f4ff8237" + integrity sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^3.0.0" + invariant "^2.2.4" + +remedial@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" + integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg== + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +remove-trailing-spaces@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz#4354d22f3236374702f58ee373168f6d6887ada7" + integrity sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5721,6 +7432,11 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -5728,16 +7444,16 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve.exports@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" @@ -5759,6 +7475,14 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -5774,6 +7498,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -5781,6 +7510,11 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5795,6 +7529,13 @@ rxjs@^6.5.1: dependencies: tslib "^1.9.0" +rxjs@^7.5.5: + version "7.6.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2" + integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -5810,7 +7551,7 @@ safe-stable-stringify@^2.4.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61" integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA== -"safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5820,6 +7561,11 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +scuid@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab" + integrity sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg== + semantic-release@^19.0.5: version "19.0.5" resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-19.0.5.tgz#d7fab4b33fc20f1288eafd6c441e5d0938e5e174" @@ -5866,7 +7612,7 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.4.tgz#13053c0d4aa11d070a2f2872b6b1e3ae1e1971b4" integrity sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA== -"semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5883,6 +7629,15 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +sentence-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" + integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + upper-case-first "^2.0.2" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -5893,6 +7648,11 @@ set-cookie-parser@^2.5.1: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + sha.js@^2.4.11: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -5913,6 +7673,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.7.3: + version "1.7.4" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" + integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -5927,6 +7692,11 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" +signedsource@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" + integrity sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -5937,11 +7707,37 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + socks-proxy-agent@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" @@ -6033,6 +7829,13 @@ split@^1.0.0: dependencies: through "2" +sponge-case@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c" + integrity sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA== + dependencies: + tslib "^2.0.3" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6078,6 +7881,16 @@ stream-combiner2@~1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +string-env-interpolation@1.0.1, string-env-interpolation@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" + integrity sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -6198,6 +8011,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swap-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-2.0.2.tgz#671aedb3c9c137e2985ef51c51f9e98445bf70d9" + integrity sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw== + dependencies: + tslib "^2.0.3" + tar-fs@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -6307,7 +8127,7 @@ through2@~2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -6317,6 +8137,20 @@ tiny-relative-date@^1.3.0: resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== +title-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982" + integrity sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA== + dependencies: + tslib "^2.0.3" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6359,6 +8193,11 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +ts-essentials@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.3.0.tgz#7e639c1a76b1805c3c60d6e1b5178da2e70aea02" + integrity sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw== + ts-jest@^29.0.3: version "29.0.3" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77" @@ -6386,7 +8225,12 @@ ts-json-schema-generator@^1.1.2: safe-stable-stringify "^2.4.0" typescript "~4.8.3" -ts-node@^10.9.1: +ts-log@^2.2.3: + version "2.2.5" + resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.5.tgz#aef3252f1143d11047e2cb6f7cfaac7408d96623" + integrity sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA== + +ts-node@^10.8.1, ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== @@ -6431,7 +8275,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@~2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== @@ -6540,6 +8384,11 @@ typescript@~4.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== +ua-parser-js@^0.7.30: + version "0.7.32" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211" + integrity sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw== + uglify-js@^3.1.4: version "3.17.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" @@ -6553,6 +8402,18 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== + +undici@^5.12.0, undici@^5.8.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.13.0.tgz#56772fba89d8b25e39bddc8c26a438bd73ea69bb" + integrity sha512-UDZKtwb2k7KRsK4SdXWG7ErXiL7yTGgLWvk2AXO1JMjgjh404nFo6tWSCM2xMpJwMPx3J8i/vfqEh1zOqvj82Q== + dependencies: + busboy "^1.6.0" + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -6598,6 +8459,13 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unixify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" + integrity sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg== + dependencies: + normalize-path "^2.1.1" + update-browserslist-db@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" @@ -6626,6 +8494,20 @@ update-notifier@^5.1.0: semver-diff "^3.1.1" xdg-basedir "^4.0.0" +upper-case-first@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" + integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg== + dependencies: + tslib "^2.0.3" + +upper-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a" + integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg== + dependencies: + tslib "^2.0.3" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -6684,6 +8566,11 @@ validate-npm-package-name@^4.0.0: dependencies: builtins "^5.0.0" +value-or-promise@1.0.11, value-or-promise@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" + integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== + walk-up-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" @@ -6696,18 +8583,44 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wcwidth@^1.0.0: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== dependencies: defaults "^1.0.3" +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +web-streams-polyfill@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +webcrypto-core@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4" + integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A== + dependencies: + "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/json-schema" "^1.1.12" + asn1js "^3.0.1" + pvtsutils "^1.3.2" + tslib "^2.4.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +whatwg-fetch@^3.4.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -6716,6 +8629,11 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -6744,6 +8662,11 @@ widest-line@^4.0.1: dependencies: string-width "^5.0.1" +wonka@^6.0.0: + version "6.1.2" + resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.1.2.tgz#2c66fa5b26a12f002a03619b988258313d0b5352" + integrity sha512-zNrXPMccg/7OEp9tSfFkMgTvhhowqasiSHdJ3eCZolXxVTV/aT6HUTofoZk9gwRbGoFey/Nss3JaZKUMKMbofg== + word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -6754,6 +8677,15 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -6800,6 +8732,11 @@ ws@8.10.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== +ws@8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -6823,6 +8760,11 @@ xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -6833,21 +8775,51 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml-ast-parser@^0.0.43: + version "0.0.43" + resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" + integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.0, yargs-parser@^21.0.1: +yargs-parser@^21.0.0, yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" @@ -6861,6 +8833,19 @@ yargs@^16.0.0, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.0: + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== + 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" + yargs@^17.3.1: version "17.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" From 6aa7583b66d4cfa5ff0dc030dee2f434bf11143b Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 20:58:15 +0900 Subject: [PATCH 072/140] ci: add ci step for generating graphql queries related types --- .github/workflows/ci.yml | 4 ++++ .github/workflows/docker-deploy.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c486dc5..36be3ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,10 @@ jobs: run: | yarn + - name: Generate codes for GraphQL + run: | + yarn codegen + - name: Lint run: | yarn lint diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 78a1adb..092c516 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -49,6 +49,10 @@ jobs: run: | yarn + - name: Generate codes for GraphQL + run: | + yarn codegen + - name: Lint run: | yarn lint From 5565d2db19114e26542a227400b90c1f5e30c196 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 20:58:23 +0900 Subject: [PATCH 073/140] docs: update project description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d4a18f..106bd95 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ ## Introduction -Cage is a cli application for detecting unfollowers on any social services. +Cage is a cli application for detecting unfollowers on any social services. this application will check all of your followers using each watcher and compare them to the previous check. if there is a difference, it will notify you of the changes. ## Usage From dd5f76db5b253d3b6bcb63ca3ab85b1252d18e1f Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 20:59:18 +0900 Subject: [PATCH 074/140] =?UTF-8?q?refactor:=20change=20project=20mascot?= =?UTF-8?q?=20emoji=20from=20=F0=9F=90=A6=20to=20=F0=9F=A6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 106bd95..2845955 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ $ cage --help Usage: cage [options] -(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒 +(almost) realtime unfollower detection for any social services 🦜⛓️ 🔒 Options: -c, --config path to the configuration file (default: "./config.json") diff --git a/src/index.ts b/src/index.ts index 64e8900..edb9f80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import packageJson from "../package.json"; program .name("cage") - .description("(almost) realtime unfollower detection for any social services 🐦⛓️ 🔒") + .description("(almost) realtime unfollower detection for any social services 🦜⛓️ 🔒") .option("-c, --config ", "path to the configuration file", "./config.json") .option("-v, --verbose", "enable verbose level logging") .version(packageJson.version) From 606da97e734861da67e70c179c645e804c4fd8e2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 21:00:08 +0900 Subject: [PATCH 075/140] refactor: remove whitespace between emojis --- README.md | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2845955..6e53f5a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ $ cage --help Usage: cage [options] -(almost) realtime unfollower detection for any social services 🦜⛓️ 🔒 +(almost) realtime unfollower detection for any social services 🦜⛓️🔒 Options: -c, --config path to the configuration file (default: "./config.json") diff --git a/src/index.ts b/src/index.ts index edb9f80..afcfcd4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import packageJson from "../package.json"; program .name("cage") - .description("(almost) realtime unfollower detection for any social services 🦜⛓️ 🔒") + .description("(almost) realtime unfollower detection for any social services 🦜⛓️🔒") .option("-c, --config ", "path to the configuration file", "./config.json") .option("-v, --verbose", "enable verbose level logging") .version(packageJson.version) From 04ff51f19b74026b74fdfe0f708b065d84ed48ac Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 21:09:31 +0900 Subject: [PATCH 076/140] refactor(core): make `BaseWatcher.doWatch` as default-provided for better abstraction --- src/watchers/base.ts | 13 +++++++++- src/watchers/github/index.ts | 46 ++++++++++++++++++----------------- src/watchers/twitter/index.ts | 9 +++---- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 6cb1660..5c5ccf5 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -12,6 +12,8 @@ export interface BaseWatcherOptions { type: TType; } +export type PartialUserData = Omit; + export abstract class BaseWatcher extends Loggable { private readonly stateFileDirectory = path.join(process.cwd(), "./dump/"); @@ -21,7 +23,16 @@ export abstract class BaseWatcher extends Loggable public abstract initialize(): Promise; - public abstract doWatch(): Promise; + public async doWatch(): Promise { + const followers = await this.getFollowers(); + + return followers.map(user => ({ + ...user, + from: this.getName().toLowerCase(), + })); + } + + protected abstract getFollowers(): Promise; public async saveState() { await this.logger.work({ diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index de14e11..cda592c 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -1,11 +1,10 @@ import { Client, createClient } from "@urql/core"; -import { UserData } from "@repositories/models/user"; - +import { BaseWatcher, PartialUserData } from "@watchers/base"; +import { FollowersDocument, MeDocument } from "@watchers/github/queries"; import { GitHubWatcherOptions } from "@watchers/github/types"; -import { BaseWatcher } from "@watchers/base"; + import { FollowersQuery, FollowersQueryVariables, MeQuery } from "@root/queries.data"; -import { FollowersDocument, MeDocument } from "@watchers/github/queries"; import { Nullable } from "@utils/types"; export class GitHubWatcher extends BaseWatcher<"GitHub"> { @@ -30,13 +29,26 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { public async initialize() { return; } - public async doWatch(): Promise { - const result: UserData[] = []; + + private async getCurrentUserId() { + const { data, error } = await this.client.query(MeDocument, {}).toPromise(); + if (error) { + throw error; + } + + if (!data) { + throw new Error("No data returned from Me query"); + } + + return data.viewer.login; + } + public async getFollowers() { + const result: PartialUserData[] = []; const currentUserId = await this.getCurrentUserId(); let cursor: string | undefined = undefined; while (true) { - const [followers, nextCursor] = await this.getFollowers(currentUserId, cursor); + const [followers, nextCursor] = await this.getFollowersFromUserId(currentUserId, cursor); result.push(...followers); if (!nextCursor) { @@ -49,19 +61,10 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { return result; } - private async getCurrentUserId() { - const { data, error } = await this.client.query(MeDocument, {}).toPromise(); - if (error) { - throw error; - } - - if (!data) { - throw new Error("No data returned from Me query"); - } - - return data.viewer.login; - } - private async getFollowers(targetUserId: string, cursor?: string): Promise<[UserData[], Nullable]> { + private async getFollowersFromUserId( + targetUserId: string, + cursor?: string, + ): Promise<[PartialUserData[], Nullable]> { const { data, error } = await this.client .query(FollowersDocument, { username: targetUserId, @@ -93,13 +96,12 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { } return { - from: this.getName(), uniqueId: edge.node.id, userId: edge.node.login, displayName: edge.node.name || edge.node.login, }; }) - .filter((item: UserData | null): item is UserData => Boolean(item)), + .filter((item: PartialUserData | null): item is PartialUserData => Boolean(item)), data.user.followers.pageInfo.endCursor, ]; } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 1d4f4b7..22167ef 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -4,7 +4,6 @@ import pluralize from "pluralize"; import { BaseWatcher } from "@watchers/base"; import { TwitterWatcherOptions } from "@watchers/twitter/types"; -import { UserData } from "@repositories/models/user"; import { TwitterHelper } from "@watchers/twitter/helper"; export class TwitterWatcher extends BaseWatcher<"Twitter"> { @@ -17,7 +16,8 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { public async initialize(): Promise { await this.helper.initialize(); } - public async doWatch() { + + public async getFollowers() { const allFollowers = await this.helper.getFollowers(); this.logger.verbose("Successfully crawled {} {}", [ @@ -25,10 +25,7 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { pluralize("follower", allFollowers.length), ]); - return allFollowers.map(user => ({ - ...user, - from: this.getName().toLowerCase(), - })); + return allFollowers; } protected getHashData(): any { From 958175fb930d2f951a1744eb5946b443d6a01b07 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 21:13:53 +0900 Subject: [PATCH 077/140] chore: update graphql-code-generator configuration to point exact path --- codegen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen.ts b/codegen.ts index 9df8350..32f4f8f 100644 --- a/codegen.ts +++ b/codegen.ts @@ -2,7 +2,7 @@ import type { CodegenConfig } from "@graphql-codegen/cli"; const config: CodegenConfig = { overwrite: true, - schema: "./queries/github/schema.graphqls", + schema: "./schemas/github.graphqls", documents: ["./src/**/queries.ts"], generates: { "src/queries.data.ts": { From b595fc47b2f2b94ca6089e89b3ee0c507d001b76 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 21:18:30 +0900 Subject: [PATCH 078/140] chore: update .eslintignore --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index c361627..d81c985 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ /.git /dist /node_modules +/schemas From a270014aa6e0e9205b7fb6e66c4eeea727576c1a Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 21:21:52 +0900 Subject: [PATCH 079/140] chore: add auto-generated typing codes to .eslintignore --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index d81c985..579e186 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ /dist /node_modules /schemas +/src/queries.data.ts From 6eb2d585abd525e2f4b8d1b9eadb54656f55456d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 7 Dec 2022 12:44:43 +0000 Subject: [PATCH 080/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.5](https://github.com/async3619/cage/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2022-12-07) ### Features * **github:** implement github watcher using GraphQL API ([7c6b0de](https://github.com/async3619/cage/commit/7c6b0dea52d6d7bbe5d3230cf20b0817c27add77)) ### Internal * change project mascot emoji from 🐦 to 🦜 ([dd5f76d](https://github.com/async3619/cage/commit/dd5f76db5b253d3b6bcb63ca3ab85b1252d18e1f)) * **core:** make `BaseWatcher.doWatch` as default-provided for better abstraction ([04ff51f](https://github.com/async3619/cage/commit/04ff51f19b74026b74fdfe0f708b065d84ed48ac)) * remove whitespace between emojis ([606da97](https://github.com/async3619/cage/commit/606da97e734861da67e70c179c645e804c4fd8e2)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5685c0..7ead2ff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.4", + "version": "1.0.0-dev.5", "license": "MIT", "publishConfig": { "access": "public" From 57dd9296e69c00c734d2d676ae743d8b2e5b7b38 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 22:44:43 +0900 Subject: [PATCH 081/140] docs: write documentation for configuration file --- README.md | 104 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6e53f5a..4c65ba7 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ of course, you can also use `docker-compose`: version: "3.9" services: - cage: - image: async3619/cage - volumes: - - /path/to/config.json:/home/node/config.json + cage: + image: async3619/cage + volumes: + - /path/to/config.json:/home/node/config.json ``` ## Watchers and Notifiers @@ -73,17 +73,17 @@ services: #### Supported Watchers -| Service | Support? | -| --------- | :-----------------: | -| Twitter | ⚠️ (Partially, WIP) | -| Instagram | ❌ | -| TikTok | ❌ | -| YouTube | ❌ | -| Twitch | ❌ | -| Facebook | ❌ | -| Reddit | ❌ | -| Discord | ❌ | -| GitHub | ❌ | +| Service | Support? | +| --------- | :------: | +| Twitter | ✅ | +| GitHub | ✅ | +| Instagram | ❌ | +| TikTok | ❌ | +| YouTube | ❌ | +| Twitch | ❌ | +| Facebook | ❌ | +| Reddit | ❌ | +| Discord | ❌ | ### Notifiers @@ -102,22 +102,72 @@ When we detect unfollowers, new followers, or any other events, Cage will notify ## Configuration this application reads configuration file from `./cage.config.json` by default. - -you can use json schema file `config.schema.json` on this repository. here is example configuration file content: +if there's no configuration file to read, this application will create you a default configuration file for you: ```json { - "watchers": { - "twitter": { - "type": "twitter" - } - }, "watchInterval": 60000, - "notifiers": { - "discord": { - "type": "discord", - "webhookUrl": "https://discord.com/api/webhooks/..." - } + "watchers": {}, + "notifiers": {} +} +``` + +you can use json schema file `config.schema.json` on this repository. + +### watchInterval: `number` (required) + +specify watching interval in millisecond format. minimal value is `60000`. + +### watchers: Record (required) + +#### twitter + +```json5 +{ + "type": "twitter", // required + + // one of these... + "auth": { + "type": "api-key", // required + "apiKey": "API key of your twitter app", // string, required + "apiSecret": "API secret of your twitter app" // string, required + }, + + // or ... + "auth": { + "type": "bearer-token", // required + "bearerToken": "bearer token of your twitter app" // string, required + }, + + // or ... + "auth": { + "type": "basic", // required + "username": "user id of your twitter account (e.g. @user_account)", // string, required + "password": "password of your twitter account" // string, required } } ``` + +#### github + +watcher configuration for GitHub service. + +```json5 +{ + "type": "github", // required + "authToken": "personal access token of your github account" // string, required +} +``` + +### notifiers: Record (required) + +#### discord + +notifier configuration for Discord WebHook notifier. + +```json5 +{ + "type": "discord", + "webhookUrl": "Discord WebHook url" // string, required +} +``` From 19dad24efa476916175263ee8e642bda4157b8c9 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 22:47:10 +0900 Subject: [PATCH 082/140] docs: make field name to use correct format --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c65ba7..0d3f298 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ you can use json schema file `config.schema.json` on this repository. specify watching interval in millisecond format. minimal value is `60000`. -### watchers: Record (required) +### watchers: `Record` (required) #### twitter @@ -159,7 +159,7 @@ watcher configuration for GitHub service. } ``` -### notifiers: Record (required) +### notifiers: `Record` (required) #### discord From c491476b057b27e86532942ac0e2f561def57b03 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 22:53:45 +0900 Subject: [PATCH 083/140] refactor(core): use correct log level when no watcher configuration provided --- src/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 95fe971..ede1ce5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -53,7 +53,7 @@ export class App extends Loggable { const notifiers = this.config.notifiers; if (!watchers.length) { - this.logger.warn("no watchers are configured. exiting..."); + this.logger.error("no watchers are configured. exiting..."); return; } From 8e9adb5869fc49589f50111afd9b04fd1640219c Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 23:29:10 +0900 Subject: [PATCH 084/140] feat(core): implement `--drop-database` cli option that deletes database file --- README.md | 33 +++++++++++++++++---------------- src/app.ts | 26 ++++++++++++++++++++++++-- src/index.ts | 9 +++++++-- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0d3f298..f0eba65 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Usage: cage [options] Options: -c, --config path to the configuration file (default: "./config.json") + -d, --drop-database delete the old database file -v, --verbose enable verbose level logging -V, --version output the version number -h, --help display help for command @@ -74,16 +75,16 @@ services: #### Supported Watchers | Service | Support? | -| --------- | :------: | -| Twitter | ✅ | -| GitHub | ✅ | -| Instagram | ❌ | -| TikTok | ❌ | -| YouTube | ❌ | -| Twitch | ❌ | -| Facebook | ❌ | -| Reddit | ❌ | -| Discord | ❌ | +|-----------|:--------:| +| Twitter | ✅ | +| GitHub | ✅ | +| Instagram | ❌ | +| TikTok | ❌ | +| YouTube | ❌ | +| Twitch | ❌ | +| Facebook | ❌ | +| Reddit | ❌ | +| Discord | ❌ | ### Notifiers @@ -92,12 +93,12 @@ When we detect unfollowers, new followers, or any other events, Cage will notify #### Supported Notifiers | Service | Support? | -| --------------- | :------: | -| Discord WebHook | ✅ | -| Slack WebHook | ❌ | -| Telegram Bot | ❌ | -| Email | ❌ | -| SMS | ❌ | +|-----------------|:--------:| +| Discord WebHook | ✅ | +| Slack WebHook | ❌ | +| Telegram Bot | ❌ | +| Email | ❌ | +| SMS | ❌ | ## Configuration diff --git a/src/app.ts b/src/app.ts index ede1ce5..241e6ae 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import * as fs from "fs-extra"; import { DataSource } from "typeorm"; import chalk from "chalk"; import prettyMilliseconds from "pretty-ms"; @@ -23,17 +24,22 @@ export class App extends Loggable { private readonly followerDataSource: DataSource; private readonly userRepository: UserRepository; private readonly userLogRepository: UserLogRepository; + private readonly databasePath = path.join(process.cwd(), "./data.sqlite"); private cleaningUp = false; private config: Config | null = null; - public constructor(private readonly configFilePath: string, private readonly verbose: boolean) { + public constructor( + private readonly configFilePath: string, + private readonly verbose: boolean, + private readonly dropDatabase: boolean, + ) { super("App"); Logger.verbose = verbose; this.followerDataSource = new DataSource({ type: "sqlite", - database: path.join(process.cwd(), "./data.sqlite"), + database: this.databasePath, entities: [User, UserLog], synchronize: true, }); @@ -42,7 +48,23 @@ export class App extends Loggable { this.userLogRepository = new UserLogRepository(this.followerDataSource.getRepository(UserLog)); } + private async doDropDatabase() { + if (!fs.existsSync(this.databasePath)) { + return; + } + + await fs.unlink(this.databasePath); + } + public async run() { + if (this.dropDatabase) { + await this.logger.work({ + message: "dropping database", + work: () => this.doDropDatabase(), + level: "info", + }); + } + this.config = await Config.create(this.configFilePath); if (!this.config) { process.exit(-1); diff --git a/src/index.ts b/src/index.ts index afcfcd4..22f29d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,11 +14,16 @@ import packageJson from "../package.json"; .name("cage") .description("(almost) realtime unfollower detection for any social services 🦜⛓️🔒") .option("-c, --config ", "path to the configuration file", "./config.json") + .option("-d, --drop-database", "delete the old database file") .option("-v, --verbose", "enable verbose level logging") .version(packageJson.version) .parse(process.argv); - const { config, verbose } = program.opts<{ config: string; verbose: boolean }>(); + const { config, verbose, dropDatabase } = program.opts<{ + config: string; + verbose: boolean; + dropDatabase: boolean; + }>(); const { latest, current } = await updateNotifier({ pkg: packageJson, distTag: packageJson.version.includes("dev") ? "dev" : "latest", @@ -44,5 +49,5 @@ import packageJson from "../package.json"; ); } - await new App(config, verbose).run(); + await new App(config, verbose, dropDatabase).run(); })(); From 98cdcf62686e98a05ecb0edd1973524cc01147f8 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 23:39:17 +0900 Subject: [PATCH 085/140] feat(core): implement `--database` cli option that can specify database path --- src/app.ts | 6 +++++- src/index.ts | 18 +++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/app.ts b/src/app.ts index 241e6ae..3210e5c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -24,7 +24,6 @@ export class App extends Loggable { private readonly followerDataSource: DataSource; private readonly userRepository: UserRepository; private readonly userLogRepository: UserLogRepository; - private readonly databasePath = path.join(process.cwd(), "./data.sqlite"); private cleaningUp = false; private config: Config | null = null; @@ -33,10 +32,15 @@ export class App extends Loggable { private readonly configFilePath: string, private readonly verbose: boolean, private readonly dropDatabase: boolean, + private readonly databasePath: string, ) { super("App"); Logger.verbose = verbose; + if (!path.isAbsolute(this.databasePath)) { + this.databasePath = path.join(process.cwd(), this.databasePath); + } + this.followerDataSource = new DataSource({ type: "sqlite", database: this.databasePath, diff --git a/src/index.ts b/src/index.ts index 22f29d3..bdaea8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,13 @@ import { Logger } from "@utils/logger"; import packageJson from "../package.json"; +interface CLIOptions { + config: string; + verbose: boolean; + dropDatabase: boolean; + database: string; +} + (async () => { const program = new Command(); @@ -14,16 +21,13 @@ import packageJson from "../package.json"; .name("cage") .description("(almost) realtime unfollower detection for any social services 🦜⛓️🔒") .option("-c, --config ", "path to the configuration file", "./config.json") - .option("-d, --drop-database", "delete the old database file") + .option("-d, --database ", "path to the database file", "./data.sqlite") + .option("-p, --drop-database", "delete the old database file") .option("-v, --verbose", "enable verbose level logging") .version(packageJson.version) .parse(process.argv); - const { config, verbose, dropDatabase } = program.opts<{ - config: string; - verbose: boolean; - dropDatabase: boolean; - }>(); + const { config, verbose, dropDatabase, database } = program.opts(); const { latest, current } = await updateNotifier({ pkg: packageJson, distTag: packageJson.version.includes("dev") ? "dev" : "latest", @@ -49,5 +53,5 @@ import packageJson from "../package.json"; ); } - await new App(config, verbose, dropDatabase).run(); + await new App(config, verbose, dropDatabase, database).run(); })(); From 4112e490e751d9d591c0e614040922ed2492a5c1 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 7 Dec 2022 23:43:12 +0900 Subject: [PATCH 086/140] refactor(core): remove saving watcher states feature --- package.json | 1 - src/app.ts | 4 --- src/watchers/base.ts | 62 ----------------------------------- src/watchers/github/index.ts | 36 +++++++------------- src/watchers/twitter/index.ts | 16 ++------- yarn.lock | 21 +----------- 6 files changed, 15 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index 7ead2ff..c2fc918 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "@twitter-api-v2/plugin-rate-limit": "^1.1.0", "@urql/core": "^3.0.5", "ajv": "^8.11.2", - "argon2": "^0.30.2", "better-ajv-errors": "^1.2.0", "boxen": "^5.1.2", "chalk": "^4.1.2", diff --git a/src/app.ts b/src/app.ts index 3210e5c..157ea77 100644 --- a/src/app.ts +++ b/src/app.ts @@ -90,15 +90,11 @@ export class App extends Loggable { }); for (const [, watcher] of watchers) { - await watcher.loadState(); - await this.logger.work({ level: "info", message: `initialize \`${chalk.green(watcher.getName())}\` watcher`, work: () => watcher.initialize(), }); - - await watcher.saveState(); } for (const notifier of notifiers) { diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 5c5ccf5..dc14e69 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,7 +1,3 @@ -import * as path from "path"; -import * as argon2 from "argon2"; -import * as fs from "fs-extra"; - import { UserData } from "@repositories/models/user"; import { WatcherTypes } from "@watchers"; @@ -15,8 +11,6 @@ export interface BaseWatcherOptions { export type PartialUserData = Omit; export abstract class BaseWatcher extends Loggable { - private readonly stateFileDirectory = path.join(process.cwd(), "./dump/"); - protected constructor(name: TType) { super(name); } @@ -33,60 +27,4 @@ export abstract class BaseWatcher extends Loggable } protected abstract getFollowers(): Promise; - - public async saveState() { - await this.logger.work({ - message: "saving state", - level: "info", - work: async () => { - const serializedData = this.serialize(); - const fileName = `${this.getName().toLowerCase()}.json`; - const filePath = path.join(this.stateFileDirectory, fileName); - - serializedData["__hash__"] = await this.hash(); - - await fs.ensureFile(filePath); - await fs.writeJson(filePath, serializedData, { spaces: 4 }); - }, - }); - } - public async loadState() { - await this.logger.work({ - message: "loading state", - level: "info", - work: async () => { - const fileName = `${this.getName().toLowerCase()}.json`; - const filePath = path.join(this.stateFileDirectory, fileName); - - if (!fs.existsSync(filePath)) { - this.logger.warn(`saved state not found. skipping loading state.`); - return; - } - - const serializedData = await fs.readJson(filePath); - const isOutdated = !(await this.checkHash(serializedData["__hash__"])); - if (isOutdated) { - this.logger.warn(`saved state is outdated. skipping loading state.`); - return; - } - - this.hydrate(serializedData); - }, - }); - } - - private async hash() { - const hash = await argon2.hash(JSON.stringify(this.getHashData())); - return Buffer.from(hash).toString("base64"); - } - private async checkHash(hash: string) { - // base64 to string - hash = Buffer.from(hash, "base64").toString("utf-8"); - return argon2.verify(hash, JSON.stringify(this.getHashData())); - } - - protected abstract getHashData(): any; - - protected abstract serialize(): Record; - protected abstract hydrate(data: Record): void; } diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index cda592c..b924833 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -29,19 +29,6 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { public async initialize() { return; } - - private async getCurrentUserId() { - const { data, error } = await this.client.query(MeDocument, {}).toPromise(); - if (error) { - throw error; - } - - if (!data) { - throw new Error("No data returned from Me query"); - } - - return data.viewer.login; - } public async getFollowers() { const result: PartialUserData[] = []; const currentUserId = await this.getCurrentUserId(); @@ -61,6 +48,18 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { return result; } + private async getCurrentUserId() { + const { data, error } = await this.client.query(MeDocument, {}).toPromise(); + if (error) { + throw error; + } + + if (!data) { + throw new Error("No data returned from Me query"); + } + + return data.viewer.login; + } private async getFollowersFromUserId( targetUserId: string, cursor?: string, @@ -105,15 +104,4 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { data.user.followers.pageInfo.endCursor, ]; } - - protected getHashData(): any { - return this.options.authToken; - } - - protected hydrate(data: GitHubWatcherOptions): void { - this.options.authToken = data.authToken; - } - protected serialize(): GitHubWatcherOptions { - return this.options; - } } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 22167ef..3ab2a84 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -13,10 +13,10 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { super("Twitter"); this.helper = new TwitterHelper(auth); } - public async initialize(): Promise { + + public async initialize() { await this.helper.initialize(); } - public async getFollowers() { const allFollowers = await this.helper.getFollowers(); @@ -27,16 +27,4 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { return allFollowers; } - - protected getHashData(): any { - return this.helper.getHashData(); - } - protected serialize(): Record { - return { - helper: this.helper.serialize(), - }; - } - protected hydrate(data: Record): void { - this.helper.hydrate(data.helper); - } } diff --git a/yarn.lock b/yarn.lock index 97b7f1e..c39e2c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1455,7 +1455,7 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.10": +"@mapbox/node-pre-gyp@^1.0.0": version "1.0.10" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== @@ -1856,11 +1856,6 @@ tslib "^2.4.1" webcrypto-core "^1.7.4" -"@phc/format@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4" - integrity sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ== - "@repeaterjs/repeater@3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" @@ -2581,15 +2576,6 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argon2@^0.30.2: - version "0.30.2" - resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.30.2.tgz#b308a039d6d0cda5a3c47cbddd12685f033c33fa" - integrity sha512-RBbXTUsrJUQH259/72CCJxQa0hV961pV4PyZ7R1czGkArSsQP4DToCS2axmNfHywXaBNEMPWMW6rM82EArulYA== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.10" - "@phc/format" "^1.0.0" - node-addon-api "^5.0.0" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -6329,11 +6315,6 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-addon-api@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" - integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== - node-cron@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" From 1b3cb39287d95de9d2a0c80da3e7692135dc40cc Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:14:36 +0900 Subject: [PATCH 087/140] fix(core): fix a bug that application could not catch errors during collection followers --- src/app.ts | 26 ++++++++++++++++++++++---- src/watchers/github/index.ts | 36 +++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/app.ts b/src/app.ts index 157ea77..b48a53b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -120,7 +120,12 @@ export class App extends Loggable { try { const [, elapsedTime] = await throttle( async () => { - const { elapsedTime: time } = await measureTime(async () => this.onCycle()); + const result = await measureTime(async () => this.onCycle()); + if ("exception" in result) { + throw result.exception; + } + + const { elapsedTime: time } = result; this.logger.info(`last task finished in ${chalk.green("{}")}.`, [ prettyMilliseconds(time, { verbose: true }), @@ -146,7 +151,7 @@ export class App extends Loggable { throw e; } - this.logger.error("An error occurred while processing scheduled task: {}", [e.message]); + this.logger.error("an error occurred while processing scheduled task: {}", [e.message]); } } } @@ -159,9 +164,22 @@ export class App extends Loggable { const startedDate = new Date(); const allUserData: UserData[] = []; for (const [, watcher] of this.config.watchers) { - const userData = await watcher.doWatch(); + try { + const userData = await watcher.doWatch(); + + allUserData.push(...userData); + } catch (e) { + if (!(e instanceof Error)) { + throw e; + } - allUserData.push(...userData); + this.logger.error("an error occurred while watching through `{green}`: {}", [ + watcher.getName(), + e.message, + ]); + + process.exit(-1); + } } this.logger.info(`all {} {} collected.`, [allUserData.length, pluralize("follower", allUserData.length)]); diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index b924833..0fd236d 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -1,4 +1,4 @@ -import { Client, createClient } from "@urql/core"; +import { Client, CombinedError, createClient } from "@urql/core"; import { BaseWatcher, PartialUserData } from "@watchers/base"; import { FollowersDocument, MeDocument } from "@watchers/github/queries"; @@ -30,22 +30,36 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { return; } public async getFollowers() { - const result: PartialUserData[] = []; - const currentUserId = await this.getCurrentUserId(); + try { + const result: PartialUserData[] = []; + const currentUserId = await this.getCurrentUserId(); - let cursor: string | undefined = undefined; - while (true) { - const [followers, nextCursor] = await this.getFollowersFromUserId(currentUserId, cursor); - result.push(...followers); + let cursor: string | undefined = undefined; + while (true) { + const [followers, nextCursor] = await this.getFollowersFromUserId(currentUserId, cursor); + result.push(...followers); - if (!nextCursor) { - break; + if (!nextCursor) { + break; + } + + cursor = nextCursor; } - cursor = nextCursor; + return result; + } catch (e) { + if (e instanceof CombinedError) { + if (e.networkError) { + throw e.networkError; + } else if (e.graphQLErrors && e.graphQLErrors.length > 0) { + throw e.graphQLErrors[0]; + } + } else { + throw e; + } } - return result; + return []; } private async getCurrentUserId() { From d3e26d281cc12858b148f300730a49e1ba2620a3 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:16:46 +0900 Subject: [PATCH 088/140] chore: add some emojis to release note headers --- .releaserc.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.releaserc.json b/.releaserc.json index 32a0894..c5de297 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -14,25 +14,25 @@ "types": [ { "type": "feat", - "section": "Features" + "section": "Features ✨" }, { "type": "fix", - "section": "Bug Fixes" + "section": "Bug Fixes \uD83D\uDC1E" }, { "type": "chore", - "section": "Internal", + "section": "Internal \uD83E\uDDF0", "hidden": true }, { "type": "refactor", - "section": "Internal", + "section": "Internal \uD83E\uDDF0", "hidden": false }, { "type": "perf", - "section": "Internal", + "section": "Internal \uD83E\uDDF0", "hidden": false } ] From 754c706e08b24104aeb5e581f1a1e983503078d3 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:25:19 +0900 Subject: [PATCH 089/140] refactor(github): make urql client use `node-fetch` instead of built-in one made this commit just to suppress built-in `fetch` function warning but I need to replace it with better solution. --- src/watchers/github/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index 0fd236d..949321f 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -1,3 +1,4 @@ +import nodeFetch from "node-fetch"; import { Client, CombinedError, createClient } from "@urql/core"; import { BaseWatcher, PartialUserData } from "@watchers/base"; @@ -15,6 +16,7 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { this.client = createClient({ url: "https://api.github.com/graphql", + fetch: nodeFetch as unknown as typeof fetch, fetchOptions: () => { return { method: "POST", From 960082762582e8ceb1d25cad3e19b8ebb25d6e2c Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:40:14 +0900 Subject: [PATCH 090/140] fix(core): fix a bug that could not catch violating minimum value of `config.watchInterval` --- src/utils/config.const.ts | 16 +++++++++- src/utils/config.ts | 62 +++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/utils/config.const.ts b/src/utils/config.const.ts index ed851a5..92bbe21 100644 --- a/src/utils/config.const.ts +++ b/src/utils/config.const.ts @@ -1,3 +1,4 @@ +import _ from "lodash"; import Ajv from "ajv"; import { ConfigData } from "@utils/config.type"; @@ -5,7 +6,20 @@ import { ConfigData } from "@utils/config.type"; import schema from "@root/../config.schema.json"; const ajv = new Ajv({ allErrors: true }); -export const validate = ajv.compile(schema); +export const validate = ajv.compile( + _.merge(schema, { + definitions: { + ConfigData: { + type: "object", + properties: { + watchInterval: { + minimum: 60000, + }, + }, + }, + }, + }), +); export const DEFAULT_CONFIG: ConfigData = { watchInterval: 60000, diff --git a/src/utils/config.ts b/src/utils/config.ts index cb52f30..7669372 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -36,38 +36,42 @@ export class Config { Config.logger.warn(`config file ${pathToken} has been created successfully.`); } - return Config.logger.work({ - level: "info", - message: `loading config file from ${pathToken}`, - work: async () => { - const data: ConfigData = await fs.readJSON(filePath); - const valid = validate(data); - if (!valid && validate.errors) { - const output = betterAjvErrors(schema, data, validate.errors, { - format: "cli", - indent: 4, - }); - - throw new Error(`config file is invalid.\n${output}`); - } - - const watcherTypes = Object.keys(data.watchers) as WatcherTypes[]; - const watchers: WatcherPair[] = []; - const watcherMap: WatcherMap = {} as WatcherMap; - for (const type of watcherTypes) { - const configs = data.watchers[type]; - if (!configs) { - throw new Error(`watcher \`${type}\` is not configured.`); + try { + return await Config.logger.work({ + level: "info", + message: `loading config file from ${pathToken}`, + work: async () => { + const data: ConfigData = await fs.readJSON(filePath); + const valid = validate(data); + if (!valid && validate.errors) { + const output = betterAjvErrors(schema, data, validate.errors, { + format: "cli", + indent: 4, + }); + + throw new Error(`config file is invalid.\n${output}`); } - const watcher = createWatcher(configs); - watchers.push([type, watcher]); - watcherMap[type] = watcher as any; - } + const watcherTypes = Object.keys(data.watchers) as WatcherTypes[]; + const watchers: WatcherPair[] = []; + const watcherMap: WatcherMap = {} as WatcherMap; + for (const type of watcherTypes) { + const configs = data.watchers[type]; + if (!configs) { + throw new Error(`watcher \`${type}\` is not configured.`); + } + + const watcher = createWatcher(configs); + watchers.push([type, watcher]); + watcherMap[type] = watcher as any; + } - return new Config(data, watcherTypes, watchers, watcherMap); - }, - }); + return new Config(data, watcherTypes, watchers, watcherMap); + }, + }); + } catch (e) { + process.exit(-1); + } } get watcherTypes(): ReadonlyArray { From cff879d104582a749b9693e176d56970f2f1456a Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:40:24 +0900 Subject: [PATCH 091/140] chore: remove redundant dependency --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index c2fc918..47db920 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,6 @@ "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", "strip-ansi": "^6.0.1", - "ts-essentials": "^9.3.0", "twitter-api-v2": "^1.12.9", "typeorm": "^0.3.10", "update-notifier": "^5.1.0" diff --git a/yarn.lock b/yarn.lock index c39e2c4..fd5c0a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8174,11 +8174,6 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-essentials@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.3.0.tgz#7e639c1a76b1805c3c60d6e1b5178da2e70aea02" - integrity sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw== - ts-jest@^29.0.3: version "29.0.3" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77" From f21dbbab24b32a16f6e429d7d5d31b16b39ecd2f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 7 Dec 2022 15:42:19 +0000 Subject: [PATCH 092/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.6](https://github.com/async3619/cage/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2022-12-07) ### Features ✨ * **core:** implement `--database` cli option that can specify database path ([98cdcf6](https://github.com/async3619/cage/commit/98cdcf62686e98a05ecb0edd1973524cc01147f8)) * **core:** implement `--drop-database` cli option that deletes database file ([8e9adb5](https://github.com/async3619/cage/commit/8e9adb5869fc49589f50111afd9b04fd1640219c)) ### Internal 🧰 * **core:** remove saving watcher states feature ([4112e49](https://github.com/async3619/cage/commit/4112e490e751d9d591c0e614040922ed2492a5c1)) * **core:** use correct log level when no watcher configuration provided ([c491476](https://github.com/async3619/cage/commit/c491476b057b27e86532942ac0e2f561def57b03)) * **github:** make urql client use `node-fetch` instead of built-in one ([754c706](https://github.com/async3619/cage/commit/754c706e08b24104aeb5e581f1a1e983503078d3)) ### Bug Fixes 🐞 * **core:** fix a bug that application could not catch errors during collection followers ([1b3cb39](https://github.com/async3619/cage/commit/1b3cb39287d95de9d2a0c80da3e7692135dc40cc)) * **core:** fix a bug that could not catch violating minimum value of `config.watchInterval` ([9600827](https://github.com/async3619/cage/commit/960082762582e8ceb1d25cad3e19b8ebb25d6e2c)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 47db920..e9977eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.5", + "version": "1.0.0-dev.6", "license": "MIT", "publishConfig": { "access": "public" From 026f5f6f89cff96e9112d37d476d002e67062224 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:48:56 +0900 Subject: [PATCH 093/140] docs: update usage script and help text --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f0eba65..1bb4a50 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Cage is a cli application for detecting unfollowers on any social services. this ## Usage ```bash -$ npm install -g cage-cli +$ npm install -g cage-cli@dev $ cage --help @@ -41,11 +41,12 @@ Usage: cage [options] (almost) realtime unfollower detection for any social services 🦜⛓️🔒 Options: - -c, --config path to the configuration file (default: "./config.json") - -d, --drop-database delete the old database file - -v, --verbose enable verbose level logging - -V, --version output the version number - -h, --help display help for command + -c, --config path to the configuration file (default: "./config.json") + -d, --database path to the database file (default: "./data.sqlite") + -p, --drop-database delete the old database file + -v, --verbose enable verbose level logging + -V, --version output the version number + -h, --help display help for command ``` or you can just deploy with `docker` if you want: From 84a7278e367632057afbff167a4e40071178494e Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 00:54:00 +0900 Subject: [PATCH 094/140] docs: add docker hub badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1bb4a50..e64a64c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@

+ + Docker Image Version (latest by date) + npm (tag) From c712e966761b060227cce51a1991f7bf7656f97b Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 11:03:26 +0900 Subject: [PATCH 095/140] fix(twitter): remove twitter auth methods that is not working anymore --- README.md | 21 +---------- config.schema.json | 66 ++-------------------------------- src/watchers/twitter/helper.ts | 37 +++---------------- src/watchers/twitter/index.ts | 4 +-- src/watchers/twitter/types.ts | 21 +---------- 5 files changed, 12 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index e64a64c..c63ce11 100644 --- a/README.md +++ b/README.md @@ -130,26 +130,7 @@ specify watching interval in millisecond format. minimal value is `60000`. ```json5 { "type": "twitter", // required - - // one of these... - "auth": { - "type": "api-key", // required - "apiKey": "API key of your twitter app", // string, required - "apiSecret": "API secret of your twitter app" // string, required - }, - - // or ... - "auth": { - "type": "bearer-token", // required - "bearerToken": "bearer token of your twitter app" // string, required - }, - - // or ... - "auth": { - "type": "basic", // required - "username": "user id of your twitter account (e.g. @user_account)", // string, required - "password": "password of your twitter account" // string, required - } + "bearerToken": "bearer token of your twitter app" // string, required } ``` diff --git a/config.schema.json b/config.schema.json index fa510ce..4cda597 100644 --- a/config.schema.json +++ b/config.schema.json @@ -18,72 +18,12 @@ "type": "string", "const": "twitter" }, - "auth": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "api-key" - }, - "apiKey": { - "type": "string" - }, - "apiSecret": { - "type": "string" - } - }, - "required": [ - "type", - "apiKey", - "apiSecret" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "bearer-token" - }, - "bearerToken": { - "type": "string" - } - }, - "required": [ - "type", - "bearerToken" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "basic" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "required": [ - "type", - "username", - "password" - ], - "additionalProperties": false - } - ] + "bearerToken": { + "type": "string" } }, "required": [ - "auth", + "bearerToken", "type" ], "additionalProperties": false diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts index 3a4d18b..1b2db04 100644 --- a/src/watchers/twitter/helper.ts +++ b/src/watchers/twitter/helper.ts @@ -3,31 +3,16 @@ import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; import { UserData } from "@repositories/models/user"; -import { TwitterApiKeyAuth, TwitterBasicAuth, TwitterBearerTokenAuth } from "@watchers/twitter/types"; - export class TwitterHelper { - private static createClient(configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth) { + private static createClient(bearerToken: string) { const plugins: ITwitterApiClientPlugin[] = [new TwitterApiRateLimitPlugin()]; - - switch (configs.type) { - case "api-key": - return new TwitterApi({ appKey: configs.apiKey, appSecret: configs.apiSecret }, { plugins }).readOnly; - - case "basic": - return new TwitterApi({ username: configs.username, password: configs.password }, { plugins }).readOnly; - - case "bearer-token": - return new TwitterApi(configs.bearerToken, { plugins }).readOnly; - - default: - throw new Error("Invalid auth type"); - } + return new TwitterApi(bearerToken, { plugins }).readOnly; } private twitterClient: TwitterApiReadOnly; - public constructor(private readonly configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth) { - this.twitterClient = TwitterHelper.createClient(this.configs); + public constructor(private readonly bearerToken: string) { + this.twitterClient = TwitterHelper.createClient(this.bearerToken); } public async getFollowers(): Promise[]> { @@ -44,18 +29,6 @@ export class TwitterHelper { } public async initialize() { - if (this.configs.type === "api-key") { - await this.twitterClient.appLogin(); - } - } - - public getHashData() { - return this.configs; - } - public hydrate(configs: TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth): void { - this.twitterClient = TwitterHelper.createClient(configs); - } - public serialize(): TwitterApiKeyAuth | TwitterBasicAuth | TwitterBearerTokenAuth { - return this.configs; + return; } } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 3ab2a84..bb23c1b 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -9,9 +9,9 @@ import { TwitterHelper } from "@watchers/twitter/helper"; export class TwitterWatcher extends BaseWatcher<"Twitter"> { private readonly helper: TwitterHelper; - public constructor({ auth }: TwitterWatcherOptions) { + public constructor({ bearerToken }: TwitterWatcherOptions) { super("Twitter"); - this.helper = new TwitterHelper(auth); + this.helper = new TwitterHelper(bearerToken); } public async initialize() { diff --git a/src/watchers/twitter/types.ts b/src/watchers/twitter/types.ts index b2b9e80..9e0a6b8 100644 --- a/src/watchers/twitter/types.ts +++ b/src/watchers/twitter/types.ts @@ -1,24 +1,5 @@ import { BaseWatcherOptions } from "@watchers/base"; -export interface TwitterApiKeyAuth { - type: "api-key"; - apiKey: string; - apiSecret: string; -} - -export interface TwitterBearerTokenAuth { - type: "bearer-token"; - bearerToken: string; -} - -export interface TwitterBasicAuth { - type: "basic"; - username: string; - password: string; -} - -export type TwitterAuth = TwitterApiKeyAuth | TwitterBearerTokenAuth | TwitterBasicAuth; - export interface TwitterWatcherOptions extends BaseWatcherOptions<"twitter"> { - auth: TwitterAuth; + bearerToken: string; } From eab3be9c2261890b030a0390662978233eacf887 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 11:10:21 +0900 Subject: [PATCH 096/140] fix(core): fix a bug that logger does not formatting with empty string --- src/utils/logger.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index fecaeb3..d15a10d 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -89,7 +89,11 @@ export class Logger implements Record { } for (const [, token, style] of matches) { - let item = replacements.shift() || "{}"; + let item = replacements.shift(); + if (typeof item === "undefined") { + item = "{}"; + } + if (style) { style.split(",").forEach(style => { if (CHALK_FORMAT_NAMES.includes(style as any)) { From fbc278f95a207f06f3da8092fbb0227220acd235 Mon Sep 17 00:00:00 2001 From: Sophia Date: Thu, 8 Dec 2022 12:21:57 +0900 Subject: [PATCH 097/140] fix(discord): now discord notifier shows user list with correct link url --- src/app.ts | 7 ++- src/notifiers/base.ts | 7 ++- src/notifiers/discord.ts | 87 +++++++++++++++++++--------------- src/watchers/base.ts | 4 ++ src/watchers/github/index.ts | 8 +++- src/watchers/twitter/helper.ts | 34 ------------- src/watchers/twitter/index.ts | 34 +++++++------ 7 files changed, 91 insertions(+), 90 deletions(-) delete mode 100644 src/watchers/twitter/helper.ts diff --git a/src/app.ts b/src/app.ts index b48a53b..4cba4a9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -245,8 +245,13 @@ export class App extends Loggable { return; } + const watcherMap = this.config.watcherMap; for (const notifier of notifiers) { - await notifier.notify(newLogs); + await notifier.notify( + newLogs.map(log => { + return [watcherMap[log.user.from], log]; + }), + ); } }; } diff --git a/src/notifiers/base.ts b/src/notifiers/base.ts index 7d9847a..cde7f6a 100644 --- a/src/notifiers/base.ts +++ b/src/notifiers/base.ts @@ -1,6 +1,11 @@ import { UserLog } from "@repositories/models/user-log"; + +import { BaseWatcher } from "@watchers/base"; + import { Loggable } from "@utils/types"; +export type NotifyPair = [BaseWatcher, UserLog]; + export abstract class BaseNotifier extends Loggable { protected constructor(name: string) { super(name); @@ -12,5 +17,5 @@ export abstract class BaseNotifier extends Loggable { public abstract initialize(options: Record): Promise; - public abstract notify(logs: UserLog[]): Promise; + public abstract notify(logs: NotifyPair[]): Promise; } diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index 2707d5e..b9daa5f 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -1,13 +1,14 @@ import pluralize from "pluralize"; +import dayjs from "dayjs"; import { User } from "@repositories/models/user"; import { UserLog, UserLogType } from "@repositories/models/user-log"; -import { BaseNotifier } from "@notifiers/base"; +import { BaseNotifier, NotifyPair } from "@notifiers/base"; +import { BaseWatcher } from "@watchers/base"; import { Fetcher } from "@utils/fetcher"; import { Logger } from "@utils/logger"; -import dayjs from "dayjs"; export interface DiscordNotifierOptions { type: "discord"; @@ -43,12 +44,12 @@ export class DiscordNotifier extends BaseNotifier { this.webhookUrl = options.webhookUrl; } - public async notify(logs: UserLog[]) { + public async notify(pairs: NotifyPair[]) { if (!this.webhookUrl) { throw new Error("DiscordNotifier is not initialized"); } - if (logs.length <= 0) { + if (pairs.length <= 0) { return; } @@ -58,9 +59,9 @@ export class DiscordNotifier extends BaseNotifier { { title: Logger.format( "Total {} {} {} found", - logs.length, - pluralize("change", logs.length), - pluralize("was", logs.length), + pairs.length, + pluralize("change", pairs.length), + pluralize("was", pairs.length), ), color: 5814783, fields: [], @@ -74,40 +75,26 @@ export class DiscordNotifier extends BaseNotifier { attachments: [], }; - const newFields: DiscordWebhookData["embeds"][0]["fields"] = []; - const followerLogs = logs.filter(l => l.type === UserLogType.Follow); - if (followerLogs.length > 0) { - const field = this.generateEmbedField( - followerLogs, - Logger.format("🎉 {} new {}", followerLogs.length, pluralize("follower", logs.length)), - ); + const fields: DiscordWebhookData["embeds"][0]["fields"] = []; + const followerLogs = pairs.filter(([, l]) => l.type === UserLogType.Follow); + const unfollowerLogs = pairs.filter(([, l]) => l.type === UserLogType.Unfollow); + const renameLogs = pairs.filter( + ([, l]) => l.type === UserLogType.RenameUserId || l.type === UserLogType.RenameDisplayName, + ); - newFields.push(field); + if (followerLogs.length > 0) { + fields.push(this.composeLogs(followerLogs, "🎉 {} new {}", "follower")); } - const unfollowerLogs = logs.filter(l => l.type === UserLogType.Unfollow); if (unfollowerLogs.length > 0) { - const field = this.generateEmbedField( - unfollowerLogs, - Logger.format("❌ {} {}", unfollowerLogs.length, pluralize("unfollower", logs.length)), - ); - - newFields.push(field); + fields.push(this.composeLogs(unfollowerLogs, "❌ {} {}", "unfollower")); } - const renameLogs = logs.filter( - l => l.type === UserLogType.RenameUserId || l.type === UserLogType.RenameDisplayName, - ); if (renameLogs.length > 0) { - const field = this.generateEmbedField( - renameLogs, - Logger.format("✏️ {} {}", renameLogs.length, pluralize("rename", logs.length)), - ); - - newFields.push(field); + fields.push(this.composeLogs(renameLogs, "✏️ {} {}", "rename")); } - data.embeds[0].fields.push(...newFields); + data.embeds[0].fields.push(...fields); await this.fetcher.fetch({ url: this.webhookUrl, @@ -116,28 +103,52 @@ export class DiscordNotifier extends BaseNotifier { }); } - private generateEmbedField(logs: UserLog[], title: string) { + private composeLogs( + logs: NotifyPair[], + messageFormat: string, + word: string, + ): DiscordWebhookData["embeds"][0]["fields"][0] { + const { name, value } = this.generateEmbedField( + logs, + Logger.format(messageFormat, logs.length, pluralize(word, logs.length)), + ); + + const valueLines = [value]; + if (logs.length > 10) { + valueLines.push(`_... and ${logs.length - 10} more_`); + } + + return { + name, + value: valueLines.join("\n"), + }; + } + + private generateEmbedField(logs: NotifyPair[], title: string) { return { name: title, value: logs - .map<[User, UserLog]>(l => [l.user, l]) + .map<[BaseWatcher, User, UserLog]>(([w, l]) => [w, l.user, l]) .slice(0, 10) - .map(([user, log]) => { + .map(([watcher, user, log]) => { if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { return Logger.format( - "{} (@{}) → {}{}", + "[{}] [{} (@{})]({}) → {}{}", + watcher.getName(), log.oldDisplayName, log.oldUserId, + watcher.getProfileUrl(log.user), log.type === UserLogType.RenameDisplayName ? "" : "@", log.type === UserLogType.RenameUserId ? user.userId : user.displayName, ); } return Logger.format( - "[{} (@{})](https://twitter.com/{})", + "[{}] [{} (@{})]({})", + watcher.getName(), user.displayName, user.userId, - user.userId, + watcher.getProfileUrl(user), ); }) .join("\n"), diff --git a/src/watchers/base.ts b/src/watchers/base.ts index dc14e69..a348ff1 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,3 +1,4 @@ +import pluralize from "pluralize"; import { UserData } from "@repositories/models/user"; import { WatcherTypes } from "@watchers"; @@ -20,6 +21,8 @@ export abstract class BaseWatcher extends Loggable public async doWatch(): Promise { const followers = await this.getFollowers(); + this.logger.info("Successfully crawled {} {}", [followers.length, pluralize("follower", followers.length)]); + return followers.map(user => ({ ...user, from: this.getName().toLowerCase(), @@ -27,4 +30,5 @@ export abstract class BaseWatcher extends Loggable } protected abstract getFollowers(): Promise; + public abstract getProfileUrl(user: UserData): string; } diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index 949321f..5a4a367 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -31,7 +31,11 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { public async initialize() { return; } - public async getFollowers() { + public getProfileUrl(user: PartialUserData) { + return `https://github.com/${user.userId}`; + } + + protected async getFollowers() { try { const result: PartialUserData[] = []; const currentUserId = await this.getCurrentUserId(); @@ -105,7 +109,7 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { return [ data.user.followers.edges - .map(edge => { + .map(edge => { if (!edge?.node) { return null; } diff --git a/src/watchers/twitter/helper.ts b/src/watchers/twitter/helper.ts deleted file mode 100644 index 1b2db04..0000000 --- a/src/watchers/twitter/helper.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ITwitterApiClientPlugin, TwitterApi, TwitterApiReadOnly } from "twitter-api-v2"; -import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; - -import { UserData } from "@repositories/models/user"; - -export class TwitterHelper { - private static createClient(bearerToken: string) { - const plugins: ITwitterApiClientPlugin[] = [new TwitterApiRateLimitPlugin()]; - return new TwitterApi(bearerToken, { plugins }).readOnly; - } - - private twitterClient: TwitterApiReadOnly; - - public constructor(private readonly bearerToken: string) { - this.twitterClient = TwitterHelper.createClient(this.bearerToken); - } - - public async getFollowers(): Promise[]> { - const followers = await this.twitterClient.v2.followers("1569485307049033729", { - max_results: 1000, - "user.fields": ["id", "name", "username", "profile_image_url"], - }); - - return followers.data.map(user => ({ - uniqueId: user.id, - displayName: user.name, - userId: user.username, - })); - } - - public async initialize() { - return; - } -} diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index bb23c1b..6423823 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -1,30 +1,36 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import pluralize from "pluralize"; +import { TwitterApi, TwitterApiReadOnly } from "twitter-api-v2"; +import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; + +import { UserData } from "@repositories/models/user"; import { BaseWatcher } from "@watchers/base"; import { TwitterWatcherOptions } from "@watchers/twitter/types"; -import { TwitterHelper } from "@watchers/twitter/helper"; - export class TwitterWatcher extends BaseWatcher<"Twitter"> { - private readonly helper: TwitterHelper; + private readonly twitterClient: TwitterApiReadOnly; public constructor({ bearerToken }: TwitterWatcherOptions) { super("Twitter"); - this.helper = new TwitterHelper(bearerToken); + this.twitterClient = new TwitterApi(bearerToken, { plugins: [new TwitterApiRateLimitPlugin()] }).readOnly; } public async initialize() { - await this.helper.initialize(); + return; + } + public getProfileUrl(user: UserData) { + return `https://twitter.com/${user.userId}`; } - public async getFollowers() { - const allFollowers = await this.helper.getFollowers(); - this.logger.verbose("Successfully crawled {} {}", [ - allFollowers.length, - pluralize("follower", allFollowers.length), - ]); + protected async getFollowers() { + const followers = await this.twitterClient.v2.followers("1569485307049033729", { + max_results: 1000, + "user.fields": ["id", "name", "username", "profile_image_url"], + }); - return allFollowers; + return followers.data.map(user => ({ + uniqueId: user.id, + displayName: user.name, + userId: user.username, + })); } } From 5b97115f5ff0a0d2ef5ec74c0954f69cb4c9189d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 8 Dec 2022 03:25:50 +0000 Subject: [PATCH 098/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.7](https://github.com/async3619/cage/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2022-12-08) ### Bug Fixes 🐞 * **core:** fix a bug that logger does not formatting with empty string ([eab3be9](https://github.com/async3619/cage/commit/eab3be9c2261890b030a0390662978233eacf887)) * **discord:** now discord notifier shows user list with correct link url ([fbc278f](https://github.com/async3619/cage/commit/fbc278f95a207f06f3da8092fbb0227220acd235)) * **twitter:** remove twitter auth methods that is not working anymore ([c712e96](https://github.com/async3619/cage/commit/c712e966761b060227cce51a1991f7bf7656f97b)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9977eb..395a083 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.6", + "version": "1.0.0-dev.7", "license": "MIT", "publishConfig": { "access": "public" From 703d676a2db40aaa3bd75f5a107417409ac84b93 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 11:33:41 +0900 Subject: [PATCH 099/140] feat(telegram): implement telegram notifier --- README.md | 11 ++ config.schema.json | 23 +++- src/app.ts | 13 +- src/notifiers/base.ts | 35 ++++-- src/notifiers/discord.ts | 49 ++------ src/notifiers/index.ts | 29 ++++- src/notifiers/telegram/constants.ts | 23 ++++ src/notifiers/telegram/index.ts | 176 ++++++++++++++++++++++++++++ src/notifiers/type.ts | 10 ++ src/utils/config.ts | 46 +++++--- src/utils/config.type.ts | 2 +- src/utils/fetcher.ts | 3 +- src/utils/groupNotifies.ts | 9 ++ src/utils/httpError.ts | 11 ++ src/utils/types.ts | 1 - src/watchers/base.ts | 6 +- src/watchers/github/index.ts | 7 +- src/watchers/github/types.ts | 5 - src/watchers/index.ts | 9 +- src/watchers/twitter/index.ts | 7 +- src/watchers/twitter/types.ts | 5 - 21 files changed, 376 insertions(+), 104 deletions(-) create mode 100644 src/notifiers/telegram/constants.ts create mode 100644 src/notifiers/telegram/index.ts create mode 100644 src/notifiers/type.ts create mode 100644 src/utils/groupNotifies.ts create mode 100644 src/utils/httpError.ts delete mode 100644 src/watchers/github/types.ts delete mode 100644 src/watchers/twitter/types.ts diff --git a/README.md b/README.md index c63ce11..e897235 100644 --- a/README.md +++ b/README.md @@ -157,3 +157,14 @@ notifier configuration for Discord WebHook notifier. "webhookUrl": "Discord WebHook url" // string, required } ``` + +#### telegram + +notifier configuration for Telegram Bot notifier. + +```json5 +{ + "type": "telegram", + "botToken": "token that generated from https://t.me/CageNotifierBot" // string, required +} +``` diff --git a/config.schema.json b/config.schema.json index 4cda597..20cd737 100644 --- a/config.schema.json +++ b/config.schema.json @@ -67,6 +67,26 @@ "webhookUrl" ], "additionalProperties": false + }, + "telegram": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "telegram" + }, + "token": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "token", + "type" + ], + "additionalProperties": false } }, "additionalProperties": false @@ -74,7 +94,8 @@ }, "required": [ "watchInterval", - "watchers" + "watchers", + "notifiers" ], "additionalProperties": false } diff --git a/src/app.ts b/src/app.ts index 4cba4a9..4250dde 100644 --- a/src/app.ts +++ b/src/app.ts @@ -97,18 +97,11 @@ export class App extends Loggable { }); } - for (const notifier of notifiers) { - const options = this.config.notifierOptions?.[notifier.getName().toLowerCase()]; - if (!options) { - this.logger.warn(`no options are configured for \`${chalk.green("{}")}\` notifier. skipping...`, [ - notifier.getName(), - ]); - } - + for (const [, notifier] of notifiers) { await this.logger.work({ level: "info", message: `initialize \`${chalk.green(notifier.getName())}\` notifier`, - work: () => notifier.initialize(options), + work: () => notifier.initialize(), }); } @@ -246,7 +239,7 @@ export class App extends Loggable { } const watcherMap = this.config.watcherMap; - for (const notifier of notifiers) { + for (const [, notifier] of notifiers) { await notifier.notify( newLogs.map(log => { return [watcherMap[log.user.from], log]; diff --git a/src/notifiers/base.ts b/src/notifiers/base.ts index cde7f6a..4093dcf 100644 --- a/src/notifiers/base.ts +++ b/src/notifiers/base.ts @@ -1,13 +1,11 @@ -import { UserLog } from "@repositories/models/user-log"; - -import { BaseWatcher } from "@watchers/base"; +import { NotifyPair } from "@notifiers/type"; import { Loggable } from "@utils/types"; +import { UserLogType } from "@repositories/models/user-log"; +import { Logger } from "@utils/logger"; -export type NotifyPair = [BaseWatcher, UserLog]; - -export abstract class BaseNotifier extends Loggable { - protected constructor(name: string) { +export abstract class BaseNotifier extends Loggable { + protected constructor(name: TType) { super(name); } @@ -15,7 +13,28 @@ export abstract class BaseNotifier extends Loggable { return super.getName().replace("Notifier", ""); } - public abstract initialize(options: Record): Promise; + public abstract initialize(): Promise; public abstract notify(logs: NotifyPair[]): Promise; + + protected formatNotify(pair: NotifyPair): string { + const [watcher, log] = pair; + const { user } = log; + + if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { + const tokens = [ + watcher.getName(), + log.oldDisplayName || "", + log.oldUserId || "", + watcher.getProfileUrl(log.user), + log.type === UserLogType.RenameDisplayName ? "" : "@", + log.type === UserLogType.RenameUserId ? user.userId : user.displayName, + ]; + + return Logger.format("[{}] [{} (@{})]({}) → {}{}", ...tokens); + } + + const tokens = [watcher.getName(), user.displayName, user.userId, watcher.getProfileUrl(user)]; + return Logger.format("[{}] [{} (@{})]({})", ...tokens); + } } diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index b9daa5f..109eb29 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -1,20 +1,17 @@ import pluralize from "pluralize"; import dayjs from "dayjs"; -import { User } from "@repositories/models/user"; -import { UserLog, UserLogType } from "@repositories/models/user-log"; +import { UserLogType } from "@repositories/models/user-log"; -import { BaseNotifier, NotifyPair } from "@notifiers/base"; -import { BaseWatcher } from "@watchers/base"; +import { BaseNotifier } from "@notifiers/base"; +import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; import { Fetcher } from "@utils/fetcher"; import { Logger } from "@utils/logger"; -export interface DiscordNotifierOptions { - type: "discord"; +export interface DiscordNotifierOptions extends BaseNotifierOption { webhookUrl: string; } - interface DiscordWebhookData { content: any; embeds: { @@ -32,18 +29,17 @@ interface DiscordWebhookData { attachments: []; } -export class DiscordNotifier extends BaseNotifier { +export class DiscordNotifier extends BaseNotifier<"Discord"> { private readonly fetcher = new Fetcher(); private webhookUrl: string | null = null; - public constructor() { - super(DiscordNotifier.name); + public constructor(private readonly options: DiscordNotifierOptions) { + super("Discord"); } - public async initialize(options: DiscordNotifierOptions) { - this.webhookUrl = options.webhookUrl; + public async initialize() { + this.webhookUrl = this.options.webhookUrl; } - public async notify(pairs: NotifyPair[]) { if (!this.webhookUrl) { throw new Error("DiscordNotifier is not initialized"); @@ -123,35 +119,10 @@ export class DiscordNotifier extends BaseNotifier { value: valueLines.join("\n"), }; } - private generateEmbedField(logs: NotifyPair[], title: string) { return { name: title, - value: logs - .map<[BaseWatcher, User, UserLog]>(([w, l]) => [w, l.user, l]) - .slice(0, 10) - .map(([watcher, user, log]) => { - if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { - return Logger.format( - "[{}] [{} (@{})]({}) → {}{}", - watcher.getName(), - log.oldDisplayName, - log.oldUserId, - watcher.getProfileUrl(log.user), - log.type === UserLogType.RenameDisplayName ? "" : "@", - log.type === UserLogType.RenameUserId ? user.userId : user.displayName, - ); - } - - return Logger.format( - "[{}] [{} (@{})]({})", - watcher.getName(), - user.displayName, - user.userId, - watcher.getProfileUrl(user), - ); - }) - .join("\n"), + value: logs.slice(0, 10).map(this.formatNotify).join("\n"), }; } } diff --git a/src/notifiers/index.ts b/src/notifiers/index.ts index 30a571b..d76c2e3 100644 --- a/src/notifiers/index.ts +++ b/src/notifiers/index.ts @@ -1,8 +1,33 @@ import { BaseNotifier } from "@notifiers/base"; import { DiscordNotifier, DiscordNotifierOptions } from "@notifiers/discord"; +import { TelegramNotifier, TelegramNotifierOptions } from "@notifiers/telegram"; +import { BaseNotifierOption } from "@notifiers/type"; + import { TypeMap } from "@utils/types"; -export type NotifierOptions = DiscordNotifierOptions; +export type NotifierClasses = DiscordNotifier | TelegramNotifier; +export type NotifierTypes = Lowercase; + +export type NotifierOptions = DiscordNotifierOptions | TelegramNotifierOptions; export type NotifierOptionMap = TypeMap; -export const AVAILABLE_NOTIFIERS: ReadonlyArray = [new DiscordNotifier()]; +export type NotifierMap = { + [TKey in NotifierTypes]: TKey extends NotifierTypes ? Extract : never; +}; +export type NotifierFactoryMap = { + [TKey in NotifierTypes]: TKey extends NotifierTypes + ? (options: NotifierOptionMap[TKey]) => Extract }> + : never; +}; +export type NotifierPair = [NotifierTypes, BaseNotifier]; + +const AVAILABLE_NOTIFIERS: Readonly = { + discord: options => new DiscordNotifier(options), + telegram: options => new TelegramNotifier(options), +}; + +export const createNotifier = (options: BaseNotifierOption): BaseNotifier => { + const { type } = options; + + return AVAILABLE_NOTIFIERS[type](options); +}; diff --git a/src/notifiers/telegram/constants.ts b/src/notifiers/telegram/constants.ts new file mode 100644 index 0000000..6623e1a --- /dev/null +++ b/src/notifiers/telegram/constants.ts @@ -0,0 +1,23 @@ +export const TELEGRAM_FOLLOWERS_TEMPLATE = ` +*🎉 {} new {}* + +{} +{} +`.trim(); + +export const TELEGRAM_UNFOLLOWERS_TEMPLATE = ` +*❌ {} {}* + +{} +{} +`.trim(); + +export const TELEGRAM_RENAMES_TEMPLATE = ` +*✏️ {} {}* + +{} +{} +`.trim(); + +export const TELEGRAM_LOG_COUNT = 25; +export const FORBIDDEN_CHARACTERS = "()._-`*".split(""); diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts new file mode 100644 index 0000000..be87c19 --- /dev/null +++ b/src/notifiers/telegram/index.ts @@ -0,0 +1,176 @@ +import pluralize from "pluralize"; + +import { BaseNotifier } from "@notifiers/base"; +import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; +import { + FORBIDDEN_CHARACTERS, + TELEGRAM_FOLLOWERS_TEMPLATE, + TELEGRAM_LOG_COUNT, + TELEGRAM_RENAMES_TEMPLATE, + TELEGRAM_UNFOLLOWERS_TEMPLATE, +} from "@notifiers/telegram/constants"; + +import { Fetcher } from "@utils/fetcher"; +import { groupNotifies } from "@utils/groupNotifies"; +import { Logger } from "@utils/logger"; +import { HttpError } from "@utils/httpError"; +import { UserLogType } from "@repositories/models/user-log"; + +export interface TelegramNotifierOptions extends BaseNotifierOption { + token: string; + url?: string; +} + +interface TokenResponse { + token: string; + expires: number; +} +interface NotifyResponse { + success: boolean; +} + +export class TelegramNotifier extends BaseNotifier<"Telegram"> { + private readonly fetcher = new Fetcher(); + private currentToken: string | null = null; + + public constructor(private readonly options: TelegramNotifierOptions) { + super("Telegram"); + } + + public async initialize(): Promise { + this.currentToken = await this.acquireToken(); + } + public async notify(logs: NotifyPair[]): Promise { + const { + follow, + unfollow, + "rename-user-id": renameUserId, + "rename-display-name": renameDisplayName, + } = groupNotifies(logs); + + const targets: [NotifyPair[], number, string, string][] = []; + if (follow) { + targets.push([follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "follower", TELEGRAM_FOLLOWERS_TEMPLATE]); + } + + if (unfollow) { + targets.push([ + unfollow.slice(0, TELEGRAM_LOG_COUNT), + unfollow.length, + "unfollower", + TELEGRAM_UNFOLLOWERS_TEMPLATE, + ]); + } + + if (renameUserId || renameDisplayName) { + const renames = [...(renameUserId || []).slice(0, 100), ...(renameDisplayName || []).slice(0, 100)]; + + targets.push([renames.slice(0, TELEGRAM_LOG_COUNT), renames.length, "rename", TELEGRAM_RENAMES_TEMPLATE]); + } + + const resultMessages: string[] = []; + for (const [target, count, word, template] of targets) { + if (target.length <= 0) { + continue; + } + + const message = Logger.format( + template, + count, + pluralize(word, count), + target.map(this.formatNotify).join("\n"), + count > TELEGRAM_LOG_COUNT ? `_\\.\\.\\. and ${count - TELEGRAM_LOG_COUNT} more_` : "", + ).trim(); + + resultMessages.push(message); + } + + await this.pushNotify(["*\\[🦜 Cage Report\\]*", ...resultMessages]); + } + + private getEndpoint(path: string) { + const base = this.options.url || "https://cage-telegram.sophia-dev.io"; + return `${base}${path}`; + } + + private async acquireToken() { + const { token } = await this.fetcher.fetchJson({ + url: this.getEndpoint("/token"), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + retryCount: 5, + retryDelay: 0, + }); + + return token; + } + private async pushNotify(content: string[]) { + while (true) { + try { + await this.fetcher.fetchJson({ + url: this.getEndpoint("/notify"), + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.currentToken}`, + }, + data: { + token: this.options.token, + content, + }, + retryCount: 5, + retryDelay: 0, + }); + + return; + } catch (e) { + if (e instanceof HttpError && e.statusCode === 403) { + this.currentToken = await this.acquireToken(); + continue; + } + + throw e; + } + } + } + + protected escapeTexts(texts: string[]) { + return texts.map(text => this.escapeText(text)); + } + protected escapeText(text: string) { + for (const ch of FORBIDDEN_CHARACTERS) { + text = text.replace(new RegExp(`\\${ch}`, "g"), `\\${ch}`); + } + + return text; + } + + protected formatNotify = (pair: NotifyPair): string => { + const [watcher, log] = pair; + const { user } = log; + + if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { + const tokens = this.escapeTexts([ + watcher.getName(), + log.oldDisplayName || "", + log.oldUserId || "", + watcher.getProfileUrl(log.user), + log.type === UserLogType.RenameDisplayName ? "" : "@", + log.type === UserLogType.RenameUserId ? user.userId : user.displayName, + ]); + + return Logger.format("\\[{}\\] [{} (@{})]\\({}\\) → {}{}", ...tokens); + } + + const tokens = this.escapeTexts([ + watcher.getName(), + user.displayName, + user.userId, + watcher.getProfileUrl(user), + ]); + + return Logger.format("\\[{}\\] [{} \\(@{}\\)]({})", ...tokens); + }; +} diff --git a/src/notifiers/type.ts b/src/notifiers/type.ts new file mode 100644 index 0000000..303b137 --- /dev/null +++ b/src/notifiers/type.ts @@ -0,0 +1,10 @@ +import { BaseWatcher } from "@watchers/base"; +import { BaseNotifier } from "@notifiers/base"; + +import { UserLog } from "@repositories/models/user-log"; + +export interface BaseNotifierOption { + type: TNotifier extends BaseNotifier ? Lowercase : string; +} + +export type NotifyPair = [BaseWatcher, UserLog]; diff --git a/src/utils/config.ts b/src/utils/config.ts index 7669372..665333e 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,11 +1,10 @@ -import _ from "lodash"; import * as path from "path"; import * as fs from "fs-extra"; import chalk from "chalk"; import betterAjvErrors from "better-ajv-errors"; import { createWatcher, WatcherMap, WatcherPair, WatcherTypes } from "@watchers"; -import { AVAILABLE_NOTIFIERS } from "@notifiers"; +import { createNotifier, NotifierMap, NotifierPair, NotifierTypes } from "@notifiers"; import { DEFAULT_CONFIG, validate } from "@utils/config.const"; import { ConfigData } from "@utils/config.type"; @@ -54,7 +53,7 @@ export class Config { const watcherTypes = Object.keys(data.watchers) as WatcherTypes[]; const watchers: WatcherPair[] = []; - const watcherMap: WatcherMap = {} as WatcherMap; + const watcherMap: Partial = {}; for (const type of watcherTypes) { const configs = data.watchers[type]; if (!configs) { @@ -66,7 +65,21 @@ export class Config { watcherMap[type] = watcher as any; } - return new Config(data, watcherTypes, watchers, watcherMap); + const notifierTypes = Object.keys(data.notifiers) as NotifierTypes[]; + const notifiers: NotifierPair[] = []; + const notifierMap: Partial = {}; + for (const type of notifierTypes) { + const configs = data.notifiers[type]; + if (!configs) { + throw new Error(`notifier \`${type}\` is not configured.`); + } + + const notifier = createNotifier(configs); + notifiers.push([type, notifier]); + notifierMap[type] = notifier as any; + } + + return new Config(data, watcherTypes, watchers, watcherMap, notifierTypes, notifiers, notifierMap); }, }); } catch (e) { @@ -77,25 +90,21 @@ export class Config { get watcherTypes(): ReadonlyArray { return [...this._watcherTypes]; } - get watchers(): ReadonlyArray { return [...this._watchers]; } - - get watcherMap(): WatcherMap { + get watcherMap(): Partial { return { ...this._watcherMap }; } - public get notifiers() { - if (!this.rawData.notifiers) { - return []; - } - - const names = Object.keys(this.rawData.notifiers); - return AVAILABLE_NOTIFIERS.filter(n => names.includes(n.getName().replace("Notifier", "").toLowerCase())); + get notifierTypes(): ReadonlyArray { + return [...this._notifierTypes]; + } + get notifiers(): ReadonlyArray { + return [...this._notifiers]; } - public get notifierOptions() { - return _.cloneDeep(this.rawData.notifiers); + get notifierMap(): Partial { + return { ...this._notifierMap }; } public get watchInterval() { @@ -106,6 +115,9 @@ export class Config { private readonly rawData: ConfigData, private readonly _watcherTypes: ReadonlyArray, private readonly _watchers: ReadonlyArray, - private readonly _watcherMap: WatcherMap, + private readonly _watcherMap: Partial, + private readonly _notifierTypes: NotifierTypes[], + private readonly _notifiers: NotifierPair[], + private readonly _notifierMap: Partial, ) {} } diff --git a/src/utils/config.type.ts b/src/utils/config.type.ts index d996090..5e5868b 100644 --- a/src/utils/config.type.ts +++ b/src/utils/config.type.ts @@ -4,5 +4,5 @@ import { NotifierOptionMap } from "@notifiers"; export interface ConfigData { watchInterval: number; watchers: Partial; - notifiers?: Partial; + notifiers: Partial; } diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index 1fde050..7e88c0a 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -5,6 +5,7 @@ import { Logger } from "@utils/logger"; import { parseCookie } from "@utils/parseCookie"; import { buildQueryString } from "@utils/buildQueryString"; import { Hydratable, Serializable } from "@utils/types"; +import { HttpError } from "@utils/httpError"; interface FetchOption { url: string; @@ -75,7 +76,7 @@ export class Fetcher implements Serializable, Hydratable { }); if (!response.ok) { - throw new Error(`${response.status} (${response.statusText})`); + throw new HttpError(response.status, response.statusText); } this.setCookies(response.headers.get("set-cookie")); diff --git a/src/utils/groupNotifies.ts b/src/utils/groupNotifies.ts new file mode 100644 index 0000000..2921a44 --- /dev/null +++ b/src/utils/groupNotifies.ts @@ -0,0 +1,9 @@ +import _ from "lodash"; + +import { UserLogType } from "@repositories/models/user-log"; + +import { NotifyPair } from "@notifiers/type"; + +export function groupNotifies(pairs: NotifyPair[]) { + return _.groupBy(pairs, ([, log]) => log.type) as unknown as Partial>; +} diff --git a/src/utils/httpError.ts b/src/utils/httpError.ts new file mode 100644 index 0000000..7113658 --- /dev/null +++ b/src/utils/httpError.ts @@ -0,0 +1,11 @@ +export class HttpError extends Error { + public readonly statusCode: number; + public readonly statusMessage: string; + + public constructor(statusCode: number, statusMessage: string) { + super(`HTTP Error ${statusCode}: ${statusMessage}`); + + this.statusCode = statusCode; + this.statusMessage = statusMessage; + } +} diff --git a/src/utils/types.ts b/src/utils/types.ts index ebe3701..488d359 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,6 +1,5 @@ import { Logger } from "@utils/logger"; -export type Shift = ((...t: T) => any) extends (first: any, ...rest: infer Rest) => any ? Rest : never; export type TypeMap = { [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; }; diff --git a/src/watchers/base.ts b/src/watchers/base.ts index a348ff1..2566d8b 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,12 +1,10 @@ import pluralize from "pluralize"; import { UserData } from "@repositories/models/user"; -import { WatcherTypes } from "@watchers"; - import { Loggable } from "@utils/types"; -export interface BaseWatcherOptions { - type: TType; +export interface BaseWatcherOptions { + type: TWatcher extends BaseWatcher ? Lowercase : string; } export type PartialUserData = Omit; diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index 5a4a367..a33d4d4 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -1,13 +1,16 @@ import nodeFetch from "node-fetch"; import { Client, CombinedError, createClient } from "@urql/core"; -import { BaseWatcher, PartialUserData } from "@watchers/base"; +import { BaseWatcher, BaseWatcherOptions, PartialUserData } from "@watchers/base"; import { FollowersDocument, MeDocument } from "@watchers/github/queries"; -import { GitHubWatcherOptions } from "@watchers/github/types"; import { FollowersQuery, FollowersQueryVariables, MeQuery } from "@root/queries.data"; import { Nullable } from "@utils/types"; +export interface GitHubWatcherOptions extends BaseWatcherOptions { + authToken: string; +} + export class GitHubWatcher extends BaseWatcher<"GitHub"> { private client: Client; diff --git a/src/watchers/github/types.ts b/src/watchers/github/types.ts deleted file mode 100644 index 06b76bd..0000000 --- a/src/watchers/github/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseWatcherOptions } from "@watchers/base"; - -export interface GitHubWatcherOptions extends BaseWatcherOptions<"github"> { - authToken: string; -} diff --git a/src/watchers/index.ts b/src/watchers/index.ts index 6a3745a..b5dc97d 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,8 +1,5 @@ -import { TwitterWatcher } from "@watchers/twitter"; -import { TwitterWatcherOptions } from "@watchers/twitter/types"; - -import { GitHubWatcher } from "@watchers/github"; -import { GitHubWatcherOptions } from "@watchers/github/types"; +import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; +import { GitHubWatcher, GitHubWatcherOptions } from "@watchers/github"; import { TypeMap } from "@utils/types"; import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; @@ -30,7 +27,7 @@ const AVAILABLE_WATCHERS: Readonly = { github: options => new GitHubWatcher(options), }; -export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { +export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { const { type } = options; return AVAILABLE_WATCHERS[type](options); diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 6423823..9702f3c 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -3,8 +3,11 @@ import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; import { UserData } from "@repositories/models/user"; -import { BaseWatcher } from "@watchers/base"; -import { TwitterWatcherOptions } from "@watchers/twitter/types"; +import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; + +export interface TwitterWatcherOptions extends BaseWatcherOptions { + bearerToken: string; +} export class TwitterWatcher extends BaseWatcher<"Twitter"> { private readonly twitterClient: TwitterApiReadOnly; diff --git a/src/watchers/twitter/types.ts b/src/watchers/twitter/types.ts deleted file mode 100644 index 9e0a6b8..0000000 --- a/src/watchers/twitter/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseWatcherOptions } from "@watchers/base"; - -export interface TwitterWatcherOptions extends BaseWatcherOptions<"twitter"> { - bearerToken: string; -} From e9a888ce917d3cafa07a8353bada4cf159764de7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 12:03:52 +0900 Subject: [PATCH 100/140] refactor(watcher): simplify watcher related type codes --- src/watchers/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/watchers/index.ts b/src/watchers/index.ts index b5dc97d..1805e02 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,8 +1,8 @@ +import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; import { GitHubWatcher, GitHubWatcherOptions } from "@watchers/github"; import { TypeMap } from "@utils/types"; -import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; export type WatcherClasses = TwitterWatcher | GitHubWatcher; export type WatcherTypes = Lowercase; @@ -11,14 +11,12 @@ export type WatcherOptions = TwitterWatcherOptions | GitHubWatcherOptions; export type WatcherOptionMap = TypeMap; export type WatcherMap = { - [TKey in WatcherClasses["name"] as Lowercase]: TKey extends WatcherClasses["name"] - ? Extract - : never; + [TKey in WatcherClasses["name"] as Lowercase]: Extract>; }; export type WatcherFactoryMap = { - [TKey in WatcherClasses["name"] as Lowercase]: TKey extends WatcherClasses["name"] - ? (options: WatcherOptionMap[Lowercase]) => Extract - : never; + [TKey in WatcherClasses["name"] as Lowercase]: ( + options: Extract>>, + ) => Extract>; }; export type WatcherPair = [WatcherTypes, BaseWatcher]; From b3565dcb81ac3f1eaa9bd1e2a9bb616b98b53137 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 13:35:15 +0900 Subject: [PATCH 101/140] ci: remove redundant ci pipeline step --- .github/workflows/ci.yml | 10 ---------- .github/workflows/docker-deploy.yml | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36be3ca..8a8608f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,16 +15,6 @@ jobs: with: persist-credentials: false - - name: Cache nextjs build - uses: actions/cache@v2 - with: - path: ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]sx?') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}- - - name: Cache node_modules id: node-cache uses: actions/cache@v2 diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 092c516..53c6dff 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -14,16 +14,6 @@ jobs: with: persist-credentials: false - - name: Cache nextjs build - uses: actions/cache@v2 - with: - path: ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]sx?') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}- - - name: Cache node_modules id: node-cache uses: actions/cache@v2 From f12c5115c4fcb706850c6de47ea1b39b829a3c34 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 15:34:34 +0900 Subject: [PATCH 102/140] fix(telegram): fix a bug that telegram notifier sending notification with no changes --- src/notifiers/telegram/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index be87c19..801b962 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -85,7 +85,9 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { resultMessages.push(message); } - await this.pushNotify(["*\\[🦜 Cage Report\\]*", ...resultMessages]); + if (resultMessages.length > 0) { + await this.pushNotify(["*\\[🦜 Cage Report\\]*", ...resultMessages]); + } } private getEndpoint(path: string) { From f79b418084158fa006a510b34109b7d3f9251696 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 15:57:16 +0900 Subject: [PATCH 103/140] refactor: print logo at startup --- src/index.ts | 5 +++++ src/utils/cli.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/utils/cli.ts diff --git a/src/index.ts b/src/index.ts index bdaea8c..50e65c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,9 @@ import { Command } from "commander"; import boxen from "boxen"; import { App } from "@root/app"; + import { Logger } from "@utils/logger"; +import { drawLine, printLogo } from "@utils/cli"; import packageJson from "../package.json"; @@ -33,6 +35,9 @@ interface CLIOptions { distTag: packageJson.version.includes("dev") ? "dev" : "latest", }).fetchInfo(); + const logoWidth = printLogo(latest, current); + drawLine(logoWidth); + if (latest !== current) { const contents = Logger.format( "{white}", diff --git a/src/utils/cli.ts b/src/utils/cli.ts new file mode 100644 index 0000000..f13d1cc --- /dev/null +++ b/src/utils/cli.ts @@ -0,0 +1,26 @@ +import chalk from "chalk"; +import stripAnsi from "strip-ansi"; + +export function printLogo(latestVersion: string, currentVersion: any) { + const isOutdated = latestVersion !== currentVersion; + const formatFunction = !isOutdated ? chalk.green : chalk.yellow; + const versionInfo = chalk.italic(formatFunction(`v${currentVersion} ${isOutdated ? "(outdated)" : ""}`)); + + const content = ` _________ _____ ____ + / ___/ __ \`/ __ \`/ _ \\ +/ /__/ /_/ / /_/ / __/ +\\___/\\__,_/\\__, /\\___/ + /____/ ${versionInfo}`; + + console.log(chalk.cyan(content)); + + return Math.max( + ...stripAnsi(content) + .split("\n") + .map(l => l.length), + ); +} + +export function drawLine(width = 45) { + console.log("=".repeat(width)); +} From 85bc8ca6df7db3287e14982bfb042007d29ced52 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 16:01:18 +0900 Subject: [PATCH 104/140] docs: add description for telegram notifier configuration --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e897235..5565171 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,9 @@ notifier configuration for Telegram Bot notifier. ```json5 { "type": "telegram", - "botToken": "token that generated from https://t.me/CageNotifierBot" // string, required + "botToken": "token that generated from https://t.me/CageNotifierBot", // string, required + "url": "custom notification relay server url" // string, optional } ``` + +In most cases, you will not need `url` property since since Cage already provides hosted version of this server out of the box. but if you want to use custom notification relay server, take a look [this repoitory](https://github.com/async3619/cage-telegram-helper#usage). From d97979af24f810bbffc6bdf267b440e69ca4acad Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 16:03:09 +0900 Subject: [PATCH 105/140] test: correct expected error message to newly formatted error message --- src/utils/fetcher.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/fetcher.spec.ts b/src/utils/fetcher.spec.ts index 7caf74c..c7772ab 100644 --- a/src/utils/fetcher.spec.ts +++ b/src/utils/fetcher.spec.ts @@ -167,7 +167,7 @@ describe("Fetcher class", function () { }); await expect(target.fetch({ url: "", retryCount: 3, retryDelay: 0 })).rejects.toThrow( - "500 (Internal Server Error)", + "HTTP Error 500: Internal Server Error", ); expect(calledCount).toBe(4); }); @@ -189,7 +189,7 @@ describe("Fetcher class", function () { const [, elapsedTime] = await throttle( expect(target.fetch({ url: "", retryCount: 3, retryDelay: 500 })).rejects.toThrow( - "500 (Internal Server Error)", + "HTTP Error 500: Internal Server Error", ), 0, true, From 0a7404f024f31a8e37503dd8473582ef13fd0a2e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 11 Dec 2022 07:04:30 +0000 Subject: [PATCH 106/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.8](https://github.com/async3619/cage/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2022-12-11) ### Features ✨ * **telegram:** implement telegram notifier ([703d676](https://github.com/async3619/cage/commit/703d676a2db40aaa3bd75f5a107417409ac84b93)) ### Bug Fixes 🐞 * **telegram:** fix a bug that telegram notifier sending notification with no changes ([f12c511](https://github.com/async3619/cage/commit/f12c5115c4fcb706850c6de47ea1b39b829a3c34)) ### Internal 🧰 * print logo at startup ([f79b418](https://github.com/async3619/cage/commit/f79b418084158fa006a510b34109b7d3f9251696)) * **watcher:** simplify watcher related type codes ([e9a888c](https://github.com/async3619/cage/commit/e9a888ce917d3cafa07a8353bada4cf159764de7)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 395a083..b645246 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.7", + "version": "1.0.0-dev.8", "license": "MIT", "publishConfig": { "access": "public" From 7b4c7f1a258ef85fbf05d3f24fb077b487fbb12d Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 17:05:19 +0900 Subject: [PATCH 107/140] feat(slack): implement slack notifier --- README.md | 15 +++++- config.schema.json | 17 +++++++ package.json | 1 + src/notifiers/index.ts | 6 ++- src/notifiers/slack.ts | 79 +++++++++++++++++++++++++++++ src/notifiers/telegram/index.ts | 32 +++--------- src/repositories/models/user-log.ts | 1 + src/utils/groupNotifies.ts | 15 +++++- yarn.lock | 31 +++++++++++ 9 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 src/notifiers/slack.ts diff --git a/README.md b/README.md index 5565171..d46d18c 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ When we detect unfollowers, new followers, or any other events, Cage will notify | Service | Support? | |-----------------|:--------:| | Discord WebHook | ✅ | -| Slack WebHook | ❌ | -| Telegram Bot | ❌ | +| Telegram Bot | ✅ | +| Slack WebHook | ✅ | | Email | ❌ | | SMS | ❌ | @@ -171,3 +171,14 @@ notifier configuration for Telegram Bot notifier. ``` In most cases, you will not need `url` property since since Cage already provides hosted version of this server out of the box. but if you want to use custom notification relay server, take a look [this repoitory](https://github.com/async3619/cage-telegram-helper#usage). + +#### slack + +notifier configuration for Slack WebHook notifier. + +```json5 +{ + "type": "slack", + "webhookUrl": "Slack WebHook url" // string, required +} +``` diff --git a/config.schema.json b/config.schema.json index 20cd737..cff68ca 100644 --- a/config.schema.json +++ b/config.schema.json @@ -87,6 +87,23 @@ "type" ], "additionalProperties": false + }, + "slack": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "slack" + }, + "webhookUrl": { + "type": "string" + } + }, + "required": [ + "type", + "webhookUrl" + ], + "additionalProperties": false } }, "additionalProperties": false diff --git a/package.json b/package.json index b645246..fe35685 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ }, "dependencies": { "@octokit/rest": "^19.0.5", + "@slack/webhook": "^6.1.0", "@twitter-api-v2/plugin-rate-limit": "^1.1.0", "@urql/core": "^3.0.5", "ajv": "^8.11.2", diff --git a/src/notifiers/index.ts b/src/notifiers/index.ts index d76c2e3..176f68c 100644 --- a/src/notifiers/index.ts +++ b/src/notifiers/index.ts @@ -1,14 +1,15 @@ import { BaseNotifier } from "@notifiers/base"; import { DiscordNotifier, DiscordNotifierOptions } from "@notifiers/discord"; import { TelegramNotifier, TelegramNotifierOptions } from "@notifiers/telegram"; +import { SlackNotifier, SlackNotifierOptions } from "@notifiers/slack"; import { BaseNotifierOption } from "@notifiers/type"; import { TypeMap } from "@utils/types"; -export type NotifierClasses = DiscordNotifier | TelegramNotifier; +export type NotifierClasses = DiscordNotifier | TelegramNotifier | SlackNotifier; export type NotifierTypes = Lowercase; -export type NotifierOptions = DiscordNotifierOptions | TelegramNotifierOptions; +export type NotifierOptions = DiscordNotifierOptions | TelegramNotifierOptions | SlackNotifierOptions; export type NotifierOptionMap = TypeMap; export type NotifierMap = { @@ -24,6 +25,7 @@ export type NotifierPair = [NotifierTypes, BaseNotifier]; const AVAILABLE_NOTIFIERS: Readonly = { discord: options => new DiscordNotifier(options), telegram: options => new TelegramNotifier(options), + slack: options => new SlackNotifier(options), }; export const createNotifier = (options: BaseNotifierOption): BaseNotifier => { diff --git a/src/notifiers/slack.ts b/src/notifiers/slack.ts new file mode 100644 index 0000000..8a566eb --- /dev/null +++ b/src/notifiers/slack.ts @@ -0,0 +1,79 @@ +import { IncomingWebhook, IncomingWebhookSendArguments } from "@slack/webhook"; + +import { BaseNotifier } from "@notifiers/base"; +import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; + +import { groupNotifies } from "@utils/groupNotifies"; +import { Logger } from "@utils/logger"; + +export interface SlackNotifierOptions extends BaseNotifierOption { + webhookUrl: string; +} + +const MAX_ITEMS_PER_MESSAGE = 25; + +export class SlackNotifier extends BaseNotifier<"Slack"> { + private readonly webhook: IncomingWebhook; + + public constructor(private readonly options: SlackNotifierOptions) { + super("Slack"); + + this.webhook = new IncomingWebhook(this.options.webhookUrl); + } + + public async initialize(): Promise { + return; + } + public async notify(logs: NotifyPair[]): Promise { + const { follow, unfollow, rename } = groupNotifies(logs); + const targets: [NotifyPair[], number, string, string][] = [ + [follow.slice(0, MAX_ITEMS_PER_MESSAGE), follow.length, "🎉 {} new {}", "follower"], + [unfollow.slice(0, MAX_ITEMS_PER_MESSAGE), unfollow.length, "❌ {} {}", "unfollower"], + [rename.slice(0, MAX_ITEMS_PER_MESSAGE), rename.length, "✏️ {} {}", "rename"], + ]; + + const result: IncomingWebhookSendArguments = { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: "_*🦜 Cage Report*_", + }, + }, + ], + }; + + for (const [logs, count, template, word] of targets) { + if (logs.length <= 0) { + continue; + } + + const title = Logger.format(template, count, word); + const userContents = logs.map(this.formatNotify).join("\n"); + let moreText = ""; + if (count > MAX_ITEMS_PER_MESSAGE) { + moreText = Logger.format("... and {} more", count - MAX_ITEMS_PER_MESSAGE); + } + + const content = `*${title}*\n${userContents}\n${moreText}`.trim(); + if (!result.blocks) { + continue; + } + + result.blocks.push({ + type: "section", + text: { + type: "mrkdwn", + text: content, + }, + }); + } + + await this.webhook.send(result); + } + + protected formatNotify(pair: NotifyPair): string { + return super.formatNotify(pair).replace(/ \[(.*? \(@.*?\))\]\((.*?)\)/g, "<$2|$1>"); + } +} diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index 801b962..728adeb 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -41,32 +41,12 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { this.currentToken = await this.acquireToken(); } public async notify(logs: NotifyPair[]): Promise { - const { - follow, - unfollow, - "rename-user-id": renameUserId, - "rename-display-name": renameDisplayName, - } = groupNotifies(logs); - - const targets: [NotifyPair[], number, string, string][] = []; - if (follow) { - targets.push([follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "follower", TELEGRAM_FOLLOWERS_TEMPLATE]); - } - - if (unfollow) { - targets.push([ - unfollow.slice(0, TELEGRAM_LOG_COUNT), - unfollow.length, - "unfollower", - TELEGRAM_UNFOLLOWERS_TEMPLATE, - ]); - } - - if (renameUserId || renameDisplayName) { - const renames = [...(renameUserId || []).slice(0, 100), ...(renameDisplayName || []).slice(0, 100)]; - - targets.push([renames.slice(0, TELEGRAM_LOG_COUNT), renames.length, "rename", TELEGRAM_RENAMES_TEMPLATE]); - } + const { follow, unfollow, rename } = groupNotifies(logs); + const targets: [NotifyPair[], number, string, string][] = [ + [follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "follower", TELEGRAM_FOLLOWERS_TEMPLATE], + [unfollow.slice(0, TELEGRAM_LOG_COUNT), unfollow.length, "unfollower", TELEGRAM_UNFOLLOWERS_TEMPLATE], + [rename.slice(0, TELEGRAM_LOG_COUNT), rename.length, "rename", TELEGRAM_RENAMES_TEMPLATE], + ]; const resultMessages: string[] = []; for (const [target, count, word, template] of targets) { diff --git a/src/repositories/models/user-log.ts b/src/repositories/models/user-log.ts index bd73aef..76f43bc 100644 --- a/src/repositories/models/user-log.ts +++ b/src/repositories/models/user-log.ts @@ -17,6 +17,7 @@ export enum UserLogType { Unfollow = "unfollow", RenameDisplayName = "rename-display-name", RenameUserId = "rename-user-id", + Rename = "rename", } @Entity({ name: "user-logs" }) diff --git a/src/utils/groupNotifies.ts b/src/utils/groupNotifies.ts index 2921a44..ef38eea 100644 --- a/src/utils/groupNotifies.ts +++ b/src/utils/groupNotifies.ts @@ -4,6 +4,17 @@ import { UserLogType } from "@repositories/models/user-log"; import { NotifyPair } from "@notifiers/type"; -export function groupNotifies(pairs: NotifyPair[]) { - return _.groupBy(pairs, ([, log]) => log.type) as unknown as Partial>; +type MapKeys = Exclude; + +export function groupNotifies(pairs: NotifyPair[]): Record { + const result = _.groupBy(pairs, ([, log]) => log.type); + + return { + [UserLogType.Follow]: result[UserLogType.Follow] || [], + [UserLogType.Unfollow]: result[UserLogType.Unfollow] || [], + [UserLogType.Rename]: [ + ...(result[UserLogType.RenameDisplayName] || []), + ...(result[UserLogType.RenameUserId] || []), + ], + }; } diff --git a/yarn.lock b/yarn.lock index fd5c0a3..844bdd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1974,6 +1974,20 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@slack/types@^1.2.1": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a" + integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg== + +"@slack/webhook@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@slack/webhook/-/webhook-6.1.0.tgz#ac9c8add919f0f5ab9440a24e76b2f83adcc0f2c" + integrity sha512-7AYNISyAjn/lA/VDwZ307K5ft5DojXgBd3DRrGoFN8XxIwIyRALdFhxBiMgAqeJH8eWoktvNwLK24R9hREEqpA== + dependencies: + "@slack/types" "^1.2.1" + "@types/node" ">=12.0.0" + axios "^0.21.4" + "@sqltools/formatter@^1.2.2": version "1.2.5" resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" @@ -2170,6 +2184,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.3.tgz#ce750ab4017effa51aed6a7230651778d54e327c" integrity sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w== +"@types/node@>=12.0.0": + version "18.11.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.13.tgz#dff34f226ec1ac0432ae3b136ec5552bd3b9c0fe" + integrity sha512-IASpMGVcWpUsx5xBOrxMj7Bl8lqfuTY7FKAnPmu5cHkfQVWF8GulWS1jbRqA934qZL35xh5xN/+Xe/i26Bod4w== + "@types/node@^18.11.10": version "18.11.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34" @@ -2637,6 +2656,13 @@ auto-bind@~4.0.0: resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== +axios@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + babel-jest@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" @@ -4194,6 +4220,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +follow-redirects@^1.14.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + form-data-encoder@^1.7.1: version "1.7.2" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" From a8fd508e9cacd68ea3b822fd7801aa5037da73b2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 17:06:01 +0900 Subject: [PATCH 108/140] docs: fix typo --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d46d18c..75e2a8c 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ When we detect unfollowers, new followers, or any other events, Cage will notify | Service | Support? | |-----------------|:--------:| -| Discord WebHook | ✅ | +| Discord Webhook | ✅ | | Telegram Bot | ✅ | -| Slack WebHook | ✅ | +| Slack Webhook | ✅ | | Email | ❌ | | SMS | ❌ | @@ -149,12 +149,12 @@ watcher configuration for GitHub service. #### discord -notifier configuration for Discord WebHook notifier. +notifier configuration for Discord Webhook notifier. ```json5 { "type": "discord", - "webhookUrl": "Discord WebHook url" // string, required + "webhookUrl": "Discord Webhook url" // string, required } ``` @@ -174,11 +174,11 @@ In most cases, you will not need `url` property since since Cage already provide #### slack -notifier configuration for Slack WebHook notifier. +notifier configuration for Slack Webhook notifier. ```json5 { "type": "slack", - "webhookUrl": "Slack WebHook url" // string, required + "webhookUrl": "Slack Webhook url" // string, required } ``` From 00f246412a5f5c137a9abdff4d55029f6c22e4ba Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 17:40:24 +0900 Subject: [PATCH 109/140] refactor(telegram): make telegram notification title message having more rich data --- package.json | 1 + src/notifiers/telegram/constants.ts | 2 +- src/notifiers/telegram/index.ts | 15 +++++++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fe35685..663de68 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "better-ajv-errors": "^1.2.0", "boxen": "^5.1.2", "chalk": "^4.1.2", + "change-case": "^4.1.2", "commander": "^9.4.1", "conventional-changelog-conventionalcommits": "^5.0.0", "cronstrue": "^2.20.0", diff --git a/src/notifiers/telegram/constants.ts b/src/notifiers/telegram/constants.ts index 6623e1a..305bed0 100644 --- a/src/notifiers/telegram/constants.ts +++ b/src/notifiers/telegram/constants.ts @@ -1,5 +1,5 @@ export const TELEGRAM_FOLLOWERS_TEMPLATE = ` -*🎉 {} new {}* +*🎉 {} {}* {} {} diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index 728adeb..74c51d8 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -1,7 +1,9 @@ import pluralize from "pluralize"; +import { capitalCase } from "change-case"; + +import { UserLogType } from "@repositories/models/user-log"; import { BaseNotifier } from "@notifiers/base"; -import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; import { FORBIDDEN_CHARACTERS, TELEGRAM_FOLLOWERS_TEMPLATE, @@ -9,12 +11,12 @@ import { TELEGRAM_RENAMES_TEMPLATE, TELEGRAM_UNFOLLOWERS_TEMPLATE, } from "@notifiers/telegram/constants"; +import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; import { Fetcher } from "@utils/fetcher"; import { groupNotifies } from "@utils/groupNotifies"; import { Logger } from "@utils/logger"; import { HttpError } from "@utils/httpError"; -import { UserLogType } from "@repositories/models/user-log"; export interface TelegramNotifierOptions extends BaseNotifierOption { token: string; @@ -43,7 +45,7 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { public async notify(logs: NotifyPair[]): Promise { const { follow, unfollow, rename } = groupNotifies(logs); const targets: [NotifyPair[], number, string, string][] = [ - [follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "follower", TELEGRAM_FOLLOWERS_TEMPLATE], + [follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "new follower", TELEGRAM_FOLLOWERS_TEMPLATE], [unfollow.slice(0, TELEGRAM_LOG_COUNT), unfollow.length, "unfollower", TELEGRAM_UNFOLLOWERS_TEMPLATE], [rename.slice(0, TELEGRAM_LOG_COUNT), rename.length, "rename", TELEGRAM_RENAMES_TEMPLATE], ]; @@ -66,7 +68,12 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { } if (resultMessages.length > 0) { - await this.pushNotify(["*\\[🦜 Cage Report\\]*", ...resultMessages]); + const titleItems = targets.map(([, count, word]) => { + return count > 0 ? `${count} ${pluralize(capitalCase(word), count)}\n` : ""; + }); + const title = Logger.format("*🦜 Cage Report*\n\n{}{}{}", ...titleItems).trim(); + + await this.pushNotify([title, ...resultMessages]); } } From c99f9a1acb5b8e18eda009b2899f118a5b57cec3 Mon Sep 17 00:00:00 2001 From: Sophia Date: Sun, 11 Dec 2022 17:47:24 +0900 Subject: [PATCH 110/140] fix(slack): fix a bug that slack notifier sending notification with no changes --- src/notifiers/slack.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/notifiers/slack.ts b/src/notifiers/slack.ts index 8a566eb..81ad62c 100644 --- a/src/notifiers/slack.ts +++ b/src/notifiers/slack.ts @@ -44,11 +44,14 @@ export class SlackNotifier extends BaseNotifier<"Slack"> { ], }; + let shouldNotify = false; for (const [logs, count, template, word] of targets) { if (logs.length <= 0) { continue; } + shouldNotify = true; + const title = Logger.format(template, count, word); const userContents = logs.map(this.formatNotify).join("\n"); let moreText = ""; @@ -70,7 +73,9 @@ export class SlackNotifier extends BaseNotifier<"Slack"> { }); } - await this.webhook.send(result); + if (shouldNotify) { + await this.webhook.send(result); + } } protected formatNotify(pair: NotifyPair): string { From c4177dd18a434252859a58ef367fc7bceaa8d37e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 11 Dec 2022 09:40:17 +0000 Subject: [PATCH 111/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.9](https://github.com/async3619/cage/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2022-12-11) ### Features ✨ * **slack:** implement slack notifier ([7b4c7f1](https://github.com/async3619/cage/commit/7b4c7f1a258ef85fbf05d3f24fb077b487fbb12d)) ### Internal 🧰 * **telegram:** make telegram notification title message having more rich data ([00f2464](https://github.com/async3619/cage/commit/00f246412a5f5c137a9abdff4d55029f6c22e4ba)) ### Bug Fixes 🐞 * **slack:** fix a bug that slack notifier sending notification with no changes ([c99f9a1](https://github.com/async3619/cage/commit/c99f9a1acb5b8e18eda009b2899f118a5b57cec3)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 663de68..dae6e5b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.8", + "version": "1.0.0-dev.9", "license": "MIT", "publishConfig": { "access": "public" From c2863c4a9624e2e4c16fb4597f0a7f48e74aac9e Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 12 Dec 2022 09:07:38 +0900 Subject: [PATCH 112/140] fix(telegram): now telegram notifier not escape for markdown formatting --- src/notifiers/telegram/constants.ts | 1 - src/notifiers/telegram/index.ts | 45 ++--------------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/src/notifiers/telegram/constants.ts b/src/notifiers/telegram/constants.ts index 305bed0..4596e99 100644 --- a/src/notifiers/telegram/constants.ts +++ b/src/notifiers/telegram/constants.ts @@ -20,4 +20,3 @@ export const TELEGRAM_RENAMES_TEMPLATE = ` `.trim(); export const TELEGRAM_LOG_COUNT = 25; -export const FORBIDDEN_CHARACTERS = "()._-`*".split(""); diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index 74c51d8..8da2237 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -1,11 +1,8 @@ import pluralize from "pluralize"; import { capitalCase } from "change-case"; -import { UserLogType } from "@repositories/models/user-log"; - import { BaseNotifier } from "@notifiers/base"; import { - FORBIDDEN_CHARACTERS, TELEGRAM_FOLLOWERS_TEMPLATE, TELEGRAM_LOG_COUNT, TELEGRAM_RENAMES_TEMPLATE, @@ -61,7 +58,7 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { count, pluralize(word, count), target.map(this.formatNotify).join("\n"), - count > TELEGRAM_LOG_COUNT ? `_\\.\\.\\. and ${count - TELEGRAM_LOG_COUNT} more_` : "", + count > TELEGRAM_LOG_COUNT ? `_... and ${count - TELEGRAM_LOG_COUNT} more_` : "", ).trim(); resultMessages.push(message); @@ -71,7 +68,7 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { const titleItems = targets.map(([, count, word]) => { return count > 0 ? `${count} ${pluralize(capitalCase(word), count)}\n` : ""; }); - const title = Logger.format("*🦜 Cage Report*\n\n{}{}{}", ...titleItems).trim(); + const title = Logger.format("_**🦜 Cage Report**_\n\n{}{}{}", ...titleItems).trim(); await this.pushNotify([title, ...resultMessages]); } @@ -124,42 +121,4 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { } } } - - protected escapeTexts(texts: string[]) { - return texts.map(text => this.escapeText(text)); - } - protected escapeText(text: string) { - for (const ch of FORBIDDEN_CHARACTERS) { - text = text.replace(new RegExp(`\\${ch}`, "g"), `\\${ch}`); - } - - return text; - } - - protected formatNotify = (pair: NotifyPair): string => { - const [watcher, log] = pair; - const { user } = log; - - if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { - const tokens = this.escapeTexts([ - watcher.getName(), - log.oldDisplayName || "", - log.oldUserId || "", - watcher.getProfileUrl(log.user), - log.type === UserLogType.RenameDisplayName ? "" : "@", - log.type === UserLogType.RenameUserId ? user.userId : user.displayName, - ]); - - return Logger.format("\\[{}\\] [{} (@{})]\\({}\\) → {}{}", ...tokens); - } - - const tokens = this.escapeTexts([ - watcher.getName(), - user.displayName, - user.userId, - watcher.getProfileUrl(user), - ]); - - return Logger.format("\\[{}\\] [{} \\(@{}\\)]({})", ...tokens); - }; } From b9b49fed0a58e506dcc4b8cc582712c3b12ffefa Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 12 Dec 2022 23:14:40 +0900 Subject: [PATCH 113/140] refactor(telegram): now notifier pushes data with new format instead of plain text --- src/notifiers/telegram/constants.ts | 3 -- src/notifiers/telegram/index.ts | 56 ++++++++++------------------- src/notifiers/telegram/types.ts | 28 +++++++++++++++ src/notifiers/telegram/utils.ts | 40 +++++++++++++++++++++ src/utils/types.ts | 5 ++- 5 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/notifiers/telegram/types.ts create mode 100644 src/notifiers/telegram/utils.ts diff --git a/src/notifiers/telegram/constants.ts b/src/notifiers/telegram/constants.ts index 4596e99..8833580 100644 --- a/src/notifiers/telegram/constants.ts +++ b/src/notifiers/telegram/constants.ts @@ -4,19 +4,16 @@ export const TELEGRAM_FOLLOWERS_TEMPLATE = ` {} {} `.trim(); - export const TELEGRAM_UNFOLLOWERS_TEMPLATE = ` *❌ {} {}* {} {} `.trim(); - export const TELEGRAM_RENAMES_TEMPLATE = ` *✏️ {} {}* {} {} `.trim(); - export const TELEGRAM_LOG_COUNT = 25; diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index 8da2237..e04a584 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -1,33 +1,21 @@ import pluralize from "pluralize"; -import { capitalCase } from "change-case"; import { BaseNotifier } from "@notifiers/base"; -import { - TELEGRAM_FOLLOWERS_TEMPLATE, - TELEGRAM_LOG_COUNT, - TELEGRAM_RENAMES_TEMPLATE, - TELEGRAM_UNFOLLOWERS_TEMPLATE, -} from "@notifiers/telegram/constants"; import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; +import { TELEGRAM_LOG_COUNT } from "@notifiers/telegram/constants"; +import { NotifyResponse, TelegramNotificationData, TokenResponse } from "@notifiers/telegram/types"; import { Fetcher } from "@utils/fetcher"; import { groupNotifies } from "@utils/groupNotifies"; import { Logger } from "@utils/logger"; import { HttpError } from "@utils/httpError"; +import { generateNotificationTargets } from "@notifiers/telegram/utils"; export interface TelegramNotifierOptions extends BaseNotifierOption { token: string; url?: string; } -interface TokenResponse { - token: string; - expires: number; -} -interface NotifyResponse { - success: boolean; -} - export class TelegramNotifier extends BaseNotifier<"Telegram"> { private readonly fetcher = new Fetcher(); private currentToken: string | null = null; @@ -41,37 +29,29 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { } public async notify(logs: NotifyPair[]): Promise { const { follow, unfollow, rename } = groupNotifies(logs); - const targets: [NotifyPair[], number, string, string][] = [ - [follow.slice(0, TELEGRAM_LOG_COUNT), follow.length, "new follower", TELEGRAM_FOLLOWERS_TEMPLATE], - [unfollow.slice(0, TELEGRAM_LOG_COUNT), unfollow.length, "unfollower", TELEGRAM_UNFOLLOWERS_TEMPLATE], - [rename.slice(0, TELEGRAM_LOG_COUNT), rename.length, "rename", TELEGRAM_RENAMES_TEMPLATE], - ]; + const targets = generateNotificationTargets({ + unfollowers: unfollow, + followers: follow, + renames: rename, + }); - const resultMessages: string[] = []; - for (const [target, count, word, template] of targets) { - if (target.length <= 0) { + const notifyData: Partial = {}; + for (const { fieldName, countFieldName, count, word, template, pairs } of targets) { + if (count <= 0) { continue; } - const message = Logger.format( + notifyData[countFieldName] = count; + notifyData[fieldName] = Logger.format( template, count, pluralize(word, count), - target.map(this.formatNotify).join("\n"), + pairs.map(this.formatNotify).join("\n"), count > TELEGRAM_LOG_COUNT ? `_... and ${count - TELEGRAM_LOG_COUNT} more_` : "", ).trim(); - - resultMessages.push(message); } - if (resultMessages.length > 0) { - const titleItems = targets.map(([, count, word]) => { - return count > 0 ? `${count} ${pluralize(capitalCase(word), count)}\n` : ""; - }); - const title = Logger.format("_**🦜 Cage Report**_\n\n{}{}{}", ...titleItems).trim(); - - await this.pushNotify([title, ...resultMessages]); - } + await this.pushNotify(notifyData); } private getEndpoint(path: string) { @@ -92,7 +72,9 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { return token; } - private async pushNotify(content: string[]) { + private async pushNotify(content: TelegramNotificationData) { + console.log(content); + while (true) { try { await this.fetcher.fetchJson({ @@ -104,7 +86,7 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { }, data: { token: this.options.token, - content, + ...content, }, retryCount: 5, retryDelay: 0, diff --git a/src/notifiers/telegram/types.ts b/src/notifiers/telegram/types.ts new file mode 100644 index 0000000..b3f3db3 --- /dev/null +++ b/src/notifiers/telegram/types.ts @@ -0,0 +1,28 @@ +import { SelectOnly } from "@utils/types"; +import { NotifyPair } from "@notifiers/type"; + +export interface TelegramNotificationData { + followers?: string; + unfollowers?: string; + renames?: string; + followerCount?: number; + unfollowerCount?: number; + renameCount?: number; +} + +export interface TokenResponse { + token: string; + expires: number; +} +export interface NotifyResponse { + success: boolean; +} + +export interface NotificationTarget { + fieldName: keyof SelectOnly; + countFieldName: keyof SelectOnly; + word: string; + template: string; + count: number; + pairs: NotifyPair[]; +} diff --git a/src/notifiers/telegram/utils.ts b/src/notifiers/telegram/utils.ts new file mode 100644 index 0000000..cf461ee --- /dev/null +++ b/src/notifiers/telegram/utils.ts @@ -0,0 +1,40 @@ +import { SelectOnly } from "@utils/types"; +import { NotificationTarget, TelegramNotificationData } from "@notifiers/telegram/types"; +import { NotifyPair } from "@notifiers/type"; +import { + TELEGRAM_FOLLOWERS_TEMPLATE, + TELEGRAM_LOG_COUNT, + TELEGRAM_RENAMES_TEMPLATE, + TELEGRAM_UNFOLLOWERS_TEMPLATE, +} from "@notifiers/telegram/constants"; + +export function generateNotificationTargets( + pairs: Record, NotifyPair[]>, +): NotificationTarget[] { + return [ + { + fieldName: "followers", + countFieldName: "followerCount", + word: "new follower", + template: TELEGRAM_FOLLOWERS_TEMPLATE, + pairs: pairs.followers.slice(0, TELEGRAM_LOG_COUNT), + count: pairs.followers.length, + }, + { + fieldName: "unfollowers", + countFieldName: "unfollowerCount", + word: "unfollower", + template: TELEGRAM_UNFOLLOWERS_TEMPLATE, + pairs: pairs.unfollowers.slice(0, TELEGRAM_LOG_COUNT), + count: pairs.unfollowers.length, + }, + { + fieldName: "renames", + countFieldName: "renameCount", + word: "rename", + template: TELEGRAM_RENAMES_TEMPLATE, + pairs: pairs.renames.slice(0, TELEGRAM_LOG_COUNT), + count: pairs.renames.length, + }, + ]; +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 488d359..4992fa6 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,9 @@ import { Logger } from "@utils/logger"; +export type SelectOnly = { + [Key in keyof Required as Required[Key] extends Type ? Key : never]: Record[Key]; +}; + export type TypeMap = { [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; }; @@ -18,7 +22,6 @@ export type Nullable = T | null | undefined; export interface Serializable { serialize(): Record; } - export interface Hydratable { hydrate(data: Record): void; } From 6c3c9943ebb83a3597cc44a090d8d7ce9929c260 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 12 Dec 2022 23:15:30 +0900 Subject: [PATCH 114/140] refactor: make minimum interval be 10s only at development mode --- src/utils/config.const.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/config.const.ts b/src/utils/config.const.ts index 92bbe21..d44367b 100644 --- a/src/utils/config.const.ts +++ b/src/utils/config.const.ts @@ -13,7 +13,7 @@ export const validate = ajv.compile( type: "object", properties: { watchInterval: { - minimum: 60000, + minimum: process.env.NODE_ENV === "development" ? 10000 : 60000, }, }, }, From 5bb7bfafb4e5ff83218d1f1ab0c07f47d9692c70 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 12 Dec 2022 14:23:05 +0000 Subject: [PATCH 115/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.10](https://github.com/async3619/cage/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-12-12) ### Bug Fixes 🐞 * **telegram:** now telegram notifier not escape for markdown formatting ([c2863c4](https://github.com/async3619/cage/commit/c2863c4a9624e2e4c16fb4597f0a7f48e74aac9e)) ### Internal 🧰 * make minimum interval be 10s only at development mode ([6c3c994](https://github.com/async3619/cage/commit/6c3c9943ebb83a3597cc44a090d8d7ce9929c260)) * **telegram:** now notifier pushes data with new format instead of plain text ([b9b49fe](https://github.com/async3619/cage/commit/b9b49fed0a58e506dcc4b8cc582712c3b12ffefa)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dae6e5b..fec68c9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.9", + "version": "1.0.0-dev.10", "license": "MIT", "publishConfig": { "access": "public" From 5f310a745490970185e1bf5c854dbbb8cbdc47a0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 12 Dec 2022 23:36:37 +0900 Subject: [PATCH 116/140] refactor: remove redundant logging --- src/notifiers/telegram/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index e04a584..0037382 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -73,8 +73,6 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { return token; } private async pushNotify(content: TelegramNotificationData) { - console.log(content); - while (true) { try { await this.fetcher.fetchJson({ From 40bfdf50cfb5eeef2cd51145766b946abb44cab2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 12 Dec 2022 23:51:08 +0900 Subject: [PATCH 117/140] fix(core): fix bug that app calling notifiers without any logs --- src/app.ts | 4 ++++ src/notifiers/discord.ts | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.ts b/src/app.ts index 4250dde..57e9279 100644 --- a/src/app.ts +++ b/src/app.ts @@ -238,6 +238,10 @@ export class App extends Loggable { return; } + if (newLogs.length <= 0) { + return; + } + const watcherMap = this.config.watcherMap; for (const [, notifier] of notifiers) { await notifier.notify( diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index 109eb29..af9d932 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -45,10 +45,6 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { throw new Error("DiscordNotifier is not initialized"); } - if (pairs.length <= 0) { - return; - } - const data: DiscordWebhookData = { content: null, embeds: [ From 3e91e036cf59a77e1bf89b71c867ae8568cb3dd3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 12 Dec 2022 14:52:31 +0000 Subject: [PATCH 118/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.11](https://github.com/async3619/cage/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-12-12) ### Internal 🧰 * remove redundant logging ([5f310a7](https://github.com/async3619/cage/commit/5f310a745490970185e1bf5c854dbbb8cbdc47a0)) ### Bug Fixes 🐞 * **core:** fix bug that app calling notifiers without any logs ([40bfdf5](https://github.com/async3619/cage/commit/40bfdf50cfb5eeef2cd51145766b946abb44cab2)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fec68c9..1c544ed 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.10", + "version": "1.0.0-dev.11", "license": "MIT", "publishConfig": { "access": "public" From 3dc63b98e916c0617a0544eca2aaa6aa46c84a9f Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 14 Dec 2022 00:13:24 +0900 Subject: [PATCH 119/140] fix(github): fix bug that github api client returns cached result --- src/watchers/github/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index a33d4d4..dfa8154 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -20,6 +20,7 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { this.client = createClient({ url: "https://api.github.com/graphql", fetch: nodeFetch as unknown as typeof fetch, + requestPolicy: "network-only", fetchOptions: () => { return { method: "POST", From 7e77507675790fb54069df2e1b033e27e36d9f52 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 13 Dec 2022 15:14:50 +0000 Subject: [PATCH 120/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.12](https://github.com/async3619/cage/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-12-13) ### Bug Fixes 🐞 * **github:** fix bug that github api client returns cached result ([3dc63b9](https://github.com/async3619/cage/commit/3dc63b98e916c0617a0544eca2aaa6aa46c84a9f)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c544ed..80b7f95 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.11", + "version": "1.0.0-dev.12", "license": "MIT", "publishConfig": { "access": "public" From 6b01a31fa15c8942bbc20f754597a5aa90e4d6c0 Mon Sep 17 00:00:00 2001 From: Sophia Date: Fri, 16 Dec 2022 19:24:10 +0900 Subject: [PATCH 121/140] feat(core): implement specific user ignoring feature --- config.schema.json | 6 ++++++ src/app.ts | 13 +++++++++++++ src/utils/config.ts | 3 +++ src/utils/config.type.ts | 1 + 4 files changed, 23 insertions(+) diff --git a/config.schema.json b/config.schema.json index cff68ca..ebf790c 100644 --- a/config.schema.json +++ b/config.schema.json @@ -107,6 +107,12 @@ } }, "additionalProperties": false + }, + "ignores": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ diff --git a/src/app.ts b/src/app.ts index 57e9279..4b2d758 100644 --- a/src/app.ts +++ b/src/app.ts @@ -238,6 +238,19 @@ export class App extends Loggable { return; } + const ignoredIds = this.config.ignores; + if (ignoredIds.length) { + this.logger.info("ignoring {}", [pluralize("user", ignoredIds.length, true)]); + + for (const log of newLogs) { + if (!ignoredIds.includes(log.user.id)) { + continue; + } + + newLogs.splice(newLogs.indexOf(log), 1); + } + } + if (newLogs.length <= 0) { return; } diff --git a/src/utils/config.ts b/src/utils/config.ts index 665333e..66bac8b 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -107,6 +107,9 @@ export class Config { return { ...this._notifierMap }; } + public get ignores(): ReadonlyArray { + return [...(this.rawData.ignores || [])]; + } public get watchInterval() { return this.rawData.watchInterval; } diff --git a/src/utils/config.type.ts b/src/utils/config.type.ts index 5e5868b..b7e74c6 100644 --- a/src/utils/config.type.ts +++ b/src/utils/config.type.ts @@ -5,4 +5,5 @@ export interface ConfigData { watchInterval: number; watchers: Partial; notifiers: Partial; + ignores?: string[]; } From a62870a6e71b66a9c5b9553aa6eb0a4766eec196 Mon Sep 17 00:00:00 2001 From: Sophia Date: Fri, 16 Dec 2022 19:24:27 +0900 Subject: [PATCH 122/140] refactor(discord): remove redundant log count checking logic --- src/notifiers/discord.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index af9d932..109eb29 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -45,6 +45,10 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { throw new Error("DiscordNotifier is not initialized"); } + if (pairs.length <= 0) { + return; + } + const data: DiscordWebhookData = { content: null, embeds: [ From cdce1c60d33c0165ad264530cbb8829d2f589b65 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Dec 2022 10:26:13 +0000 Subject: [PATCH 123/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.13](https://github.com/async3619/cage/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-12-16) ### Features ✨ * **core:** implement specific user ignoring feature ([6b01a31](https://github.com/async3619/cage/commit/6b01a31fa15c8942bbc20f754597a5aa90e4d6c0)) ### Internal 🧰 * **discord:** remove redundant log count checking logic ([a62870a](https://github.com/async3619/cage/commit/a62870a6e71b66a9c5b9553aa6eb0a4766eec196)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80b7f95..b57a1b5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.12", + "version": "1.0.0-dev.13", "license": "MIT", "publishConfig": { "access": "public" From 5e53aa9b631ae439a7050886e9630aa55ea416b2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 20 Dec 2022 03:12:20 +0900 Subject: [PATCH 124/140] fix(twitter): now twitter watcher uses actual user id instead of hardcoded one --- config.schema.json | 6 +++++- src/watchers/twitter/index.ts | 23 ++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/config.schema.json b/config.schema.json index ebf790c..742e577 100644 --- a/config.schema.json +++ b/config.schema.json @@ -20,11 +20,15 @@ }, "bearerToken": { "type": "string" + }, + "username": { + "type": "string" } }, "required": [ "bearerToken", - "type" + "type", + "username" ], "additionalProperties": false }, diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index 9702f3c..cf2b839 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -7,25 +7,42 @@ import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; export interface TwitterWatcherOptions extends BaseWatcherOptions { bearerToken: string; + username: string; } export class TwitterWatcher extends BaseWatcher<"Twitter"> { private readonly twitterClient: TwitterApiReadOnly; + private readonly currentUserName: string; + private currentUserId: string | null = null; - public constructor({ bearerToken }: TwitterWatcherOptions) { + public constructor({ bearerToken, username }: TwitterWatcherOptions) { super("Twitter"); this.twitterClient = new TwitterApi(bearerToken, { plugins: [new TwitterApiRateLimitPlugin()] }).readOnly; + this.currentUserName = username; } public async initialize() { - return; + const { data } = await this.twitterClient.v2.userByUsername(this.currentUserName, { + "user.fields": ["id"], + }); + + if (!data) { + throw new Error("Failed to get user id"); + } + + this.logger.verbose("Successfully initialized with user id {}", [data.id]); + this.currentUserId = data.id; } public getProfileUrl(user: UserData) { return `https://twitter.com/${user.userId}`; } protected async getFollowers() { - const followers = await this.twitterClient.v2.followers("1569485307049033729", { + if (!this.currentUserId) { + throw new Error("Watcher is not initialized"); + } + + const followers = await this.twitterClient.v2.followers(this.currentUserId, { max_results: 1000, "user.fields": ["id", "name", "username", "profile_image_url"], }); From 53507b3352fd2eed27c017af74621328e0a3e8db Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 20 Dec 2022 16:51:34 +0900 Subject: [PATCH 125/140] refactor(core): make app-overfocused business logics separated into individual tasks --- jest.config.ts | 2 +- src/app.ts | 138 ++++------------------------ src/notifiers/base.ts | 27 ++++-- src/notifiers/discord.ts | 34 +++---- src/notifiers/slack.ts | 24 ++--- src/notifiers/telegram/constants.ts | 24 ++--- src/notifiers/telegram/index.ts | 59 +++++++----- src/notifiers/telegram/types.ts | 12 --- src/notifiers/telegram/utils.ts | 40 -------- src/notifiers/type.ts | 6 +- src/repositories/base.ts | 46 ---------- src/repositories/models/user.ts | 5 +- src/repositories/user-log.ts | 40 +------- src/repositories/user.ts | 24 +---- src/tasks/base.spec.ts | 52 +++++++++++ src/tasks/base.ts | 101 ++++++++++++++++++++ src/tasks/grab-user.spec.ts | 49 ++++++++++ src/tasks/grab-user.ts | 23 +++++ src/tasks/index.spec.ts | 8 ++ src/tasks/index.ts | 22 +++++ src/tasks/new-follower.spec.ts | 29 ++++++ src/tasks/new-follower.ts | 21 +++++ src/tasks/notify.spec.ts | 43 +++++++++ src/tasks/notify.ts | 21 +++++ src/tasks/rename.spec.ts | 78 ++++++++++++++++ src/tasks/rename.ts | 41 +++++++++ src/tasks/save.spec.ts | 38 ++++++++ src/tasks/save.ts | 19 ++++ src/tasks/unfollower.spec.ts | 27 ++++++ src/tasks/unfollower.ts | 23 +++++ src/utils/generateRandomString.ts | 8 -- src/utils/getTimestamp.ts | 3 - src/utils/groupNotifies.ts | 12 +-- src/utils/index.ts | 18 ++++ src/utils/isRequired.ts | 3 + src/utils/logger.ts | 2 +- src/utils/types.ts | 1 - src/watchers/base.ts | 26 ++++-- src/watchers/github/index.ts | 60 ++++-------- src/watchers/twitter/index.ts | 10 +- tsconfig.json | 5 +- 41 files changed, 777 insertions(+), 447 deletions(-) delete mode 100644 src/notifiers/telegram/utils.ts create mode 100644 src/tasks/base.spec.ts create mode 100644 src/tasks/base.ts create mode 100644 src/tasks/grab-user.spec.ts create mode 100644 src/tasks/grab-user.ts create mode 100644 src/tasks/index.spec.ts create mode 100644 src/tasks/index.ts create mode 100644 src/tasks/new-follower.spec.ts create mode 100644 src/tasks/new-follower.ts create mode 100644 src/tasks/notify.spec.ts create mode 100644 src/tasks/notify.ts create mode 100644 src/tasks/rename.spec.ts create mode 100644 src/tasks/rename.ts create mode 100644 src/tasks/save.spec.ts create mode 100644 src/tasks/save.ts create mode 100644 src/tasks/unfollower.spec.ts create mode 100644 src/tasks/unfollower.ts delete mode 100644 src/utils/generateRandomString.ts delete mode 100644 src/utils/getTimestamp.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/isRequired.ts diff --git a/jest.config.ts b/jest.config.ts index b1728a4..f9a5b4e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,7 +9,7 @@ const jestConfig: JestConfigWithTsJest = { transform: { "^.+\\.(t|j)s$": "ts-jest", }, - collectCoverageFrom: ["**/*.ts", "!coverage/**", "!utils/noop.ts", "!index.ts", "!app.ts"], + collectCoverageFrom: ["**/*.ts", "!coverage/**", "!utils/noop.ts", "!index.ts", "!app.ts", "!**/models/*.ts"], coverageDirectory: "../coverage", testEnvironment: "node", roots: [""], diff --git a/src/app.ts b/src/app.ts index 4b2d758..b105105 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,25 +5,18 @@ import chalk from "chalk"; import prettyMilliseconds from "pretty-ms"; import pluralize from "pluralize"; -import { UserRepository } from "@repositories/user"; -import { UserLogRepository } from "@repositories/user-log"; +import { UserRepository, User } from "@repositories/user"; +import { UserLogRepository, UserLog } from "@repositories/user-log"; -import { User, UserData } from "@repositories/models/user"; -import { UserLog, UserLogType } from "@root/repositories/models/user-log"; +import { DEFAULT_TASKS, TaskData } from "@tasks"; -import { Config } from "@utils/config"; -import { throttle } from "@utils/throttle"; -import { mapBy } from "@utils/mapBy"; -import { measureTime } from "@utils/measureTime"; -import { sleep } from "@utils/sleep"; -import { Logger } from "@utils/logger"; -import { Loggable } from "@utils/types"; -import { getDiff } from "@utils/getDiff"; +import { Config, throttle, measureTime, sleep, Logger, Loggable } from "@utils"; export class App extends Loggable { private readonly followerDataSource: DataSource; private readonly userRepository: UserRepository; private readonly userLogRepository: UserLogRepository; + private readonly taskClasses = DEFAULT_TASKS; private cleaningUp = false; private config: Config | null = null; @@ -71,8 +64,7 @@ export class App extends Loggable { this.config = await Config.create(this.configFilePath); if (!this.config) { - process.exit(-1); - return; + throw new Error("config is not loaded."); } const watchers = this.config.watchers; @@ -154,114 +146,22 @@ export class App extends Loggable { throw new Error("Config is not loaded."); } - const startedDate = new Date(); - const allUserData: UserData[] = []; - for (const [, watcher] of this.config.watchers) { - try { - const userData = await watcher.doWatch(); - - allUserData.push(...userData); - } catch (e) { - if (!(e instanceof Error)) { - throw e; - } - - this.logger.error("an error occurred while watching through `{green}`: {}", [ - watcher.getName(), - e.message, - ]); - - process.exit(-1); - } - } - - this.logger.info(`all {} {} collected.`, [allUserData.length, pluralize("follower", allUserData.length)]); - - const followingMap = await this.userLogRepository.getFollowStatusMap(); - const oldUsers = await this.userRepository.find(); - const oldUsersMap = mapBy(oldUsers, "id"); - - const newUsers = await this.userRepository.createFromData(allUserData, startedDate); - const newUserMap = mapBy(newUsers, "id"); - - // find user renaming their displayName or userId - const displayNameRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "displayName"); - const userIdRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "userId"); - - // find users who are not followed yet. - const newFollowers = newUsers.filter(p => !followingMap[p.id]); - const unfollowers = oldUsers.filter(p => !newUserMap[p.id] && followingMap[p.id]); - - const renameLogs: UserLog[] = []; - if (displayNameRenamedUsers.length || userIdRenamedUsers.length) { - const displayName = displayNameRenamedUsers.map(p => { - const oldUser = oldUsersMap[p.id]; - const userLog = this.userLogRepository.create(); - userLog.type = UserLogType.RenameDisplayName; - userLog.user = p; - userLog.oldDisplayName = oldUser?.displayName; - userLog.oldUserId = oldUser?.userId; - - return userLog; - }); - - const userId = userIdRenamedUsers.map(p => { - const oldUser = oldUsersMap[p.id]; - const userLog = this.userLogRepository.create(); - userLog.type = UserLogType.RenameUserId; - userLog.user = p; - userLog.oldDisplayName = oldUser?.displayName; - userLog.oldUserId = oldUser?.userId; - - return userLog; - }); - - renameLogs.push(...displayName, ...userId); - } - - const newLogs = await this.userLogRepository.batchWriteLogs( - [ - [newFollowers, UserLogType.Follow], - [unfollowers, UserLogType.Unfollow], - ], - renameLogs, - ); - - this.logger.info(`all {} {} saved`, [newLogs.length, pluralize("log", newLogs.length)]); - - this.logger.info("tracked {} new {}", [newFollowers.length, pluralize("follower", newFollowers.length)]); - this.logger.info("tracked {} {}", [unfollowers.length, pluralize("unfollower", unfollowers.length)]); - - const notifiers = this.config.notifiers; - if (!notifiers.length) { - this.logger.info("no notifiers are configured. skipping notification..."); - return; - } - - const ignoredIds = this.config.ignores; - if (ignoredIds.length) { - this.logger.info("ignoring {}", [pluralize("user", ignoredIds.length, true)]); - - for (const log of newLogs) { - if (!ignoredIds.includes(log.user.id)) { - continue; - } + const taskData: TaskData[] = []; + for (const TaskClass of this.taskClasses) { + const taskInstance = new TaskClass( + this.config.watchers.map(([, watcher]) => watcher), + this.config.notifiers.map(([, notifier]) => notifier), + this.userRepository, + this.userLogRepository, + TaskClass.name, + ); - newLogs.splice(newLogs.indexOf(log), 1); + const data = await taskInstance.doWork(taskData); + if (data.type === "terminate") { + return; } - } - if (newLogs.length <= 0) { - return; - } - - const watcherMap = this.config.watcherMap; - for (const [, notifier] of notifiers) { - await notifier.notify( - newLogs.map(log => { - return [watcherMap[log.user.from], log]; - }), - ); + taskData.push(data); } }; } diff --git a/src/notifiers/base.ts b/src/notifiers/base.ts index 4093dcf..4eb3fe6 100644 --- a/src/notifiers/base.ts +++ b/src/notifiers/base.ts @@ -1,8 +1,11 @@ -import { NotifyPair } from "@notifiers/type"; +import { capitalCase } from "change-case"; + +import { UserLog, UserLogType } from "@repositories/models/user-log"; + +import { UserLogMap } from "@notifiers/type"; -import { Loggable } from "@utils/types"; -import { UserLogType } from "@repositories/models/user-log"; import { Logger } from "@utils/logger"; +import { Loggable } from "@utils/types"; export abstract class BaseNotifier extends Loggable { protected constructor(name: TType) { @@ -15,18 +18,17 @@ export abstract class BaseNotifier extends Loggable public abstract initialize(): Promise; - public abstract notify(logs: NotifyPair[]): Promise; + public abstract notify(logs: UserLog[], logMap: UserLogMap): Promise; - protected formatNotify(pair: NotifyPair): string { - const [watcher, log] = pair; + protected formatNotify(log: UserLog): string { const { user } = log; if (log.type === UserLogType.RenameUserId || log.type === UserLogType.RenameDisplayName) { const tokens = [ - watcher.getName(), + capitalCase(user.from), log.oldDisplayName || "", log.oldUserId || "", - watcher.getProfileUrl(log.user), + user.profileUrl, log.type === UserLogType.RenameDisplayName ? "" : "@", log.type === UserLogType.RenameUserId ? user.userId : user.displayName, ]; @@ -34,7 +36,12 @@ export abstract class BaseNotifier extends Loggable return Logger.format("[{}] [{} (@{})]({}) → {}{}", ...tokens); } - const tokens = [watcher.getName(), user.displayName, user.userId, watcher.getProfileUrl(user)]; - return Logger.format("[{}] [{} (@{})]({})", ...tokens); + return Logger.format( + "[{}] [{} (@{})]({})", + capitalCase(user.from), + user.displayName, + user.userId, + user.profileUrl, + ); } } diff --git a/src/notifiers/discord.ts b/src/notifiers/discord.ts index 109eb29..db8e650 100644 --- a/src/notifiers/discord.ts +++ b/src/notifiers/discord.ts @@ -1,10 +1,10 @@ import pluralize from "pluralize"; import dayjs from "dayjs"; -import { UserLogType } from "@repositories/models/user-log"; +import { UserLog, UserLogType } from "@repositories/models/user-log"; import { BaseNotifier } from "@notifiers/base"; -import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; +import { BaseNotifierOption, UserLogMap } from "@notifiers/type"; import { Fetcher } from "@utils/fetcher"; import { Logger } from "@utils/logger"; @@ -40,12 +40,12 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { public async initialize() { this.webhookUrl = this.options.webhookUrl; } - public async notify(pairs: NotifyPair[]) { + public async notify(logs: UserLog[], logMap: UserLogMap) { if (!this.webhookUrl) { throw new Error("DiscordNotifier is not initialized"); } - if (pairs.length <= 0) { + if (logs.length <= 0) { return; } @@ -55,9 +55,9 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { { title: Logger.format( "Total {} {} {} found", - pairs.length, - pluralize("change", pairs.length), - pluralize("was", pairs.length), + logs.length, + pluralize("change", logs.length), + pluralize("was", logs.length), ), color: 5814783, fields: [], @@ -72,12 +72,9 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { }; const fields: DiscordWebhookData["embeds"][0]["fields"] = []; - const followerLogs = pairs.filter(([, l]) => l.type === UserLogType.Follow); - const unfollowerLogs = pairs.filter(([, l]) => l.type === UserLogType.Unfollow); - const renameLogs = pairs.filter( - ([, l]) => l.type === UserLogType.RenameUserId || l.type === UserLogType.RenameDisplayName, - ); - + const followerLogs = logMap[UserLogType.Follow]; + const unfollowerLogs = logMap[UserLogType.Unfollow]; + const renameLogs = logMap[UserLogType.Rename]; if (followerLogs.length > 0) { fields.push(this.composeLogs(followerLogs, "🎉 {} new {}", "follower")); } @@ -100,15 +97,12 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { } private composeLogs( - logs: NotifyPair[], + logs: UserLog[], messageFormat: string, word: string, ): DiscordWebhookData["embeds"][0]["fields"][0] { - const { name, value } = this.generateEmbedField( - logs, - Logger.format(messageFormat, logs.length, pluralize(word, logs.length)), - ); - + const message = Logger.format(messageFormat, logs.length, pluralize(word, logs.length)); + const { name, value } = this.generateEmbedField(logs, message); const valueLines = [value]; if (logs.length > 10) { valueLines.push(`_... and ${logs.length - 10} more_`); @@ -119,7 +113,7 @@ export class DiscordNotifier extends BaseNotifier<"Discord"> { value: valueLines.join("\n"), }; } - private generateEmbedField(logs: NotifyPair[], title: string) { + private generateEmbedField(logs: UserLog[], title: string) { return { name: title, value: logs.slice(0, 10).map(this.formatNotify).join("\n"), diff --git a/src/notifiers/slack.ts b/src/notifiers/slack.ts index 81ad62c..c10d7f8 100644 --- a/src/notifiers/slack.ts +++ b/src/notifiers/slack.ts @@ -1,10 +1,10 @@ import { IncomingWebhook, IncomingWebhookSendArguments } from "@slack/webhook"; import { BaseNotifier } from "@notifiers/base"; -import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; +import { BaseNotifierOption, UserLogMap } from "@notifiers/type"; -import { groupNotifies } from "@utils/groupNotifies"; import { Logger } from "@utils/logger"; +import { UserLog } from "@repositories/models/user-log"; export interface SlackNotifierOptions extends BaseNotifierOption { webhookUrl: string; @@ -24,24 +24,16 @@ export class SlackNotifier extends BaseNotifier<"Slack"> { public async initialize(): Promise { return; } - public async notify(logs: NotifyPair[]): Promise { - const { follow, unfollow, rename } = groupNotifies(logs); - const targets: [NotifyPair[], number, string, string][] = [ + public async notify(logs: UserLog[], logMap: UserLogMap): Promise { + const { follow, unfollow, rename } = logMap; + const targets: [UserLog[], number, string, string][] = [ [follow.slice(0, MAX_ITEMS_PER_MESSAGE), follow.length, "🎉 {} new {}", "follower"], [unfollow.slice(0, MAX_ITEMS_PER_MESSAGE), unfollow.length, "❌ {} {}", "unfollower"], [rename.slice(0, MAX_ITEMS_PER_MESSAGE), rename.length, "✏️ {} {}", "rename"], ]; const result: IncomingWebhookSendArguments = { - blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: "_*🦜 Cage Report*_", - }, - }, - ], + blocks: [{ type: "section", text: { type: "mrkdwn", text: "_*🦜 Cage Report*_" } }], }; let shouldNotify = false; @@ -78,7 +70,7 @@ export class SlackNotifier extends BaseNotifier<"Slack"> { } } - protected formatNotify(pair: NotifyPair): string { - return super.formatNotify(pair).replace(/ \[(.*? \(@.*?\))\]\((.*?)\)/g, "<$2|$1>"); + protected formatNotify(log: UserLog): string { + return super.formatNotify(log).replace(/ \[(.*? \(@.*?\))\]\((.*?)\)/g, "<$2|$1>"); } } diff --git a/src/notifiers/telegram/constants.ts b/src/notifiers/telegram/constants.ts index 8833580..e7dd5f6 100644 --- a/src/notifiers/telegram/constants.ts +++ b/src/notifiers/telegram/constants.ts @@ -1,19 +1,9 @@ -export const TELEGRAM_FOLLOWERS_TEMPLATE = ` -*🎉 {} {}* +import { UserLogType } from "@repositories/models/user-log"; -{} -{} -`.trim(); -export const TELEGRAM_UNFOLLOWERS_TEMPLATE = ` -*❌ {} {}* +export const CONTENT_TEMPLATES: Partial> = { + [UserLogType.Follow]: ["**🎉 {}**\n\n{}", "new follower"], + [UserLogType.Unfollow]: ["**❌ {}**\n\n{}", "unfollower"], + [UserLogType.Rename]: ["**✏️ {}**\n\n{}", "rename"], +}; -{} -{} -`.trim(); -export const TELEGRAM_RENAMES_TEMPLATE = ` -*✏️ {} {}* - -{} -{} -`.trim(); -export const TELEGRAM_LOG_COUNT = 25; +export const MAXIMUM_LOG_COUNT = 50; diff --git a/src/notifiers/telegram/index.ts b/src/notifiers/telegram/index.ts index 0037382..2d92f14 100644 --- a/src/notifiers/telegram/index.ts +++ b/src/notifiers/telegram/index.ts @@ -1,15 +1,15 @@ import pluralize from "pluralize"; import { BaseNotifier } from "@notifiers/base"; -import { BaseNotifierOption, NotifyPair } from "@notifiers/type"; -import { TELEGRAM_LOG_COUNT } from "@notifiers/telegram/constants"; +import { BaseNotifierOption, UserLogMap } from "@notifiers/type"; import { NotifyResponse, TelegramNotificationData, TokenResponse } from "@notifiers/telegram/types"; +import { CONTENT_TEMPLATES, MAXIMUM_LOG_COUNT } from "@notifiers/telegram/constants"; + +import { UserLog, UserLogType } from "@repositories/models/user-log"; import { Fetcher } from "@utils/fetcher"; -import { groupNotifies } from "@utils/groupNotifies"; -import { Logger } from "@utils/logger"; import { HttpError } from "@utils/httpError"; -import { generateNotificationTargets } from "@notifiers/telegram/utils"; +import { Logger } from "@utils/logger"; export interface TelegramNotifierOptions extends BaseNotifierOption { token: string; @@ -27,31 +27,40 @@ export class TelegramNotifier extends BaseNotifier<"Telegram"> { public async initialize(): Promise { this.currentToken = await this.acquireToken(); } - public async notify(logs: NotifyPair[]): Promise { - const { follow, unfollow, rename } = groupNotifies(logs); - const targets = generateNotificationTargets({ - unfollowers: unfollow, - followers: follow, - renames: rename, - }); - - const notifyData: Partial = {}; - for (const { fieldName, countFieldName, count, word, template, pairs } of targets) { - if (count <= 0) { + public async notify(_: UserLog[], logMap: UserLogMap): Promise { + const reportTargets: UserLogType[] = [UserLogType.Follow, UserLogType.Unfollow, UserLogType.Rename]; + const content: Partial> = {}; + for (const type of reportTargets) { + const logs = logMap[type]; + if (logs.length <= 0) { continue; } - notifyData[countFieldName] = count; - notifyData[fieldName] = Logger.format( - template, - count, - pluralize(word, count), - pairs.map(this.formatNotify).join("\n"), - count > TELEGRAM_LOG_COUNT ? `_... and ${count - TELEGRAM_LOG_COUNT} more_` : "", - ).trim(); + const template = CONTENT_TEMPLATES[type]; + if (!template) { + throw new Error(`There is no message template for log type '${type}'`); + } + + const [title, action] = template; + //TODO: replace this to adjusting the length of the message by content not hard-coded + let messageContent = logs.slice(0, MAXIMUM_LOG_COUNT).map(this.formatNotify).join("\n"); + if (logs.length > MAXIMUM_LOG_COUNT) { + const remainCount = logs.length - MAXIMUM_LOG_COUNT; + messageContent += `\n\n_... and ${remainCount} more_`; + } + + const text = Logger.format(title, pluralize(action, logs.length, true), messageContent); + content[type] = [text, logs.length]; } - await this.pushNotify(notifyData); + await this.pushNotify({ + followers: content[UserLogType.Follow]?.[0], + unfollowers: content[UserLogType.Unfollow]?.[0], + renames: content[UserLogType.Rename]?.[0], + followerCount: content[UserLogType.Follow]?.[1], + unfollowerCount: content[UserLogType.Unfollow]?.[1], + renameCount: content[UserLogType.Rename]?.[1], + }); } private getEndpoint(path: string) { diff --git a/src/notifiers/telegram/types.ts b/src/notifiers/telegram/types.ts index b3f3db3..0780a33 100644 --- a/src/notifiers/telegram/types.ts +++ b/src/notifiers/telegram/types.ts @@ -1,6 +1,3 @@ -import { SelectOnly } from "@utils/types"; -import { NotifyPair } from "@notifiers/type"; - export interface TelegramNotificationData { followers?: string; unfollowers?: string; @@ -17,12 +14,3 @@ export interface TokenResponse { export interface NotifyResponse { success: boolean; } - -export interface NotificationTarget { - fieldName: keyof SelectOnly; - countFieldName: keyof SelectOnly; - word: string; - template: string; - count: number; - pairs: NotifyPair[]; -} diff --git a/src/notifiers/telegram/utils.ts b/src/notifiers/telegram/utils.ts deleted file mode 100644 index cf461ee..0000000 --- a/src/notifiers/telegram/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SelectOnly } from "@utils/types"; -import { NotificationTarget, TelegramNotificationData } from "@notifiers/telegram/types"; -import { NotifyPair } from "@notifiers/type"; -import { - TELEGRAM_FOLLOWERS_TEMPLATE, - TELEGRAM_LOG_COUNT, - TELEGRAM_RENAMES_TEMPLATE, - TELEGRAM_UNFOLLOWERS_TEMPLATE, -} from "@notifiers/telegram/constants"; - -export function generateNotificationTargets( - pairs: Record, NotifyPair[]>, -): NotificationTarget[] { - return [ - { - fieldName: "followers", - countFieldName: "followerCount", - word: "new follower", - template: TELEGRAM_FOLLOWERS_TEMPLATE, - pairs: pairs.followers.slice(0, TELEGRAM_LOG_COUNT), - count: pairs.followers.length, - }, - { - fieldName: "unfollowers", - countFieldName: "unfollowerCount", - word: "unfollower", - template: TELEGRAM_UNFOLLOWERS_TEMPLATE, - pairs: pairs.unfollowers.slice(0, TELEGRAM_LOG_COUNT), - count: pairs.unfollowers.length, - }, - { - fieldName: "renames", - countFieldName: "renameCount", - word: "rename", - template: TELEGRAM_RENAMES_TEMPLATE, - pairs: pairs.renames.slice(0, TELEGRAM_LOG_COUNT), - count: pairs.renames.length, - }, - ]; -} diff --git a/src/notifiers/type.ts b/src/notifiers/type.ts index 303b137..0321b34 100644 --- a/src/notifiers/type.ts +++ b/src/notifiers/type.ts @@ -1,10 +1,8 @@ -import { BaseWatcher } from "@watchers/base"; import { BaseNotifier } from "@notifiers/base"; - -import { UserLog } from "@repositories/models/user-log"; +import { UserLog, UserLogType } from "@repositories/models/user-log"; export interface BaseNotifierOption { type: TNotifier extends BaseNotifier ? Lowercase : string; } -export type NotifyPair = [BaseWatcher, UserLog]; +export type UserLogMap = Record; diff --git a/src/repositories/base.ts b/src/repositories/base.ts index dbd92f2..0138344 100644 --- a/src/repositories/base.ts +++ b/src/repositories/base.ts @@ -1,53 +1,7 @@ import { BaseEntity, Repository } from "typeorm"; -import { AsyncFn } from "@utils/types"; - export abstract class BaseRepository extends Repository { - private readonly buffer: TEntity[] = []; - private inTransaction = false; - protected constructor(repository: Repository) { super(repository.target, repository.manager, repository.queryRunner); } - - public async transaction(task: AsyncFn) { - this.inTransaction = true; - - try { - await task(); - } catch (error) { - await this.closeTransaction(); - throw error; - } - - await this.closeTransaction(); - } - - private async closeTransaction() { - if (this.buffer.length === 0) { - return; - } - - const items = [...this.buffer]; - this.buffer.length = 0; - this.inTransaction = false; - - await this.save(items); - } - - protected async saveItems(item: TEntity): Promise; - protected async saveItems(items: TEntity[]): Promise; - protected async saveItems(items: TEntity[] | TEntity): Promise { - if (this.inTransaction) { - this.buffer.push(...(Array.isArray(items) ? items : [items])); - return items; - } - - if (!Array.isArray(items)) { - const [result] = await this.save([items]); - return result; - } - - return this.save(items); - } } diff --git a/src/repositories/models/user.ts b/src/repositories/models/user.ts index 0f8a19a..851d2cf 100644 --- a/src/repositories/models/user.ts +++ b/src/repositories/models/user.ts @@ -2,8 +2,6 @@ import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, RelationId, Up import { UserLog } from "@root/repositories/models/user-log"; -export type UserData = Pick; - @Entity({ name: "users" }) export class User extends BaseEntity { @Column({ type: "varchar", length: 255, primary: true }) @@ -24,6 +22,9 @@ export class User extends BaseEntity { @Column({ type: "datetime" }) public lastlyCheckedAt!: Date; + @Column({ type: "varchar" }) + public profileUrl!: string; + @CreateDateColumn() public createdAt!: Date; diff --git a/src/repositories/user-log.ts b/src/repositories/user-log.ts index f500f82..432050f 100644 --- a/src/repositories/user-log.ts +++ b/src/repositories/user-log.ts @@ -1,8 +1,6 @@ import { Repository } from "typeorm"; import { BaseRepository } from "@repositories/base"; - -import { User } from "@repositories/models/user"; import { UserLog, UserLogType } from "@repositories/models/user-log"; import { mapBy } from "@utils/mapBy"; @@ -12,42 +10,6 @@ export class UserLogRepository extends BaseRepository { super(repository); } - public async writeLog(user: User, type: UserLog["type"]) { - const userLog = this.create(); - userLog.user = user; - userLog.type = type; - - return this.saveItems(userLog); - } - public async writeLogs(users: User[], type: UserLog["type"]) { - const userLogs = users.map(user => { - const userLog = this.create(); - userLog.user = user; - userLog.type = type; - - return userLog; - }); - - return this.saveItems(userLogs); - } - public async batchWriteLogs(items: Array<[User[], UserLog["type"]]>, rawLogs?: UserLog[]) { - const userLogs = items.flatMap(([users, type]) => - users.map(user => { - const userLog = this.create(); - userLog.user = user; - userLog.type = type; - - return userLog; - }), - ); - - if (rawLogs) { - userLogs.push(...rawLogs); - } - - return this.saveItems(userLogs); - } - public async getFollowStatusMap() { const data = await this.createQueryBuilder() .select("type") @@ -60,3 +22,5 @@ export class UserLogRepository extends BaseRepository { return mapBy(data, "userId", item => item.type === UserLogType.Follow); } } + +export * from "./models/user-log"; diff --git a/src/repositories/user.ts b/src/repositories/user.ts index fca363b..faf37f7 100644 --- a/src/repositories/user.ts +++ b/src/repositories/user.ts @@ -1,30 +1,12 @@ import { Repository } from "typeorm"; import { BaseRepository } from "@repositories/base"; -import { User, UserData } from "@repositories/models/user"; +import { User } from "@repositories/models/user"; export class UserRepository extends BaseRepository { public constructor(repository: Repository) { super(repository); } - - public async createFromData(data: UserData | UserData[], lastlyCheckedAt: Date = new Date()) { - if (!Array.isArray(data)) { - data = [data]; - } - - const users = data.map(item => { - const user = this.create(); - user.id = `${item.from}:${item.uniqueId}`; - user.displayName = item.displayName; - user.userId = item.userId; - user.uniqueId = item.uniqueId; - user.from = item.from; - user.lastlyCheckedAt = lastlyCheckedAt; - - return user; - }); - - return this.saveItems(users); - } } + +export * from "./models/user"; diff --git a/src/tasks/base.spec.ts b/src/tasks/base.spec.ts new file mode 100644 index 0000000..998d9ba --- /dev/null +++ b/src/tasks/base.spec.ts @@ -0,0 +1,52 @@ +import stripAnsi from "strip-ansi"; + +import { BaseTask, TaskData } from "@tasks/base"; +import { UserLog } from "@repositories/models/user-log"; +import { WorkOptions } from "@utils"; + +describe("BaseTask", () => { + it("should determine given task data type", function () { + const data: TaskData[] = [ + { type: "new-users", data: [] }, + { type: "new-logs", data: [] }, + { type: "notify" }, + { type: "save", savedCount: 0 }, + { type: "terminate", reason: "" }, + { type: "skip", reason: "" }, + ]; + + expect(data.map(BaseTask.isNewUsersData)).toEqual([true, false, false, false, false, false]); + expect(data.map(BaseTask.isNewLogsData)).toEqual([false, true, false, false, false, false]); + expect(data.map(BaseTask.isTerminateData)).toEqual([false, false, false, false, true, false]); + expect(data.map(BaseTask.isSkipData)).toEqual([false, false, false, false, false, true]); + }); + + it("should process task with rich logging", async () => { + const taskDataArray: [TaskData, string][] = [ + [{ type: "new-logs", data: [{} as UserLog] }, "Created 1 new logs"], + [{ type: "skip", reason: "mock-reason" }, "Skipping `Mock` task: mock-reason"], + [{ type: "terminate", reason: "mock-reason" }, "Terminating whole task pipeline: mock-reason"], + ]; + + for (const [item, targetMessage] of taskDataArray) { + class MockTask extends BaseTask { + public async process(): Promise { + return item; + } + } + + const task = new MockTask([], [], null as any, null as any, "MockTask"); + let logMessage = ""; + Object.defineProperty(task, "logger", { + value: { + work: jest.fn().mockImplementation((options: WorkOptions) => options.work()), + info: jest.fn().mockImplementation((message: string) => (logMessage = message)), + }, + }); + + await task.doWork([]); + expect(task["logger"].info).toBeCalledTimes(1); + expect(stripAnsi(logMessage)).toBe(targetMessage); + } + }); +}); diff --git a/src/tasks/base.ts b/src/tasks/base.ts new file mode 100644 index 0000000..6fccc48 --- /dev/null +++ b/src/tasks/base.ts @@ -0,0 +1,101 @@ +import { BaseNotifier } from "@notifiers/base"; +import { BaseWatcher } from "@watchers/base"; + +import { UserRepository } from "@repositories/user"; +import { UserLogRepository } from "@repositories/user-log"; +import { User } from "@repositories/models/user"; +import { UserLog } from "@repositories/models/user-log"; + +import { Loggable, Logger } from "@utils"; + +interface NewUsersTaskData { + type: "new-users"; + data: User[]; +} +interface NewLogsTaskData { + type: "new-logs"; + data: UserLog[]; +} +interface NotifyTaskData { + type: "notify"; +} +interface SaveTaskData { + type: "save"; + savedCount: number; +} +interface TerminateTaskData { + type: "terminate"; + reason: string; +} +interface SkipTaskData { + type: "skip"; + reason: string; +} + +export type TaskData = + | NewUsersTaskData + | NewLogsTaskData + | NotifyTaskData + | SaveTaskData + | TerminateTaskData + | SkipTaskData; +export type TaskClass = new (...args: ConstructorParameters) => BaseTask; + +export abstract class BaseTask extends Loggable { + public constructor( + protected readonly watchers: ReadonlyArray>, + protected readonly notifiers: ReadonlyArray>, + protected readonly userRepository: UserRepository, + protected readonly userLogRepository: UserLogRepository, + name: string, + ) { + super(name.replace(/task$/i, "")); + } + + public static isNewUsersData(data: TaskData): data is NewUsersTaskData { + return data.type === "new-users"; + } + public static isNewLogsData(data: TaskData): data is NewLogsTaskData { + return data.type === "new-logs"; + } + public static isTerminateData(data: TaskData): data is TerminateTaskData { + return data.type === "terminate"; + } + public static isSkipData(data: TaskData): data is SkipTaskData { + return data.type === "skip"; + } + + public async doWork(previousData: TaskData[]): Promise { + const data = await this.logger.work({ + message: Logger.format("processing `{green}` task", this.getName()), + level: "info", + work: () => this.process(previousData), + }); + + this.logData(data); + return data; + } + + protected abstract process(previousData: TaskData[]): Promise; + + protected terminate(reason: string): TerminateTaskData { + return { type: "terminate", reason }; + } + protected skip(reason: string): SkipTaskData { + return { type: "skip", reason }; + } + + private logData(data: TaskData) { + if (BaseTask.isNewLogsData(data) && data.data.length > 0) { + this.logger.info(Logger.format("Created {green} new logs", data.data.length)); + } + + if (BaseTask.isTerminateData(data)) { + this.logger.info(Logger.format("Terminating whole task pipeline: {red}", data.reason)); + } + + if (BaseTask.isSkipData(data)) { + this.logger.info(Logger.format("Skipping `{green}` task: {red}", this.getName(), data.reason)); + } + } +} diff --git a/src/tasks/grab-user.spec.ts b/src/tasks/grab-user.spec.ts new file mode 100644 index 0000000..97461d0 --- /dev/null +++ b/src/tasks/grab-user.spec.ts @@ -0,0 +1,49 @@ +import { GrabUserTask } from "@tasks/grab-user"; +import { BaseWatcher } from "@watchers/base"; + +describe("GrabUserTask", () => { + it("should grab users from watchers", async function () { + const watchers = [ + { + doWatch: jest.fn().mockResolvedValueOnce([{ id: 1, name: "user1" }]), + }, + { + doWatch: jest.fn().mockResolvedValueOnce([{ id: 2, name: "user2" }]), + }, + ] as any as BaseWatcher[]; + + const task = new GrabUserTask(watchers, [], null as any, null as any, GrabUserTask.name); + const result = await task.process(); + + expect(result).toEqual({ + type: "new-users", + data: [ + { id: 1, name: "user1" }, + { id: 2, name: "user2" }, + ], + }); + expect(watchers[0].doWatch).toBeCalledTimes(1); + expect(watchers[1].doWatch).toBeCalledTimes(1); + }); + + it("should terminate if no users found", async function () { + const watchers = [ + { + doWatch: jest.fn().mockResolvedValueOnce([]), + }, + { + doWatch: jest.fn().mockResolvedValueOnce([]), + }, + ] as any as BaseWatcher[]; + + const task = new GrabUserTask(watchers, [], null as any, null as any, GrabUserTask.name); + const result = await task.process(); + + expect(result).toEqual({ + type: "terminate", + reason: "No followers found", + }); + expect(watchers[0].doWatch).toBeCalledTimes(1); + expect(watchers[1].doWatch).toBeCalledTimes(1); + }); +}); diff --git a/src/tasks/grab-user.ts b/src/tasks/grab-user.ts new file mode 100644 index 0000000..5c4936f --- /dev/null +++ b/src/tasks/grab-user.ts @@ -0,0 +1,23 @@ +import { User } from "@repositories/models/user"; + +import { BaseTask, TaskData } from "@tasks/base"; + +export class GrabUserTask extends BaseTask { + public async process(): Promise { + const taskStartedAt = new Date(); + const allUsers: User[] = []; + for (const watcher of this.watchers) { + const users = await watcher.doWatch(taskStartedAt); + allUsers.push(...users); + } + + if (allUsers.length <= 0) { + return this.terminate("No followers found"); + } + + return { + type: "new-users", + data: allUsers, + }; + } +} diff --git a/src/tasks/index.spec.ts b/src/tasks/index.spec.ts new file mode 100644 index 0000000..cd2ea48 --- /dev/null +++ b/src/tasks/index.spec.ts @@ -0,0 +1,8 @@ +import { DEFAULT_TASKS } from "@tasks/index"; + +describe("Tasks", () => { + it("should provide predefined task array", () => { + expect(DEFAULT_TASKS).toBeDefined(); + expect(DEFAULT_TASKS.length).toBeGreaterThan(0); + }); +}); diff --git a/src/tasks/index.ts b/src/tasks/index.ts new file mode 100644 index 0000000..322f1fd --- /dev/null +++ b/src/tasks/index.ts @@ -0,0 +1,22 @@ +import { TaskClass } from "@tasks/base"; +import { NotifyTask } from "@tasks/notify"; +import { UnfollowerTask } from "@tasks/unfollower"; +import { NewFollowerTask } from "@tasks/new-follower"; +import { GrabUserTask } from "@tasks/grab-user"; +import { SaveTask } from "@tasks/save"; +import { RenameTask } from "@tasks/rename"; + +export const DEFAULT_TASKS: ReadonlyArray = [ + GrabUserTask, + NewFollowerTask, + UnfollowerTask, + RenameTask, + NotifyTask, + SaveTask, +]; + +export * from "./base"; +export * from "./grab-user"; +export * from "./new-follower"; +export * from "./notify"; +export * from "./unfollower"; diff --git a/src/tasks/new-follower.spec.ts b/src/tasks/new-follower.spec.ts new file mode 100644 index 0000000..f641232 --- /dev/null +++ b/src/tasks/new-follower.spec.ts @@ -0,0 +1,29 @@ +import { NewFollowerTask } from "@tasks/new-follower"; +import { UserLogRepository, UserLogType } from "@repositories/user-log"; + +describe("NewFollowerTask", () => { + it("should detect new followers between old and new user items", async function () { + const mockUserLogRepository = { + getFollowStatusMap: jest.fn().mockResolvedValue({ + user1: true, + user2: false, + }), + + create: jest.fn().mockReturnValue({}), + } as unknown as UserLogRepository; + + const followers = [{ id: "user1" }, { id: "user2" }]; + const task = new NewFollowerTask([], [], null as any, mockUserLogRepository, NewFollowerTask.name); + const result = await task.process([{ type: "new-users", data: followers as any }]); + + expect(result).toEqual({ + type: "new-logs", + data: [ + { + type: UserLogType.Follow, + user: { id: "user2" }, + }, + ], + }); + }); +}); diff --git a/src/tasks/new-follower.ts b/src/tasks/new-follower.ts new file mode 100644 index 0000000..c44fbba --- /dev/null +++ b/src/tasks/new-follower.ts @@ -0,0 +1,21 @@ +import { BaseTask, TaskData } from "@tasks/base"; +import { UserLogType } from "@repositories/models/user-log"; + +export class NewFollowerTask extends BaseTask { + public async process(previousData: TaskData[]): Promise { + const followingMap = await this.userLogRepository.getFollowStatusMap(); + const newUsers = previousData.filter(BaseTask.isNewUsersData).flatMap(item => item.data); + const newFollowers = newUsers.filter(p => !followingMap[p.id]); + + return { + type: "new-logs", + data: newFollowers.map(item => { + const log = this.userLogRepository.create(); + log.type = UserLogType.Follow; + log.user = item; + + return log; + }), + }; + } +} diff --git a/src/tasks/notify.spec.ts b/src/tasks/notify.spec.ts new file mode 100644 index 0000000..984ed00 --- /dev/null +++ b/src/tasks/notify.spec.ts @@ -0,0 +1,43 @@ +import { NotifyTask } from "@tasks/notify"; +import { BaseNotifier } from "@notifiers/base"; +import { UserLogType } from "@repositories/models/user-log"; + +describe("NotifyTask", () => { + it("should notify new logs through notifiers", async () => { + const mockNotifier = { notify: jest.fn() } as unknown as BaseNotifier; + const mockUserLogs = [ + { type: UserLogType.Follow } as any, + { type: UserLogType.Unfollow } as any, + { type: UserLogType.RenameUserId } as any, + { type: UserLogType.RenameDisplayName } as any, + ]; + + const task = new NotifyTask([], [mockNotifier], null as any, null as any, NotifyTask.name); + const result = await task.process([ + { + type: "new-logs", + data: mockUserLogs, + }, + ]); + + expect(result).toEqual({ type: "notify" }); + expect(mockNotifier.notify).toHaveBeenCalledTimes(1); + expect(mockNotifier.notify).toHaveBeenCalledWith(mockUserLogs, { + [UserLogType.Follow]: [mockUserLogs[0]], + [UserLogType.Unfollow]: [mockUserLogs[1]], + [UserLogType.RenameUserId]: [mockUserLogs[2]], + [UserLogType.RenameDisplayName]: [mockUserLogs[3]], + [UserLogType.Rename]: [mockUserLogs[3], mockUserLogs[2]], + }); + }); + + it("should skip notifying if there is no new logs", async () => { + const mockNotifier = { notify: jest.fn() } as unknown as BaseNotifier; + + const task = new NotifyTask([], [mockNotifier], null as any, null as any, NotifyTask.name); + const result = await task.process([]); + + expect(result).toEqual({ type: "skip", reason: "No new logs to notify was found" }); + expect(mockNotifier.notify).not.toHaveBeenCalled(); + }); +}); diff --git a/src/tasks/notify.ts b/src/tasks/notify.ts new file mode 100644 index 0000000..01f63dd --- /dev/null +++ b/src/tasks/notify.ts @@ -0,0 +1,21 @@ +import { BaseTask, TaskData } from "@tasks/base"; + +import { groupNotifies } from "@utils/groupNotifies"; + +export class NotifyTask extends BaseTask { + public async process(previousData: TaskData[]): Promise { + const newLogs = previousData.filter(BaseTask.isNewLogsData).flatMap(item => item.data); + if (newLogs.length <= 0) { + return this.skip("No new logs to notify was found"); + } + + const logMap = groupNotifies(newLogs); + for (const notifier of this.notifiers) { + await notifier.notify(newLogs, logMap); + } + + return { + type: "notify", + }; + } +} diff --git a/src/tasks/rename.spec.ts b/src/tasks/rename.spec.ts new file mode 100644 index 0000000..28b97b4 --- /dev/null +++ b/src/tasks/rename.spec.ts @@ -0,0 +1,78 @@ +import { BaseEntity } from "typeorm"; +import { User, UserRepository } from "@root/repositories/user"; +import { RenameTask } from "@tasks/rename"; +import { UserLogRepository, UserLogType } from "@repositories/user-log"; + +let mockUserId = 0; +function createMockUser(userId: string, displayName: string): Omit { + const id = `user${++mockUserId}`; + + return { + id, + from: "mock", + uniqueId: id, + userId, + displayName, + lastlyCheckedAt: new Date(), + profileUrl: "https://example.com/user1", + createdAt: new Date(), + updatedAt: new Date(), + userLogs: [], + userLogIds: [], + }; +} + +describe("RenameTask", () => { + it("should detect renamed users between old and new user items", async () => { + const mockNewUsers: Omit[] = [ + createMockUser("mock1", "mock1"), + createMockUser("mock2", "mock2"), + ]; + const mockOldUsers: Omit[] = [ + { ...mockNewUsers[0], displayName: "old1" }, + { ...mockNewUsers[1], userId: "old2" }, + ]; + + const task = new RenameTask( + [], + [], + { find: jest.fn().mockResolvedValue(mockOldUsers) } as unknown as UserRepository, + { create: jest.fn().mockImplementation(item => item || {}) } as unknown as UserLogRepository, + RenameTask.name, + ); + const result = await task.process([{ type: "new-users", data: mockNewUsers as unknown as User[] }]); + + expect(result).toMatchObject({ + type: "new-logs", + data: [ + { + type: UserLogType.RenameDisplayName, + user: { id: mockNewUsers[0].id, displayName: "mock1" }, + oldDisplayName: "old1", + }, + { + type: UserLogType.RenameUserId, + user: { id: mockNewUsers[1].id, userId: "mock2" }, + oldUserId: "old2", + }, + ], + }); + }); + + it("should not detect renamed users on new user items", async () => { + const mockNewUsers: Omit[] = [createMockUser("mock1", "mock1")]; + const task = new RenameTask( + [], + [], + { find: jest.fn().mockResolvedValue(mockNewUsers) } as unknown as UserRepository, + { create: jest.fn().mockImplementation(item => item || {}) } as unknown as UserLogRepository, + RenameTask.name, + ); + const result = await task.process([{ type: "new-users", data: [] }]); + + expect(result).toMatchObject({ + type: "new-logs", + data: [], + }); + }); +}); diff --git a/src/tasks/rename.ts b/src/tasks/rename.ts new file mode 100644 index 0000000..1f6686e --- /dev/null +++ b/src/tasks/rename.ts @@ -0,0 +1,41 @@ +import { BaseTask, TaskData } from "@tasks/base"; + +import { UserLogType } from "@repositories/models/user-log"; +import { User } from "@repositories/models/user"; + +import { getDiff, isRequired, mapBy } from "@utils"; + +export class RenameTask extends BaseTask { + private createGenerator( + logType: UserLogType.RenameUserId | UserLogType.RenameDisplayName, + oldUsersMap: Record, + ) { + return (item: User) => { + const oldUser = oldUsersMap[item.id]; + return this.userLogRepository.create({ + type: logType, + user: item, + oldDisplayName: oldUser.displayName, + oldUserId: oldUser.userId, + }); + }; + } + + public async process(previousData: TaskData[]): Promise { + const oldUsers = await this.userRepository.find(); + const oldUsersMap = mapBy(oldUsers, "id"); + const newUsers = previousData.filter(BaseTask.isNewUsersData).flatMap(item => item.data); + + // find user renaming their displayName or userId + const displayNameRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "displayName"); + const userIdRenamedUsers = getDiff(newUsers, oldUsers, "uniqueId", "userId"); + + return { + type: "new-logs", + data: [ + ...displayNameRenamedUsers.map(this.createGenerator(UserLogType.RenameDisplayName, oldUsersMap)), + ...userIdRenamedUsers.map(this.createGenerator(UserLogType.RenameUserId, oldUsersMap)), + ].filter(isRequired), + }; + } +} diff --git a/src/tasks/save.spec.ts b/src/tasks/save.spec.ts new file mode 100644 index 0000000..1fb2621 --- /dev/null +++ b/src/tasks/save.spec.ts @@ -0,0 +1,38 @@ +import { UserRepository } from "@repositories/user"; +import { UserLogRepository } from "@repositories/user-log"; + +import { SaveTask } from "@tasks/save"; + +describe("SaveTask", () => { + it("should save new users and user logs", async () => { + const mockUserRepository = { save: jest.fn() } as unknown as UserRepository; + const mockUserLogRepository = { save: jest.fn() } as unknown as UserLogRepository; + + const mockUsers = [{ id: "user1" }, { id: "user2" }] as any; + const mockUserLogs = [{ id: "log1" }, { id: "log2" }] as any; + const task = new SaveTask([], [], mockUserRepository, mockUserLogRepository, SaveTask.name); + + const result = await task.process([ + { type: "new-users", data: mockUsers }, + { type: "new-logs", data: mockUserLogs }, + ]); + + expect(result).toEqual({ type: "save", savedCount: 4 }); + expect(mockUserRepository.save).toHaveBeenCalledTimes(1); + expect(mockUserRepository.save).toHaveBeenCalledWith(mockUsers); + expect(mockUserLogRepository.save).toHaveBeenCalledTimes(1); + expect(mockUserLogRepository.save).toHaveBeenCalledWith(mockUserLogs); + }); + + it("should skip saving if there is no new users and user logs", async () => { + const mockUserRepository = { save: jest.fn() } as unknown as UserRepository; + const mockUserLogRepository = { save: jest.fn() } as unknown as UserLogRepository; + + const task = new SaveTask([], [], mockUserRepository, mockUserLogRepository, SaveTask.name); + const result = await task.process([]); + + expect(result).toEqual({ type: "skip", reason: "No new logs or users to save was found" }); + expect(mockUserRepository.save).not.toHaveBeenCalled(); + expect(mockUserLogRepository.save).not.toHaveBeenCalled(); + }); +}); diff --git a/src/tasks/save.ts b/src/tasks/save.ts new file mode 100644 index 0000000..2642885 --- /dev/null +++ b/src/tasks/save.ts @@ -0,0 +1,19 @@ +import { BaseTask, TaskData } from "@tasks/base"; + +export class SaveTask extends BaseTask { + public async process(previousData: TaskData[]): Promise { + const newLogs = previousData.filter(BaseTask.isNewLogsData).flatMap(item => item.data); + const newUsers = previousData.filter(BaseTask.isNewUsersData).flatMap(item => item.data); + if (newLogs.length <= 0 && newUsers.length <= 0) { + return this.skip("No new logs or users to save was found"); + } + + await this.userRepository.save(newUsers); + await this.userLogRepository.save(newLogs); + + return { + type: "save", + savedCount: newLogs.length + newUsers.length, + }; + } +} diff --git a/src/tasks/unfollower.spec.ts b/src/tasks/unfollower.spec.ts new file mode 100644 index 0000000..773fd62 --- /dev/null +++ b/src/tasks/unfollower.spec.ts @@ -0,0 +1,27 @@ +import { UserLogRepository, UserLogType } from "@repositories/user-log"; +import { UserRepository } from "@repositories/user"; + +import { UnfollowerTask } from "@tasks/unfollower"; + +describe("UnfollowerTask", () => { + it("should detect unfollowers between old and new user items", async () => { + const mockUserRepository = { + find: jest.fn().mockResolvedValue([{ id: "user1" }, { id: "user2" }]), + } as unknown as UserRepository; + const mockUserLogRepository = { + getFollowStatusMap: jest.fn().mockResolvedValue({ user1: true, user2: true }), + create: jest.fn().mockImplementation(item => item || {}), + } as unknown as UserLogRepository; + + const task = new UnfollowerTask([], [], mockUserRepository, mockUserLogRepository, UnfollowerTask.name); + const result = await task.process([{ type: "new-users", data: [] }]); + + expect(result).toEqual({ + type: "new-logs", + data: [ + { type: UserLogType.Unfollow, user: { id: "user1" } }, + { type: UserLogType.Unfollow, user: { id: "user2" } }, + ], + }); + }); +}); diff --git a/src/tasks/unfollower.ts b/src/tasks/unfollower.ts new file mode 100644 index 0000000..303e695 --- /dev/null +++ b/src/tasks/unfollower.ts @@ -0,0 +1,23 @@ +import { BaseTask, TaskData } from "@tasks/base"; +import { mapBy } from "@utils/mapBy"; +import { UserLogType } from "@repositories/models/user-log"; + +export class UnfollowerTask extends BaseTask { + public async process(previousData: TaskData[]): Promise { + const followingMap = await this.userLogRepository.getFollowStatusMap(); + const oldUsers = await this.userRepository.find(); + const newUsers = previousData.filter(BaseTask.isNewUsersData).flatMap(item => item.data); + const newUserMap = mapBy(newUsers, "id"); + const unfollowers = oldUsers.filter(p => !newUserMap[p.id] && followingMap[p.id]); + + return { + type: "new-logs", + data: unfollowers.map(item => + this.userLogRepository.create({ + type: UserLogType.Unfollow, + user: item, + }), + ), + }; + } +} diff --git a/src/utils/generateRandomString.ts b/src/utils/generateRandomString.ts deleted file mode 100644 index 96cd0cf..0000000 --- a/src/utils/generateRandomString.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function generateRandomString(length: number, chars: string) { - let result = ""; - for (let i = length; i > 0; --i) { - result += chars[Math.floor(Math.random() * chars.length)]; - } - - return result; -} diff --git a/src/utils/getTimestamp.ts b/src/utils/getTimestamp.ts deleted file mode 100644 index b51b0ed..0000000 --- a/src/utils/getTimestamp.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getTimestamp() { - return new Date().getTime(); -} diff --git a/src/utils/groupNotifies.ts b/src/utils/groupNotifies.ts index ef38eea..d468dbd 100644 --- a/src/utils/groupNotifies.ts +++ b/src/utils/groupNotifies.ts @@ -1,17 +1,17 @@ import _ from "lodash"; -import { UserLogType } from "@repositories/models/user-log"; +import { UserLog, UserLogType } from "@repositories/models/user-log"; -import { NotifyPair } from "@notifiers/type"; +import { UserLogMap } from "@notifiers/type"; -type MapKeys = Exclude; - -export function groupNotifies(pairs: NotifyPair[]): Record { - const result = _.groupBy(pairs, ([, log]) => log.type); +export function groupNotifies(pairs: UserLog[]): UserLogMap { + const result = _.groupBy(pairs, log => log.type); return { [UserLogType.Follow]: result[UserLogType.Follow] || [], [UserLogType.Unfollow]: result[UserLogType.Unfollow] || [], + [UserLogType.RenameUserId]: result[UserLogType.RenameUserId] || [], + [UserLogType.RenameDisplayName]: result[UserLogType.RenameDisplayName] || [], [UserLogType.Rename]: [ ...(result[UserLogType.RenameDisplayName] || []), ...(result[UserLogType.RenameUserId] || []), diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..7a78f5d --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,18 @@ +export * from "./buildQueryString"; +export * from "./cli"; +export * from "./config"; +export * from "./config.const"; +export * from "./config.type"; +export * from "./fetcher"; +export * from "./getDiff"; +export * from "./groupNotifies"; +export * from "./httpError"; +export * from "./isRequired"; +export * from "./logger"; +export * from "./mapBy"; +export * from "./measureTime"; +export * from "./noop"; +export * from "./parseCookie"; +export * from "./sleep"; +export * from "./throttle"; +export * from "./types"; diff --git a/src/utils/isRequired.ts b/src/utils/isRequired.ts new file mode 100644 index 0000000..ad16c1f --- /dev/null +++ b/src/utils/isRequired.ts @@ -0,0 +1,3 @@ +export function isRequired(value: T | undefined | null): value is T { + return Boolean(value); +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index d15a10d..f1c073a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -16,7 +16,7 @@ const LOG_LEVEL_COLOR_MAP: Record> = { debug: chalk.magenta, }; -interface WorkOptions { +export interface WorkOptions { level: LogLevel; message: string; failedLevel?: LogLevel; diff --git a/src/utils/types.ts b/src/utils/types.ts index 4992fa6..8a6ac58 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -3,7 +3,6 @@ import { Logger } from "@utils/logger"; export type SelectOnly = { [Key in keyof Required as Required[Key] extends Type ? Key : never]: Record[Key]; }; - export type TypeMap = { [TKey in T["type"]]: TKey extends T["type"] ? Extract : never; }; diff --git a/src/watchers/base.ts b/src/watchers/base.ts index 2566d8b..850f13b 100644 --- a/src/watchers/base.ts +++ b/src/watchers/base.ts @@ -1,13 +1,17 @@ import pluralize from "pluralize"; -import { UserData } from "@repositories/models/user"; +import { User } from "@repositories/models/user"; import { Loggable } from "@utils/types"; +import { BaseEntity } from "typeorm"; export interface BaseWatcherOptions { type: TWatcher extends BaseWatcher ? Lowercase : string; } -export type PartialUserData = Omit; +export type PartialUser = Omit< + User, + keyof BaseEntity | "from" | "id" | "lastlyCheckedAt" | "createdAt" | "updatedAt" | "userLogs" | "userLogIds" +>; export abstract class BaseWatcher extends Loggable { protected constructor(name: TType) { @@ -16,17 +20,21 @@ export abstract class BaseWatcher extends Loggable public abstract initialize(): Promise; - public async doWatch(): Promise { + public async doWatch(startedAt: Date): Promise { const followers = await this.getFollowers(); + const from = this.getName().toLowerCase(); this.logger.info("Successfully crawled {} {}", [followers.length, pluralize("follower", followers.length)]); - return followers.map(user => ({ - ...user, - from: this.getName().toLowerCase(), - })); + return followers.map(user => + User.create({ + ...user, + id: `${from}:${user.uniqueId}`, + from, + lastlyCheckedAt: startedAt, + }), + ); } - protected abstract getFollowers(): Promise; - public abstract getProfileUrl(user: UserData): string; + protected abstract getFollowers(): Promise; } diff --git a/src/watchers/github/index.ts b/src/watchers/github/index.ts index dfa8154..1ef611d 100644 --- a/src/watchers/github/index.ts +++ b/src/watchers/github/index.ts @@ -1,16 +1,19 @@ import nodeFetch from "node-fetch"; import { Client, CombinedError, createClient } from "@urql/core"; -import { BaseWatcher, BaseWatcherOptions, PartialUserData } from "@watchers/base"; +import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; import { FollowersDocument, MeDocument } from "@watchers/github/queries"; import { FollowersQuery, FollowersQueryVariables, MeQuery } from "@root/queries.data"; +import { isRequired } from "@utils/isRequired"; import { Nullable } from "@utils/types"; export interface GitHubWatcherOptions extends BaseWatcherOptions { authToken: string; } +const isPartialUser = (user: PartialUser | null): user is PartialUser => Boolean(user); + export class GitHubWatcher extends BaseWatcher<"GitHub"> { private client: Client; @@ -35,13 +38,10 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { public async initialize() { return; } - public getProfileUrl(user: PartialUserData) { - return `https://github.com/${user.userId}`; - } protected async getFollowers() { try { - const result: PartialUserData[] = []; + const result: PartialUser[] = []; const currentUserId = await this.getCurrentUserId(); let cursor: string | undefined = undefined; @@ -64,21 +64,17 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { } else if (e.graphQLErrors && e.graphQLErrors.length > 0) { throw e.graphQLErrors[0]; } - } else { - throw e; } - } - return []; + throw e; + } } private async getCurrentUserId() { const { data, error } = await this.client.query(MeDocument, {}).toPromise(); if (error) { throw error; - } - - if (!data) { + } else if (!data) { throw new Error("No data returned from Me query"); } @@ -87,44 +83,28 @@ export class GitHubWatcher extends BaseWatcher<"GitHub"> { private async getFollowersFromUserId( targetUserId: string, cursor?: string, - ): Promise<[PartialUserData[], Nullable]> { + ): Promise<[PartialUser[], Nullable]> { const { data, error } = await this.client - .query(FollowersDocument, { - username: targetUserId, - cursor, - }) + .query(FollowersDocument, { username: targetUserId, cursor }) .toPromise(); if (error) { throw error; - } - - if (!data) { - throw new Error("No data returned from Followers query"); - } - - if (!data.user) { - throw new Error("No user returned from Followers query"); - } - - if (!data.user.followers.edges) { + } else if (!data?.user?.followers?.edges) { throw new Error("No followers returned from Followers query"); } return [ data.user.followers.edges - .map(edge => { - if (!edge?.node) { - return null; - } - - return { - uniqueId: edge.node.id, - userId: edge.node.login, - displayName: edge.node.name || edge.node.login, - }; - }) - .filter((item: PartialUserData | null): item is PartialUserData => Boolean(item)), + .map(edge => edge?.node) + .filter(isRequired) + .map(node => ({ + uniqueId: node.id, + userId: node.login, + displayName: node.name || node.login, + profileUrl: `https://github.com/${node.login}`, + })) + .filter(isPartialUser), data.user.followers.pageInfo.endCursor, ]; } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index cf2b839..a7055a8 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -1,9 +1,7 @@ import { TwitterApi, TwitterApiReadOnly } from "twitter-api-v2"; import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; -import { UserData } from "@repositories/models/user"; - -import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; +import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; export interface TwitterWatcherOptions extends BaseWatcherOptions { bearerToken: string; @@ -33,11 +31,8 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { this.logger.verbose("Successfully initialized with user id {}", [data.id]); this.currentUserId = data.id; } - public getProfileUrl(user: UserData) { - return `https://twitter.com/${user.userId}`; - } - protected async getFollowers() { + protected async getFollowers(): Promise { if (!this.currentUserId) { throw new Error("Watcher is not initialized"); } @@ -51,6 +46,7 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { uniqueId: user.id, displayName: user.name, userId: user.username, + profileUrl: `https://twitter.com/${user.username}`, })); } } diff --git a/tsconfig.json b/tsconfig.json index 645de76..2eed051 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,12 +27,15 @@ "paths": { "@root/*": ["./src/*"], "@utils/*": ["./src/utils/*"], + "@utils": ["./src/utils"], "@watchers/*": ["./src/watchers/*"], "@watchers": ["./src/watchers"], "@followers/*": ["./src/followers/*"], "@repositories/*": ["./src/repositories/*"], "@notifiers/*": ["./src/notifiers/*"], - "@notifiers": ["./src/notifiers"] + "@notifiers": ["./src/notifiers"], + "@tasks/*": ["./src/tasks/*"], + "@tasks": ["./src/tasks"] } } } From 1aa7732102bd36b429607a6f787ed3351d455dd0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 20 Dec 2022 07:56:01 +0000 Subject: [PATCH 126/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.14](https://github.com/async3619/cage/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-12-20) ### Bug Fixes 🐞 * **twitter:** now twitter watcher uses actual user id instead of hardcoded one ([5e53aa9](https://github.com/async3619/cage/commit/5e53aa9b631ae439a7050886e9630aa55ea416b2)) ### Internal 🧰 * **core:** make app-overfocused business logics separated into individual tasks ([53507b3](https://github.com/async3619/cage/commit/53507b3352fd2eed27c017af74621328e0a3e8db)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b57a1b5..98e8e76 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.13", + "version": "1.0.0-dev.14", "license": "MIT", "publishConfig": { "access": "public" From dd0c7e3c2bcd5a90e402ab05c9105fbefa7fa90e Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 3 Jul 2023 03:22:13 +0100 Subject: [PATCH 127/140] feat(mastodon): implement mastodon watcher --- config.schema.json | 21 +++ package.json | 5 +- src/watchers/index.ts | 6 +- src/watchers/mastodon/index.ts | 54 +++++++ yarn.lock | 250 ++++++++++++++------------------- 5 files changed, 188 insertions(+), 148 deletions(-) create mode 100644 src/watchers/mastodon/index.ts diff --git a/config.schema.json b/config.schema.json index 742e577..e43846e 100644 --- a/config.schema.json +++ b/config.schema.json @@ -48,6 +48,27 @@ "type" ], "additionalProperties": false + }, + "mastodon": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "mastodon" + }, + "url": { + "type": "string" + }, + "accessToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "type", + "url" + ], + "additionalProperties": false } }, "additionalProperties": false diff --git a/package.json b/package.json index 98e8e76..b342438 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "scripts": { "build": "tsc --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json", - "dev": "node --watch -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", + "dev": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", + "watch": "node --watch -r ts-node/register -r dotenv/config -r tsconfig-paths/register ./src/index.ts", "semantic-release": "semantic-release", "lint": "eslint \"src/**/*.ts\"", "prepublishOnly": "npm run build", @@ -89,11 +90,11 @@ "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "lodash": "^4.17.21", + "masto": "^5.11.3", "node-cron": "^3.0.2", "node-fetch": "^2.6.7", "pluralize": "^8.0.0", "pretty-ms": "^7.0.1", - "puppeteer": "^19.3.0", "reflect-metadata": "^0.1.13", "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", diff --git a/src/watchers/index.ts b/src/watchers/index.ts index 1805e02..18a2678 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -1,13 +1,14 @@ import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; import { GitHubWatcher, GitHubWatcherOptions } from "@watchers/github"; +import { MastodonWatcher, MastodonWatcherOptions } from "@watchers/mastodon"; import { TypeMap } from "@utils/types"; -export type WatcherClasses = TwitterWatcher | GitHubWatcher; +export type WatcherClasses = TwitterWatcher | GitHubWatcher | MastodonWatcher; export type WatcherTypes = Lowercase; -export type WatcherOptions = TwitterWatcherOptions | GitHubWatcherOptions; +export type WatcherOptions = TwitterWatcherOptions | GitHubWatcherOptions | MastodonWatcherOptions; export type WatcherOptionMap = TypeMap; export type WatcherMap = { @@ -23,6 +24,7 @@ export type WatcherPair = [WatcherTypes, BaseWatcher]; const AVAILABLE_WATCHERS: Readonly = { twitter: options => new TwitterWatcher(options), github: options => new GitHubWatcher(options), + mastodon: options => new MastodonWatcher(options), }; export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { diff --git a/src/watchers/mastodon/index.ts b/src/watchers/mastodon/index.ts new file mode 100644 index 0000000..b72f460 --- /dev/null +++ b/src/watchers/mastodon/index.ts @@ -0,0 +1,54 @@ +import * as masto from "masto"; + +import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; + +export interface MastodonWatcherOptions extends BaseWatcherOptions { + url: string; + accessToken: string; +} + +export class MastodonWatcher extends BaseWatcher<"Mastodon"> { + private client: masto.mastodon.Client | null = null; + private account: masto.mastodon.v1.Account | null = null; + + constructor(private readonly options: MastodonWatcherOptions) { + super("Mastodon"); + } + + public async initialize(): Promise { + this.client = await masto.login({ + url: this.options.url, + accessToken: this.options.accessToken, + }); + + this.account = await this.client.v1.accounts.lookup({ + acct: "@sophia_dev@silicon.moe", + }); + + return Promise.resolve(undefined); + } + + protected async getFollowers(): Promise { + if (!this.client || !this.account) { + throw new Error("Mastodon watcher has not initialized"); + } + + const { id, followersCount } = this.account; + const result: PartialUser[] = []; + for await (const followers of this.client.v1.accounts.listFollowers(id, { limit: 80 })) { + const partialUsers = followers.map(follower => ({ + profileUrl: follower.url, + uniqueId: follower.acct, + displayName: follower.displayName, + userId: follower.id, + })); + + result.push(...partialUsers); + if (result.length >= followersCount) { + break; + } + } + + return result; + } +} diff --git a/yarn.lock b/yarn.lock index 844bdd4..8e6aa69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1470,6 +1470,17 @@ semver "^7.3.5" tar "^6.1.11" +"@mastojs/ponyfills@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@mastojs/ponyfills/-/ponyfills-1.0.4.tgz#22a0d51b2a79392001939950d124c316650c53a7" + integrity sha512-1NaIGmcU7OmyNzx0fk+cYeGTkdXlOJOSdetaC4pStVWsrhht2cdlYSAfe5NDW3FcUmcEm2vVceB9lcClN1RCxw== + dependencies: + "@types/node" "^18.11.17" + "@types/node-fetch" "^2.6.2" + abort-controller "^3.0.0" + form-data "^4.0.0" + node-fetch "^2.6.7" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2194,6 +2205,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34" integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ== +"@types/node@^18.11.17": + version "18.16.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" + integrity sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -2275,13 +2291,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" - integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.38.0": version "5.39.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz#778b2d9e7f293502c7feeea6c74dca8eb3e67511" @@ -2804,7 +2813,7 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bl@^4.0.3, bl@^4.1.0: +bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -2892,11 +2901,6 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - 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" @@ -2907,7 +2911,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.2.1, buffer@^5.5.0: +buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -2998,6 +3002,14 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3143,11 +3155,6 @@ chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -3510,7 +3517,7 @@ cronstrue@^2.20.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.20.0.tgz#709ef674ccf385f3c0e77c40a5141911e38b5d05" integrity sha512-cGGxqXGt+yf46nQw2nipLpQLaqlXbeAdcR2HoHYar91bpWRFViw16XCwbSLs0uwhzzYjHwN9BVw5SYIXNqIFwg== -cross-fetch@3.1.5, cross-fetch@^3.1.5: +cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -3561,7 +3568,7 @@ debounce@^1.2.0: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3679,11 +3686,6 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -devtools-protocol@0.0.1056733: - version "0.0.1056733" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz#55bb1d56761014cc221131cca5e6bad94eefb2b9" - integrity sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA== - dezalgo@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" @@ -3802,7 +3804,7 @@ encoding@^0.1.12, encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4002,6 +4004,11 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -4063,17 +4070,6 @@ extract-files@^9.0.0: resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4142,13 +4138,6 @@ fbjs@^3.0.0: setimmediate "^1.0.5" ua-parser-js "^0.7.30" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -4239,6 +4228,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + formdata-node@^4.3.1: version "4.4.1" resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" @@ -4260,11 +4258,6 @@ fromentries@^1.3.2: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -4344,6 +4337,16 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4548,6 +4551,16 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -4630,7 +4643,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -5008,7 +5021,7 @@ isomorphic-fetch@^3.0.0: node-fetch "^2.6.1" whatwg-fetch "^3.4.1" -isomorphic-ws@5.0.0: +isomorphic-ws@5.0.0, isomorphic-ws@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== @@ -6068,6 +6081,19 @@ marked@^4.0.10: resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.1.tgz#2f709a4462abf65a283f2453dc1c42ab177d302e" integrity sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw== +masto@^5.11.3: + version "5.11.3" + resolved "https://registry.yarnpkg.com/masto/-/masto-5.11.3.tgz#941b2587d624dd7526884821e52619a6c25f8d0c" + integrity sha512-GtSnrqm5fHPaaU0iwag4LCmvpp82rDng6yOZinmOJHHlUfo6Gnq5QY6x3lJCxCnsPIXpTu1yaX42bWrSQyoQPA== + dependencies: + "@mastojs/ponyfills" "^1.0.4" + change-case "^4.1.2" + eventemitter3 "^5.0.0" + isomorphic-ws "^5.0.0" + qs "^6.11.0" + semver "^7.3.7" + ws "^8.13.0" + meow@^8.0.0: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" @@ -6260,11 +6286,6 @@ minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -6688,6 +6709,11 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -7006,11 +7032,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -7123,11 +7144,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-all-reject-late@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" @@ -7173,11 +7189,6 @@ promzard@^0.3.0: dependencies: read "1" -proxy-from-env@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -7198,34 +7209,6 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" -puppeteer-core@19.3.0: - version "19.3.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.3.0.tgz#deba854f3dd3f74a04db200274827a67e200f39e" - integrity sha512-P8VAAOBnBJo/7DKJnj1b0K9kZBF2D8lkdL94CjJ+DZKCp182LQqYemPI9omUSZkh4bgykzXjZhaVR1qtddTTQg== - dependencies: - cross-fetch "3.1.5" - debug "4.3.4" - devtools-protocol "0.0.1056733" - extract-zip "2.0.1" - https-proxy-agent "5.0.1" - proxy-from-env "1.1.0" - rimraf "3.0.2" - tar-fs "2.1.1" - unbzip2-stream "1.4.3" - ws "8.10.0" - -puppeteer@^19.3.0: - version "19.3.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.3.0.tgz#defa8b6a6401b23cdc4f634c441b55d61f6b3d65" - integrity sha512-WJbi/ULaeuFOz7cfMgJlJCBAZiyqIFeQ6os4h5ex3PVTt2qosXgwI9eruFZqFAwJRv8x5pOuMhWR0aSRgyDqEg== - dependencies: - cosmiconfig "7.0.1" - devtools-protocol "0.0.1056733" - https-proxy-agent "5.0.1" - progress "2.0.3" - proxy-from-env "1.1.0" - puppeteer-core "19.3.0" - pvtsutils@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de" @@ -7248,6 +7231,13 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== +qs@^6.11.0: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + queue-lit@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.5.0.tgz#8197fdafda1edd615c8a0fc14c48353626e5160a" @@ -7327,7 +7317,7 @@ read@1, read@^1.0.7, read@~1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -7515,7 +7505,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -7690,6 +7680,15 @@ shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -8030,27 +8029,6 @@ swap-case@^2.0.2: dependencies: tslib "^2.0.3" -tar-fs@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - tar@^6.0.2: version "6.1.12" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" @@ -8401,14 +8379,6 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" integrity sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg== -unbzip2-stream@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -8734,16 +8704,16 @@ write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" - integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== - ws@8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +ws@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -8866,14 +8836,6 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 643c056dbf220a6540bcc8110a7411232a2bbea6 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 3 Jul 2023 03:29:57 +0100 Subject: [PATCH 128/140] docs: add mastodon to supported watchers table --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 75e2a8c..e2ea76e 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ services: |-----------|:--------:| | Twitter | ✅ | | GitHub | ✅ | +| Mastodon | ✅ | | Instagram | ❌ | | TikTok | ❌ | | YouTube | ❌ | From d149b33d93c14a6d5ab93f157c01491c9ed1dce0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Jul 2023 02:31:44 +0000 Subject: [PATCH 129/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.15](https://github.com/async3619/cage/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2023-07-03) ### Features ✨ * **mastodon:** implement mastodon watcher ([dd0c7e3](https://github.com/async3619/cage/commit/dd0c7e3c2bcd5a90e402ab05c9105fbefa7fa90e)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b342438..7fffae7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.14", + "version": "1.0.0-dev.15", "license": "MIT", "publishConfig": { "access": "public" From b097f4c18603096cd7639404f143aebb799676e5 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 3 Jul 2023 10:25:12 +0100 Subject: [PATCH 130/140] feat(bluesky): implement bluesky watcher --- config.schema.json | 24 ++++++++ package.json | 1 + src/watchers/bluesky/index.ts | 58 +++++++++++++++++++ src/watchers/index.ts | 11 +++- yarn.lock | 102 ++++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/watchers/bluesky/index.ts diff --git a/config.schema.json b/config.schema.json index e43846e..e03b841 100644 --- a/config.schema.json +++ b/config.schema.json @@ -69,6 +69,30 @@ "url" ], "additionalProperties": false + }, + "bluesky": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bluesky" + }, + "service": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password", + "type" + ], + "additionalProperties": false } }, "additionalProperties": false diff --git a/package.json b/package.json index 7fffae7..2c9a2b3 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "typescript": "^4.9.3" }, "dependencies": { + "@atproto/api": "^0.3.13", "@octokit/rest": "^19.0.5", "@slack/webhook": "^6.1.0", "@twitter-api-v2/plugin-rate-limit": "^1.1.0", diff --git a/src/watchers/bluesky/index.ts b/src/watchers/bluesky/index.ts new file mode 100644 index 0000000..ed58f47 --- /dev/null +++ b/src/watchers/bluesky/index.ts @@ -0,0 +1,58 @@ +import { BskyAgent } from "@atproto/api"; + +import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; + +export interface BlueSkyWatcherOptions extends BaseWatcherOptions { + service?: string; + email: string; + password: string; +} + +export class BlueSkyWatcher extends BaseWatcher<"BlueSky"> { + private agent: BskyAgent | null = null; + + constructor(private readonly options: BlueSkyWatcherOptions) { + super("BlueSky"); + } + + public async initialize(): Promise { + this.agent = new BskyAgent({ service: this.options.service || "https://bsky.social" }); + + await this.agent.login({ + identifier: this.options.email, + password: this.options.password, + }); + } + + protected async getFollowers(): Promise { + if (!this.agent) { + throw new Error("Bluesky watcher has not initialized"); + } + + let cursor: string | undefined; + const result: PartialUser[] = []; + while (true) { + const { data } = await this.agent.getFollowers({ + actor: "lunamoth.bsky.social", + limit: 100, + cursor, + }); + + cursor = data.cursor; + for (const follower of data.followers) { + result.push({ + uniqueId: follower.did, + userId: follower.handle, + displayName: follower.displayName || follower.handle, + profileUrl: `https://bsky.app/profile/${follower.handle}`, + }); + } + + if (data.followers.length < 100) { + break; + } + } + + return result; + } +} diff --git a/src/watchers/index.ts b/src/watchers/index.ts index 18a2678..b4675de 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -2,13 +2,19 @@ import { BaseWatcher, BaseWatcherOptions } from "@watchers/base"; import { TwitterWatcher, TwitterWatcherOptions } from "@watchers/twitter"; import { GitHubWatcher, GitHubWatcherOptions } from "@watchers/github"; import { MastodonWatcher, MastodonWatcherOptions } from "@watchers/mastodon"; +import { BlueSkyWatcher, BlueSkyWatcherOptions } from "@watchers/bluesky"; import { TypeMap } from "@utils/types"; -export type WatcherClasses = TwitterWatcher | GitHubWatcher | MastodonWatcher; +export type WatcherClasses = TwitterWatcher | GitHubWatcher | MastodonWatcher | BlueSkyWatcher; export type WatcherTypes = Lowercase; -export type WatcherOptions = TwitterWatcherOptions | GitHubWatcherOptions | MastodonWatcherOptions; +export type WatcherOptions = + | TwitterWatcherOptions + | GitHubWatcherOptions + | MastodonWatcherOptions + | BlueSkyWatcherOptions; + export type WatcherOptionMap = TypeMap; export type WatcherMap = { @@ -25,6 +31,7 @@ const AVAILABLE_WATCHERS: Readonly = { twitter: options => new TwitterWatcher(options), github: options => new GitHubWatcher(options), mastodon: options => new MastodonWatcher(options), + bluesky: options => new BlueSkyWatcher(options), }; export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { diff --git a/yarn.lock b/yarn.lock index 8e6aa69..4de5fff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,6 +40,67 @@ dependencies: node-fetch "^2.6.1" +"@atproto/api@^0.3.13": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.13.tgz#e5ccaa83bb909e662286cdf74a77a76de6562a47" + integrity sha512-smDlomgipca16G+jKXAZSMfsAmA5wG8WR3Z1dj29ZShVJlhs6+HHdxX7dWVDYEdSeb2rp/wyHN/tQhxGDAkz/g== + dependencies: + "@atproto/common-web" "*" + "@atproto/uri" "*" + "@atproto/xrpc" "*" + tlds "^1.234.0" + typed-emitter "^2.1.0" + +"@atproto/common-web@*": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.1.0.tgz#5529fa66f9533aa00cfd13f0a25757df7b26bd3d" + integrity sha512-qD6xF60hvH+cP++fk/mt+0S9cxs94KsK+rNWypNlgnlp7r9By4ltXwtDSR/DNTA8mwDeularUno4VbTd2IWIzA== + dependencies: + multiformats "^9.6.4" + uint8arrays "3.0.0" + zod "^3.14.2" + +"@atproto/identifier@*": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75" + integrity sha512-3LV7+4E6S0k8Rru7NBkyDF6Zf6NHVUXVS9d4l9fiXWMC49ghZMjq0vPmz80xjG1rRuFdJFbpRf4ApFciGxLIyQ== + dependencies: + "@atproto/common-web" "*" + +"@atproto/lexicon@*": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.1.0.tgz#e7784cc868c734314d5bf9af83487aba7ccae0b3" + integrity sha512-Iy+gV9w42xLhrZrmcbZh7VFoHjXuzWvecGHIfz44owNjjv7aE/d2P5BbOX/XicSkmQ8Qkpg0BqwYDD1XBVS+DQ== + dependencies: + "@atproto/common-web" "*" + "@atproto/identifier" "*" + "@atproto/nsid" "*" + "@atproto/uri" "*" + iso-datestring-validator "^2.2.2" + multiformats "^9.6.4" + zod "^3.14.2" + +"@atproto/nsid@*": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4" + integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw== + +"@atproto/uri@*": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@atproto/uri/-/uri-0.0.2.tgz#c6d3788e6f12d66ba72690d2d70fe6c291b4acfb" + integrity sha512-/6otLZF7BLpT9suSdHuXLbL12nINcWPsLmcOI+dctqovWUjH+XIRVNXDQgBYSrPVetxMiknuEwWelmnA33AEXg== + dependencies: + "@atproto/identifier" "*" + "@atproto/nsid" "*" + +"@atproto/xrpc@*": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.1.0.tgz#798569095538ac060475ae51f1b4c071ff8776d6" + integrity sha512-LhBeZkQwPezjEtricGYnG62udFglOqlnmMSS0KyWgEAPi4KMp4H2F4jNoXcf5NPtZ9S4N4hJaErHX4PJYv2lfA== + dependencies: + "@atproto/lexicon" "*" + zod "^3.14.2" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -5013,6 +5074,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +iso-datestring-validator@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" + integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== + isomorphic-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" @@ -6315,6 +6381,11 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multiformats@^9.4.2, multiformats@^9.6.4: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -7531,6 +7602,13 @@ rxjs@^6.5.1: dependencies: tslib "^1.9.0" +rxjs@^7.5.2: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + rxjs@^7.5.5: version "7.6.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2" @@ -8134,6 +8212,11 @@ title-case@^3.0.3: dependencies: tslib "^2.0.3" +tlds@^1.234.0: + version "1.240.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.240.0.tgz#3d3d776d97aa079e43ef4d2f9ef9845e55cff08e" + integrity sha512-1OYJQenswGZSOdRw7Bql5Qu7uf75b+F3HFBXbqnG/ifHa0fev1XcG+3pJf3pA/KC6RtHQzfKgIf1vkMlMG7mtQ== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8329,6 +8412,13 @@ type-fest@^2.13.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== +typed-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" + integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== + optionalDependencies: + rxjs "^7.5.2" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -8379,6 +8469,13 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" integrity sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg== +uint8arrays@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" + integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== + dependencies: + multiformats "^9.4.2" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -8845,3 +8942,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.14.2: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== From 50444055334b31d28f2823740add0553e2378a9e Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 3 Jul 2023 10:43:21 +0100 Subject: [PATCH 131/140] fix(core): fix a bug that could not retrieve old data due to maximum depth --- src/repositories/models/user.ts | 5 +---- src/tasks/rename.spec.ts | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/repositories/models/user.ts b/src/repositories/models/user.ts index 851d2cf..ab0ae22 100644 --- a/src/repositories/models/user.ts +++ b/src/repositories/models/user.ts @@ -1,4 +1,4 @@ -import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, RelationId, UpdateDateColumn } from "typeorm"; +import { BaseEntity, Column, CreateDateColumn, Entity, OneToMany, UpdateDateColumn } from "typeorm"; import { UserLog } from "@root/repositories/models/user-log"; @@ -33,7 +33,4 @@ export class User extends BaseEntity { @OneToMany(() => UserLog, followerLog => followerLog.user, { cascade: true }) public userLogs!: UserLog[]; - - @RelationId((follower: User) => follower.userLogs) - public userLogIds!: number[]; } diff --git a/src/tasks/rename.spec.ts b/src/tasks/rename.spec.ts index 28b97b4..092a570 100644 --- a/src/tasks/rename.spec.ts +++ b/src/tasks/rename.spec.ts @@ -18,7 +18,6 @@ function createMockUser(userId: string, displayName: string): Omit Date: Mon, 3 Jul 2023 09:50:10 +0000 Subject: [PATCH 132/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.16](https://github.com/async3619/cage/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2023-07-03) ### Features ✨ * **bluesky:** implement bluesky watcher ([b097f4c](https://github.com/async3619/cage/commit/b097f4c18603096cd7639404f143aebb799676e5)) ### Bug Fixes 🐞 * **core:** fix a bug that could not retrieve old data due to maximum depth ([5044405](https://github.com/async3619/cage/commit/50444055334b31d28f2823740add0553e2378a9e)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c9a2b3..e8b89a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.15", + "version": "1.0.0-dev.16", "license": "MIT", "publishConfig": { "access": "public" From f3ec6419284fc9272369b176749fce6410fef2d2 Mon Sep 17 00:00:00 2001 From: Sophia Date: Mon, 3 Jul 2023 11:00:05 +0100 Subject: [PATCH 133/140] fix(bluesky): use provided users did instead of hard coded one --- src/watchers/bluesky/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/watchers/bluesky/index.ts b/src/watchers/bluesky/index.ts index ed58f47..148c05e 100644 --- a/src/watchers/bluesky/index.ts +++ b/src/watchers/bluesky/index.ts @@ -10,6 +10,7 @@ export interface BlueSkyWatcherOptions extends BaseWatcherOptions { private agent: BskyAgent | null = null; + private userId: string | null = null; constructor(private readonly options: BlueSkyWatcherOptions) { super("BlueSky"); @@ -18,22 +19,24 @@ export class BlueSkyWatcher extends BaseWatcher<"BlueSky"> { public async initialize(): Promise { this.agent = new BskyAgent({ service: this.options.service || "https://bsky.social" }); - await this.agent.login({ + const response = await this.agent.login({ identifier: this.options.email, password: this.options.password, }); + + this.userId = response.data.did; } protected async getFollowers(): Promise { - if (!this.agent) { + if (!this.agent || !this.userId) { throw new Error("Bluesky watcher has not initialized"); } - let cursor: string | undefined; + let cursor: string | undefined = undefined; const result: PartialUser[] = []; while (true) { const { data } = await this.agent.getFollowers({ - actor: "lunamoth.bsky.social", + actor: this.userId, limit: 100, cursor, }); From 044765588d6d34acafe5c771e6e9968470ef0569 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Jul 2023 10:08:31 +0000 Subject: [PATCH 134/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.17](https://github.com/async3619/cage/compare/v1.0.0-dev.16...v1.0.0-dev.17) (2023-07-03) ### Bug Fixes 🐞 * **bluesky:** use provided users did instead of hard coded one ([f3ec641](https://github.com/async3619/cage/commit/f3ec6419284fc9272369b176749fce6410fef2d2)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8b89a2..192e9ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.16", + "version": "1.0.0-dev.17", "license": "MIT", "publishConfig": { "access": "public" From 18e63749aadeece022d8d21cc0ed9184ec04a7af Mon Sep 17 00:00:00 2001 From: "Sophia (Turner)" Date: Fri, 1 Nov 2024 01:36:34 +0900 Subject: [PATCH 135/140] fix(twitter): make twitter watcher to use rettiwt-api package instead of official one (#4) --- .github/workflows/ci.yml | 2 +- .github/workflows/docker-deploy.yml | 2 +- Dockerfile | 6 +- config.schema.json | 4 +- package.json | 3 +- src/watchers/twitter/index.ts | 40 ++++---- yarn.lock | 141 +++++++++++++++++++++++----- 7 files changed, 145 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a8608f..5e01eb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Install and Build uses: actions/setup-node@v3 with: - node-version: "18.x" + node-version: "22.x" - name: Install yarn run: | diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 53c6dff..72c3bed 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -29,7 +29,7 @@ jobs: - name: Install and Build uses: actions/setup-node@v3 with: - node-version: "18.x" + node-version: "22.x" - name: Install yarn run: | diff --git a/Dockerfile b/Dockerfile index 288708d..9f75887 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine as builder +FROM node:22-alpine as builder RUN apk add --update --no-cache curl git openssh openssl @@ -18,7 +18,7 @@ ENV NODE_ENV ${NODE_ENV} RUN ["yarn", "build"] -FROM node:18-alpine as prod-deps +FROM node:22-alpine as prod-deps USER node WORKDIR /home/node @@ -35,7 +35,7 @@ ENV NODE_ENV ${NODE_ENV} CMD [ "node", "dist/src/index" ] -FROM node:18-alpine as production +FROM node:22-alpine as production USER node WORKDIR /home/node diff --git a/config.schema.json b/config.schema.json index e03b841..f42106a 100644 --- a/config.schema.json +++ b/config.schema.json @@ -18,7 +18,7 @@ "type": "string", "const": "twitter" }, - "bearerToken": { + "apiKey": { "type": "string" }, "username": { @@ -26,7 +26,7 @@ } }, "required": [ - "bearerToken", + "apiKey", "type", "username" ], diff --git a/package.json b/package.json index 192e9ec..955bd0d 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "@atproto/api": "^0.3.13", "@octokit/rest": "^19.0.5", "@slack/webhook": "^6.1.0", - "@twitter-api-v2/plugin-rate-limit": "^1.1.0", "@urql/core": "^3.0.5", "ajv": "^8.11.2", "better-ajv-errors": "^1.2.0", @@ -97,10 +96,10 @@ "pluralize": "^8.0.0", "pretty-ms": "^7.0.1", "reflect-metadata": "^0.1.13", + "rettiwt-api": "^4.1.4", "set-cookie-parser": "^2.5.1", "sqlite3": "^5.1.2", "strip-ansi": "^6.0.1", - "twitter-api-v2": "^1.12.9", "typeorm": "^0.3.10", "update-notifier": "^5.1.0" } diff --git a/src/watchers/twitter/index.ts b/src/watchers/twitter/index.ts index a7055a8..73eaeb7 100644 --- a/src/watchers/twitter/index.ts +++ b/src/watchers/twitter/index.ts @@ -1,29 +1,26 @@ -import { TwitterApi, TwitterApiReadOnly } from "twitter-api-v2"; -import { TwitterApiRateLimitPlugin } from "@twitter-api-v2/plugin-rate-limit"; +import { Cursor, Rettiwt, User } from "rettiwt-api"; import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; export interface TwitterWatcherOptions extends BaseWatcherOptions { - bearerToken: string; + apiKey: string; username: string; } export class TwitterWatcher extends BaseWatcher<"Twitter"> { - private readonly twitterClient: TwitterApiReadOnly; + private readonly twitterClient: Rettiwt; private readonly currentUserName: string; + private currentUserId: string | null = null; - public constructor({ bearerToken, username }: TwitterWatcherOptions) { + public constructor({ apiKey, username }: TwitterWatcherOptions) { super("Twitter"); - this.twitterClient = new TwitterApi(bearerToken, { plugins: [new TwitterApiRateLimitPlugin()] }).readOnly; + this.twitterClient = new Rettiwt({ apiKey }); this.currentUserName = username; } public async initialize() { - const { data } = await this.twitterClient.v2.userByUsername(this.currentUserName, { - "user.fields": ["id"], - }); - + const data = await this.twitterClient.user.details(this.currentUserName); if (!data) { throw new Error("Failed to get user id"); } @@ -37,16 +34,23 @@ export class TwitterWatcher extends BaseWatcher<"Twitter"> { throw new Error("Watcher is not initialized"); } - const followers = await this.twitterClient.v2.followers(this.currentUserId, { - max_results: 1000, - "user.fields": ["id", "name", "username", "profile_image_url"], - }); + const users: User[] = []; + let cursor: Cursor | null = null; + while (true) { + const followers = await this.twitterClient.user.followers(this.currentUserId, 100, cursor?.value); + if (!followers.next.value || followers.list.length === 0) { + break; + } + + users.push(...followers.list); + cursor = followers.next; + } - return followers.data.map(user => ({ + return users.map(user => ({ uniqueId: user.id, - displayName: user.name, - userId: user.username, - profileUrl: `https://twitter.com/${user.username}`, + displayName: user.fullName, + userId: user.userName, + profileUrl: `https://twitter.com/${user.userName}`, })); } } diff --git a/yarn.lock b/yarn.lock index 4de5fff..4bea677 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2102,11 +2102,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@twitter-api-v2/plugin-rate-limit@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@twitter-api-v2/plugin-rate-limit/-/plugin-rate-limit-1.1.0.tgz#a80bbc86f4629575c13ab2f688dafe8b7eb05058" - integrity sha512-5nUk0w0s1yzzMe+NLN5BlKzwgl9OHkF1Makb5Xje1ntCO1H1fGq0qli80WRvcrpcwKZcSckRkXd4HEksh2LZ6g== - "@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" @@ -2333,6 +2328,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/validator@^13.11.8": + version "13.12.2" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.2.tgz#760329e756e18a4aab82fc502b51ebdfebbe49f5" + integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== + "@types/ws@^8.0.0": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -2511,6 +2511,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.0.2: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" @@ -2726,6 +2733,15 @@ auto-bind@~4.0.0: resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== +axios@1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.3.tgz#7f50f23b3aa246eff43c54834272346c396613f4" + integrity sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -3130,6 +3146,14 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.0.0, chalk@^2.3.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3139,14 +3163,6 @@ chalk@^2.0.0, chalk@^2.3.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.1.0.tgz#c4b4a62bfb6df0eeeb5dbc52e6a9ecaff14b9976" @@ -3243,6 +3259,15 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-validator@0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" + integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.10.53" + validator "^13.9.0" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -3417,6 +3442,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== + commander@^9.0.0, commander@^9.4.0, commander@^9.4.1: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" @@ -3535,6 +3565,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookiejar@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -4275,24 +4310,29 @@ follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.0: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + form-data-encoder@^1.7.1: version "1.7.2" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== +form-data@4.0.0, form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -4704,6 +4744,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" + integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== + dependencies: + agent-base "^7.0.2" + debug "4" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -5870,6 +5918,11 @@ libnpmversion@^3.0.7: proc-log "^2.0.0" semver "^7.3.7" +libphonenumber-js@^1.10.53: + version "1.11.12" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.12.tgz#e108a4632048255f30b872bb1abbb3356f290fbb" + integrity sha512-QkJn9/D7zZ1ucvT++TQSvZuSA2xAWeUytU+DiEQwbPKLyrDpvbul2AFs1CGbRAPpSCCk47aRAb5DX5mmcayp4g== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -7260,6 +7313,11 @@ promzard@^0.3.0: dependencies: read "1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -7566,6 +7624,37 @@ retry@^0.13.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== +rettiwt-api@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/rettiwt-api/-/rettiwt-api-4.1.4.tgz#76d0eba3c86eb3fc2c4c27f7a1ce78d52dc857de" + integrity sha512-0NXu6KOy8iPiGHreib2cu8Uf7OWGMgiyR87KT9XkLRr5eiKgbVZxdq+Rv13CYLUJhiTz3qVNYPy5ai6wZR6shg== + dependencies: + axios "1.6.3" + chalk "4.1.2" + class-validator "0.14.1" + commander "11.1.0" + https-proxy-agent "7.0.2" + rettiwt-auth "2.1.0" + rettiwt-core "4.3.3" + +rettiwt-auth@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/rettiwt-auth/-/rettiwt-auth-2.1.0.tgz#43fedd40cab5b775b92a1bef740d7b2a36553463" + integrity sha512-N3G1kX/4TFYvUumxcACHO9ngVnhM+DhpuRn2/JxWlgHuztAQShbO9ux3hfMducCqzzjdcMLPJhrEDt9fwSbpaQ== + dependencies: + axios "1.6.3" + commander "11.1.0" + cookiejar "2.1.4" + https-proxy-agent "7.0.2" + +rettiwt-core@4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/rettiwt-core/-/rettiwt-core-4.3.3.tgz#5cf00d624aef804391415e3993ead539154b7125" + integrity sha512-3GCCqsQSVn259vTSSkgL/lMQZtfmkNNffuIZfAHl/0xSzXbhTlrGySQGuRE+XU3ceESaMdQfxN7q2UhXNa8eaQ== + dependencies: + axios "1.6.3" + form-data "4.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -8355,11 +8444,6 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -twitter-api-v2@^1.12.9: - version "1.12.9" - resolved "https://registry.yarnpkg.com/twitter-api-v2/-/twitter-api-v2-1.12.9.tgz#447fc4efb693bbb7983a6dcec2de5da7e0f27648" - integrity sha512-nmKwcUiXgoEfcOtAH/hYojy0oSxbouWvvNsHVphiHSW9x9aus2Jdg9/NRB2Gzyq5G6xl+EkjUlEWwBQjp1FOfg== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -8640,6 +8724,11 @@ validate-npm-package-name@^4.0.0: dependencies: builtins "^5.0.0" +validator@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + value-or-promise@1.0.11, value-or-promise@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" From 962f542773fe686c1f0cc79461db75e6403917a8 Mon Sep 17 00:00:00 2001 From: "Sophia (Turner)" Date: Fri, 1 Nov 2024 01:39:26 +0900 Subject: [PATCH 136/140] chore: correct nodejs version for `rettiwt-api` (#5) --- .github/workflows/ci.yml | 2 +- .github/workflows/docker-deploy.yml | 2 +- Dockerfile | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e01eb9..ab27bbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Install and Build uses: actions/setup-node@v3 with: - node-version: "22.x" + node-version: "20.11.1" - name: Install yarn run: | diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 72c3bed..5916a4e 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -29,7 +29,7 @@ jobs: - name: Install and Build uses: actions/setup-node@v3 with: - node-version: "22.x" + node-version: "20.11.1" - name: Install yarn run: | diff --git a/Dockerfile b/Dockerfile index 9f75887..40a94e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine as builder +FROM node:20.18-alpine as builder RUN apk add --update --no-cache curl git openssh openssl @@ -18,7 +18,7 @@ ENV NODE_ENV ${NODE_ENV} RUN ["yarn", "build"] -FROM node:22-alpine as prod-deps +FROM node:20.18-alpine as prod-deps USER node WORKDIR /home/node @@ -35,7 +35,7 @@ ENV NODE_ENV ${NODE_ENV} CMD [ "node", "dist/src/index" ] -FROM node:22-alpine as production +FROM node:20.18-alpine as production USER node WORKDIR /home/node From 6d68725f1b9a6040ef2ad3b3d062867534c72ced Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 31 Oct 2024 19:55:55 +0000 Subject: [PATCH 137/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.18](https://github.com/async3619/cage/compare/v1.0.0-dev.17...v1.0.0-dev.18) (2024-10-31) ### Bug Fixes 🐞 * **twitter:** make twitter watcher to use rettiwt-api package instead of official one ([#4](https://github.com/async3619/cage/issues/4)) ([18e6374](https://github.com/async3619/cage/commit/18e63749aadeece022d8d21cc0ed9184ec04a7af)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 955bd0d..5a8117f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.17", + "version": "1.0.0-dev.18", "license": "MIT", "publishConfig": { "access": "public" From 9a8a474f20f56b34114f305b2709459d4bdc15ca Mon Sep 17 00:00:00 2001 From: "Sophia (Turner)" Date: Fri, 1 Nov 2024 05:18:29 +0900 Subject: [PATCH 138/140] docs: update twitter watcher description (#6) --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e2ea76e..2825050 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,13 @@ specify watching interval in millisecond format. minimal value is `60000`. #### twitter +Internally, Cage uses [Rettiwt-API](https://github.com/Rishikant181/Rettiwt-API) to get the followers list. so you need to get the API Key with extensions that provided by the author. you can see the instruction [here](https://github.com/Rishikant181/Rettiwt-API#1-using-a-browser-recommended). + ```json5 { "type": "twitter", // required - "bearerToken": "bearer token of your twitter app" // string, required + "apiKey": "API Key that retrieved with X Auth Helper extension", // string, required + "username": "your twitter username", // string } ``` From a679f2314ad9c7142281aad93ce0b7f5be225232 Mon Sep 17 00:00:00 2001 From: "Sophia (Turner)" Date: Fri, 1 Nov 2024 15:15:29 +0900 Subject: [PATCH 139/140] feat(instagram): implement instagram watcher (#7) --- README.md | 18 +- config.schema.json | 28 ++ package.json | 1 + src/utils/types.ts | 2 + src/watchers/index.ts | 7 +- src/watchers/instagram/index.ts | 69 +++++ yarn.lock | 453 +++++++++++++++++++++++++++++++- 7 files changed, 564 insertions(+), 14 deletions(-) create mode 100644 src/watchers/instagram/index.ts diff --git a/README.md b/README.md index 2825050..7f82eb7 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ services: | Twitter | ✅ | | GitHub | ✅ | | Mastodon | ✅ | -| Instagram | ❌ | +| Instagram | ✅ | | TikTok | ❌ | | YouTube | ❌ | | Twitch | ❌ | @@ -149,6 +149,22 @@ watcher configuration for GitHub service. } ``` +#### instagram + +watcher configuration for Instagram service. + +```json5 +{ + "type": "instagram", // required + "username": "your instagram username", // string, required + "password": "your instagram password", // string, required + "targetUserName": "target instagram username you want to crawl followers", // string, required + "requestDelay": 1000 // number, optional, default: 1000 + // requestDelay is the delay time between each request to instagram server. + // this is to prevent getting banned from instagram server. +} +``` + ### notifiers: `Record` (required) #### discord diff --git a/config.schema.json b/config.schema.json index f42106a..37834ce 100644 --- a/config.schema.json +++ b/config.schema.json @@ -93,6 +93,34 @@ "type" ], "additionalProperties": false + }, + "instagram": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "instagram" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "targetUserName": { + "type": "string" + }, + "requestDelay": { + "type": "number" + } + }, + "required": [ + "password", + "targetUserName", + "type", + "username" + ], + "additionalProperties": false } }, "additionalProperties": false diff --git a/package.json b/package.json index 5a8117f..0744bcb 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "fs-extra": "^11.1.0", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", + "instagram-private-api": "^1.46.1", "lodash": "^4.17.21", "masto": "^5.11.3", "node-cron": "^3.0.2", diff --git a/src/utils/types.ts b/src/utils/types.ts index 8a6ac58..0affd19 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,7 @@ import { Logger } from "@utils/logger"; +export type Resolve = T extends Promise ? U : T; + export type SelectOnly = { [Key in keyof Required as Required[Key] extends Type ? Key : never]: Record[Key]; }; diff --git a/src/watchers/index.ts b/src/watchers/index.ts index b4675de..64a486e 100644 --- a/src/watchers/index.ts +++ b/src/watchers/index.ts @@ -5,15 +5,17 @@ import { MastodonWatcher, MastodonWatcherOptions } from "@watchers/mastodon"; import { BlueSkyWatcher, BlueSkyWatcherOptions } from "@watchers/bluesky"; import { TypeMap } from "@utils/types"; +import { InstagramWatcher, InstagramWatcherOptions } from "@watchers/instagram"; -export type WatcherClasses = TwitterWatcher | GitHubWatcher | MastodonWatcher | BlueSkyWatcher; +export type WatcherClasses = TwitterWatcher | GitHubWatcher | MastodonWatcher | BlueSkyWatcher | InstagramWatcher; export type WatcherTypes = Lowercase; export type WatcherOptions = | TwitterWatcherOptions | GitHubWatcherOptions | MastodonWatcherOptions - | BlueSkyWatcherOptions; + | BlueSkyWatcherOptions + | InstagramWatcherOptions; export type WatcherOptionMap = TypeMap; @@ -32,6 +34,7 @@ const AVAILABLE_WATCHERS: Readonly = { github: options => new GitHubWatcher(options), mastodon: options => new MastodonWatcher(options), bluesky: options => new BlueSkyWatcher(options), + instagram: options => new InstagramWatcher(options), }; export const createWatcher = (options: BaseWatcherOptions): BaseWatcher => { diff --git a/src/watchers/instagram/index.ts b/src/watchers/instagram/index.ts new file mode 100644 index 0000000..d390e32 --- /dev/null +++ b/src/watchers/instagram/index.ts @@ -0,0 +1,69 @@ +import { BaseWatcher, BaseWatcherOptions, PartialUser } from "@watchers/base"; +import { IgApiClient } from "instagram-private-api"; +import { Resolve, sleep } from "@utils"; + +export interface InstagramWatcherOptions extends BaseWatcherOptions { + username: string; + password: string; + targetUserName: string; + requestDelay?: number; +} + +export class InstagramWatcher extends BaseWatcher<"Instagram"> { + private readonly client = new IgApiClient(); + + private readonly username: string; + private readonly password: string; + private readonly targetUserName: string; + private readonly requestDelay: number; + + private loggedInUser: Resolve> | null = null; + + public constructor({ username, password, targetUserName, requestDelay = 1000 }: InstagramWatcherOptions) { + super("Instagram"); + + this.username = username; + this.password = password; + this.targetUserName = targetUserName; + this.requestDelay = requestDelay; + } + + public async initialize() { + this.client.state.generateDevice(this.username); + await this.client.simulate.preLoginFlow(); + + this.loggedInUser = await this.client.account.login(this.username, this.password); + + this.logger.verbose("Successfully initialized with user name {}", [this.loggedInUser.username]); + } + + protected async getFollowers() { + // get followers + const id = await this.client.user.getIdByUsername(this.targetUserName); + if (!id) { + throw new Error("Failed to get user id"); + } + + const followersFeed = this.client.feed.accountFollowers(id); + const followers: PartialUser[] = []; + while (true) { + const items = await followersFeed.items(); + followers.push( + ...items.map(user => ({ + uniqueId: user.pk.toString(), + displayName: user.full_name, + userId: user.username, + profileUrl: `https://instagram.com/${user.username}`, + })), + ); + + if (!followersFeed.isMoreAvailable()) { + break; + } + + await sleep(this.requestDelay); + } + + return followers; + } +} diff --git a/yarn.lock b/yarn.lock index 4bea677..f6a9033 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1516,6 +1516,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@lifeomic/attempt@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@lifeomic/attempt/-/attempt-3.1.0.tgz#7fc703559177b81a008b9d263e3d9a001d11d08a" + integrity sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw== + "@mapbox/node-pre-gyp@^1.0.0": version "1.0.10" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" @@ -2135,6 +2140,21 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bluebird@*": + version "3.5.42" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.42.tgz#7ec05f1ce9986d920313c1377a5662b1b563d366" + integrity sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A== + +"@types/caseless@*": + version "0.12.5" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" + integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== + +"@types/chance@^1.0.2": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.6.tgz#2fe3de58742629602c3fbab468093b27207f04ad" + integrity sha512-V+pm3stv1Mvz8fSKJJod6CglNGVqEQ6OyuqitoDkWywEODM/eJd1eSuIp9xt6DrX8BWZ2eDSIzbw1tPCUTvGbQ== + "@types/configstore@*": version "6.0.0" resolved "https://registry.yarnpkg.com/@types/configstore/-/configstore-6.0.0.tgz#a49bb7d86a6cd1b0bb486fb336eec6966530aa13" @@ -2298,6 +2318,24 @@ resolved "https://registry.yarnpkg.com/@types/replace-ext/-/replace-ext-2.0.0.tgz#c64764557739c75754eac4c7f23162fd09526ac3" integrity sha512-9aGCj2qmL1YHLXfqfzwP1h7fDfeB4l0gSXRndq+20lJ+kljS3/ngOpcn1RJGhZCe5C2JzWu07nElCKcJ6iAJbg== +"@types/request-promise@^4.1.43": + version "4.1.51" + resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.51.tgz#a6bb43289569de84055073757d37a8fd6146bfe3" + integrity sha512-qVcP9Fuzh9oaAh8oPxiSoWMFGnWKkJDknnij66vi09Yiy62bsSDqtd+fG5kIM9wLLgZsRP3Y6acqj9O/v2ZtRw== + dependencies: + "@types/bluebird" "*" + "@types/request" "*" + +"@types/request@*": + version "2.48.12" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" + integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/retry@0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" @@ -2315,6 +2353,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/update-notifier@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-6.0.1.tgz#b1891f56be7ef4f124058bb6ca6934a7da524c78" @@ -2535,7 +2578,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2709,6 +2752,13 @@ asap@^2.0.0, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + asn1js@^3.0.1, asn1js@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" @@ -2718,6 +2768,11 @@ asn1js@^3.0.1, asn1js@^3.0.5: pvutils "^1.1.3" tslib "^2.4.0" +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -2733,6 +2788,16 @@ auto-bind@~4.0.0: resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + axios@1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.3.tgz#7f50f23b3aa246eff43c54834272346c396613f4" @@ -2857,6 +2922,13 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + before-after-hook@^2.2.0: version "2.2.3" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" @@ -2873,6 +2945,11 @@ better-ajv-errors@^1.2.0: jsonpointer "^5.0.0" leven "^3.1.0 < 4" +bignumber.js@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + bin-links@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" @@ -2899,6 +2976,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bluebird@^3.5.0, bluebird@^3.7.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + bottleneck@^2.18.1: version "2.19.5" resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" @@ -3146,6 +3228,11 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -3173,6 +3260,11 @@ chalk@^5.0.1: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.1.2.tgz#d957f370038b75ac572471e83be4c5ca9f8e8c45" integrity sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ== +chance@^1.0.18: + version "1.1.12" + resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.12.tgz#6a263cf241674af50a1b903357f9d328a6f252fb" + integrity sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q== + change-case-all@1.0.14: version "1.0.14" resolved "https://registry.yarnpkg.com/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1" @@ -3259,6 +3351,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-transformer@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.3.2.tgz#779ef5f124784324b40f8e927c774bd96cdecd4b" + integrity sha512-9QY6QXBH/+Gt1C3HBmJCrgY6+EFpIa6aLjfDnlXFx0zQl/HjrCE7qoaI0srNrxpMIfsobCpgUdDG5JYtJOpVsw== + class-validator@0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" @@ -3435,7 +3532,7 @@ columnify@^1.6.0: strip-ansi "^6.0.1" wcwidth "^1.0.0" -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3570,6 +3667,11 @@ cookiejar@2.1.4: resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -3639,6 +3741,13 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + dataloader@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.1.0.tgz#c69c538235e85e7ac6c6c444bae8ecabf5de9df7" @@ -3866,6 +3975,14 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -4147,6 +4264,11 @@ expect@^29.3.1: jest-message-util "^29.3.1" jest-util "^29.3.1" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -4166,6 +4288,16 @@ extract-files@^9.0.0: resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4315,6 +4447,11 @@ follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + form-data-encoder@^1.7.1: version "1.7.2" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" @@ -4329,6 +4466,16 @@ form-data@4.0.0, form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^2.5.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2" + integrity sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + safe-buffer "^5.2.1" + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -4338,6 +4485,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + formdata-node@^4.3.1: version "4.4.1" resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" @@ -4472,6 +4628,13 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + git-log-parser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/git-log-parser/-/git-log-parser-1.2.0.tgz#2e6a4c1b13fc00028207ba795a7ac31667b9fd4a" @@ -4637,6 +4800,19 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -4744,6 +4920,15 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-proxy-agent@7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" @@ -4803,6 +4988,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +image-size@^0.7.3: + version "0.7.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.7.5.tgz#269f357cf5797cb44683dfa99790e54c705ead04" + integrity sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g== + immutable@~3.7.6: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" @@ -4911,6 +5101,33 @@ inquirer@^8.0.0: through "^2.3.6" wrap-ansi "^7.0.0" +instagram-private-api@^1.46.1: + version "1.46.1" + resolved "https://registry.yarnpkg.com/instagram-private-api/-/instagram-private-api-1.46.1.tgz#13aaa90dbc948ec636a9b7f7dd81d5dbcfdf408f" + integrity sha512-fq0q6UfhpikKZ5Kw8HNwS6YpsNghE9I/uc8AM9Do9nsQ+3H1u0jLz+0t/FcGkGTjZz5VGvU8s2VbWj9wxchwYg== + dependencies: + "@lifeomic/attempt" "^3.0.0" + "@types/chance" "^1.0.2" + "@types/request-promise" "^4.1.43" + bluebird "^3.7.1" + chance "^1.0.18" + class-transformer "^0.3.1" + debug "^4.1.1" + image-size "^0.7.3" + json-bigint "^1.0.0" + lodash "^4.17.20" + luxon "^1.12.1" + reflect-metadata "^0.1.13" + request "^2.88.0" + request-promise "^4.2.4" + rxjs "^6.5.2" + snakecase-keys "^3.1.0" + tough-cookie "^2.5.0" + ts-custom-error "^2.2.2" + ts-xor "^1.0.6" + url-regex-safe "^3.0.0" + utility-types "^3.10.0" + into-stream@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" @@ -4926,7 +5143,7 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ip-regex@^4.1.0: +ip-regex@4.3.0, ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== @@ -5078,7 +5295,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typedarray@^1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== @@ -5140,6 +5357,11 @@ isomorphic-ws@5.0.0, isomorphic-ws@^5.0.0: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + issue-parser@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-6.0.0.tgz#b1edd06315d4f2044a9755daf85fdafde9b4014a" @@ -5636,11 +5858,23 @@ js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -5666,6 +5900,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -5683,7 +5922,7 @@ json-stringify-nice@^1.1.4: resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== -json-stringify-safe@^5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== @@ -5741,6 +5980,16 @@ jsonwebtoken@^8.5.1: ms "^2.1.1" semver "^5.6.0" +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + just-diff-apply@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" @@ -6039,7 +6288,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== -lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -6105,6 +6354,11 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== +luxon@^1.12.1: + version "1.28.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0" + integrity sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw== + make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -6178,7 +6432,7 @@ map-obj@^1.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== -map-obj@^4.0.0: +map-obj@^4.0.0, map-obj@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== @@ -6258,7 +6512,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -6828,6 +7082,11 @@ nullthrows@^1.1.1: resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7156,6 +7415,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -7318,6 +7582,11 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +psl@^1.1.28: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -7331,6 +7600,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pupa@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" @@ -7367,6 +7641,11 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + queue-lit@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.5.0.tgz#8197fdafda1edd615c8a0fc14c48353626e5160a" @@ -7553,6 +7832,49 @@ remove-trailing-spaces@^1.0.6: resolved "https://registry.yarnpkg.com/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz#4354d22f3236374702f58ee373168f6d6887ada7" integrity sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise@^4.2.4: + version "4.2.6" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2" + integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ== + dependencies: + bluebird "^3.5.0" + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -7684,7 +8006,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^6.5.1: +rxjs@^6.5.1, rxjs@^6.5.2: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -7705,7 +8027,7 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" -safe-buffer@^5.0.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7720,7 +8042,7 @@ safe-stable-stringify@^2.4.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61" integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -7916,6 +8238,14 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +snakecase-keys@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/snakecase-keys/-/snakecase-keys-3.2.1.tgz#ce5d1a2de8a93c939d7992f76f2743aa59f3d5ad" + integrity sha512-CjU5pyRfwOtaOITYv5C8DzpZ8XA/ieRsDpr93HI2r6e3YInC6moZpSQbmUtg8cTk58tq2x3jcG2gv+p1IZGmMA== + dependencies: + map-obj "^4.1.0" + to-snake-case "^1.0.0" + socks-proxy-agent@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" @@ -8030,6 +8360,21 @@ sqlite3@^5.1.2: optionalDependencies: node-gyp "8.x" +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" @@ -8051,6 +8396,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== + stream-combiner2@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" @@ -8301,6 +8651,11 @@ title-case@^3.0.3: dependencies: tslib "^2.0.3" +tlds@^1.228.0: + version "1.255.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.255.0.tgz#53c2571766c10da95928c716c48dfcf141341e3f" + integrity sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw== + tlds@^1.234.0: version "1.240.0" resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.240.0.tgz#3d3d776d97aa079e43ef4d2f9ef9845e55cff08e" @@ -8323,6 +8678,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg== + to-readable-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" @@ -8335,6 +8695,28 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +to-snake-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-snake-case/-/to-snake-case-1.0.0.tgz#ce746913897946019a87e62edfaeaea4c608ab8c" + integrity sha512-joRpzBAk1Bhi2eGEYBjukEWHOe/IvclOkiJl3DtA91jV6NwQ3MwXA4FHYeqk8BNp/D8bmi9tcNbRu/SozP0jbQ== + dependencies: + to-space-case "^1.0.0" + +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA== + dependencies: + to-no-case "^1.0.0" + +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -8355,6 +8737,11 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +ts-custom-error@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-2.2.2.tgz#ee769cd6a9cf35dc2e9fedefbb3842f3a2fbceae" + integrity sha512-I0FEdfdatDjeigRqh1JFj67bcIKyRNm12UVGheBjs2pXgyELg2xeiQLVaWu1pVmNGXZVnz/fvycSU41moBIpOg== + ts-jest@^29.0.3: version "29.0.3" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77" @@ -8406,6 +8793,11 @@ ts-node@^10.8.1, ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-xor@^1.0.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-xor/-/ts-xor-1.3.0.tgz#3e59f24f0321f9f10f350e0cee3b534b89a2c70b" + integrity sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA== + tsc-alias@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.1.tgz#2cb548a451cc99ccd2dabcd583a0ebe22f3c7dd8" @@ -8444,6 +8836,18 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -8685,16 +9089,34 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" +url-regex-safe@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-regex-safe/-/url-regex-safe-3.0.0.tgz#102a38f74a1a731973fa42690c6a56656fddff12" + integrity sha512-+2U40NrcmtWFVjuxXVt9bGRw6c7/MgkGKN9xIfPrT/2RX0LTkkae6CCEDp93xqUN0UKm/rr821QnHd2dHQmN3A== + dependencies: + ip-regex "4.3.0" + tlds "^1.228.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -8734,6 +9156,15 @@ value-or-promise@1.0.11, value-or-promise@^1.0.11: resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + walk-up-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" From bbdd830735e812f4fa55214d77cdb21e9e94c040 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 1 Nov 2024 06:17:02 +0000 Subject: [PATCH 140/140] =?UTF-8?q?chore(=F0=9F=93=A6):=201.0.0-dev.19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.0.0-dev.19](https://github.com/async3619/cage/compare/v1.0.0-dev.18...v1.0.0-dev.19) (2024-11-01) ### Features ✨ * **instagram:** implement instagram watcher ([#7](https://github.com/async3619/cage/issues/7)) ([a679f23](https://github.com/async3619/cage/commit/a679f2314ad9c7142281aad93ce0b7f5be225232)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0744bcb..14de789 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cage-cli", "description": "realtime unfollower detection for any social services", - "version": "1.0.0-dev.18", + "version": "1.0.0-dev.19", "license": "MIT", "publishConfig": { "access": "public"