diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 20374f4194182..59cb333e379f5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -31,6 +31,19 @@ pnpm --filter ./packages/core/ run test:update:snapshot pnpm --filter ./apps/backend/ run test:update:snapshot ``` +## GraphQL Queries + +The GraphQL queries live in `packages/core/src/graphql/queries/*.graphql`, +and their TypeScript types are generated from GitHub's published schema into `packages/core/src/graphql/generated/`. +Those generated files are committed, so if you change a query, regenerate them and include the result in your PR: + +```bash +pnpm --filter ./packages/core/ run generate-graphql-types +``` + +CI runs `pnpm --filter ./packages/core/ run check-graphql-types`, which fails if the committed types no longer match the queries. +Never edit the generated files by hand — change the `.graphql` file and regenerate. + ## Themes Contribution We have stopped the addition of new themes to decrease maintenance efforts. If you are considering contributing your theme just because you are using it personally, then instead of adding it to our theme collection, you can use card [customization options](../docs/advanced_documentation.md#customization). diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5e7101a8e2de9..6500dbaac1b04 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -57,6 +57,11 @@ updates: patterns: - "axios" - "axios-*" + graphql: + patterns: + - "graphql" + - "@graphql-codegen/*" + - "@octokit/graphql-schema" # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b75661e8ad015..439f4c8c1ce63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,5 +129,8 @@ jobs: - name: Lint (deduped deps) run: pnpm run lint:deps + - name: Lint (generated GraphQL types) + run: pnpm --filter ./packages/core/ run check-graphql-types + - name: Typecheck run: pnpm run typecheck diff --git a/apps/backend/tests/e2e/e2e.test.js b/apps/backend/tests/e2e/e2e.test.js index e440e58abea1e..fb968c9572817 100644 --- a/apps/backend/tests/e2e/e2e.test.js +++ b/apps/backend/tests/e2e/e2e.test.js @@ -201,7 +201,7 @@ beforeAll(async () => { return [200, STATS_MOCK_RESPONSE]; } - if (query.includes("query userInfo") && variables?.login === USER) { + if (query.includes("query topLanguages") && variables?.login === USER) { return [200, TOP_LANGS_MOCK_RESPONSE]; } diff --git a/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap index 4626f2045aaf3..784234ff773d8 100644 --- a/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap +++ b/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap @@ -699,7 +699,7 @@ exports[`Test /api contract > should match the public many-params response snaps ", - "graphqlRequest": "{"query":"\\n query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n name\\n login\\n commits: contributionsCollection (from: $startTime) {\\n totalCommitContributions,\\n }\\n reviews: contributionsCollection {\\n totalPullRequestReviewContributions\\n }\\n repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {\\n totalCount\\n }\\n pullRequests(first: 1) {\\n totalCount\\n }\\n mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) {\\n totalCount\\n }\\n openIssues: issues(states: OPEN) {\\n totalCount\\n }\\n closedIssues: issues(states: CLOSED) {\\n totalCount\\n }\\n followers {\\n totalCount\\n }\\n repositoryDiscussions @include(if: $includeDiscussions) {\\n totalCount\\n }\\n repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) {\\n totalCount\\n }\\n \\n repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) {\\n totalCount\\n nodes {\\n name\\n stargazerCount\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n\\n }\\n }\\n","variables":{"login":"anuraghazra","first":100,"after":null,"includeMergedPullRequests":true,"includeDiscussions":true,"includeDiscussionsAnswers":true,"startTime":"2024-01-01T00:00:00Z","ownerAffiliations":["OWNER","COLLABORATOR"]}}", + "graphqlRequest": "{"query":"\\nquery userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n name\\n login\\n commits: contributionsCollection(from: $startTime) {\\n totalCommitContributions\\n }\\n reviews: contributionsCollection {\\n totalPullRequestReviewContributions\\n }\\n repositoriesContributedTo(\\n first: 1\\n contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]\\n ) {\\n totalCount\\n }\\n pullRequests(first: 1) {\\n totalCount\\n }\\n mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) {\\n totalCount\\n }\\n openIssues: issues(states: OPEN) {\\n totalCount\\n }\\n closedIssues: issues(states: CLOSED) {\\n totalCount\\n }\\n followers {\\n totalCount\\n }\\n repositoryDiscussions @include(if: $includeDiscussions) {\\n totalCount\\n }\\n repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) {\\n totalCount\\n }\\n ...RepoStars\\n }\\n}\\nfragment RepoStars on User {\\n repositories(\\n first: 100\\n after: $after\\n ownerAffiliations: $ownerAffiliations\\n orderBy: {direction: DESC, field: STARGAZERS}\\n ) {\\n totalCount\\n nodes {\\n ...RepoNode\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\\nfragment RepoNode on Repository {\\n name\\n stargazerCount\\n}","variables":{"login":"anuraghazra","after":null,"includeMergedPullRequests":true,"includeDiscussions":true,"includeDiscussionsAnswers":true,"startTime":"2024-01-01T00:00:00Z","ownerAffiliations":["OWNER","COLLABORATOR"]}}", "headers": [ [ "Cache-Control", diff --git a/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap index 524372fb0f8d6..8c88bd71dbd3a 100644 --- a/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap +++ b/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap @@ -239,7 +239,7 @@ exports[`Test /api/gist contract > should match the public many-params response ", - "graphqlRequest": "{"query":"\\nquery gistInfo($gistName: String!) {\\n viewer {\\n gist(name: $gistName) {\\n description\\n owner {\\n login\\n }\\n stargazerCount\\n forks {\\n totalCount\\n }\\n files {\\n name\\n language {\\n name\\n }\\n size\\n }\\n }\\n }\\n}\\n","variables":{"gistName":"happy-gist-id"}}", + "graphqlRequest": "{"query":"\\nquery gistInfo($gistName: String!) {\\n viewer {\\n gist(name: $gistName) {\\n description\\n owner {\\n login\\n }\\n stargazerCount\\n forks {\\n totalCount\\n }\\n files {\\n ...GistFileInfo\\n }\\n }\\n }\\n}\\nfragment GistFileInfo on GistFile {\\n name\\n language {\\n name\\n }\\n size\\n}","variables":{"gistName":"happy-gist-id"}}", "headers": [ [ "Cache-Control", diff --git a/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap index dc788899d533a..9b29eb4d6e0b8 100644 --- a/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap +++ b/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap @@ -504,7 +504,7 @@ exports[`Test /api/pin contract > should match the public many-params response s ", - "graphqlRequest": "{"query":"\\n fragment RepoInfo on Repository {\\n name\\n nameWithOwner\\n isPrivate\\n isArchived\\n isTemplate\\n stargazerCount\\n description\\n primaryLanguage {\\n color\\n id\\n name\\n }\\n forkCount\\n }\\n query getRepo($login: String!, $repo: String!) {\\n user(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n organization(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n }\\n ","variables":{"login":"anuraghazra","repo":"convoychat"}}", + "graphqlRequest": "{"query":"\\nquery getRepo($login: String!, $repo: String!) {\\n user(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n organization(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n}\\nfragment RepoInfo on Repository {\\n name\\n nameWithOwner\\n isPrivate\\n isArchived\\n isTemplate\\n stargazerCount\\n description\\n primaryLanguage {\\n color\\n id\\n name\\n }\\n forkCount\\n}","variables":{"login":"anuraghazra","repo":"convoychat"}}", "headers": [ [ "Cache-Control", diff --git a/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap index ea87380551eed..41a655f33e875 100644 --- a/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap +++ b/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap @@ -412,7 +412,7 @@ exports[`Test /api/top-langs contract > should match the public many-params resp ", - "graphqlRequest": "{"query":"\\n query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n # do not fetch forks\\n repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) {\\n nodes {\\n name\\n languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {\\n edges {\\n size\\n node {\\n color\\n name\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n ","variables":{"login":"anuraghazra","ownerAffiliations":["OWNER","COLLABORATOR"]}}", + "graphqlRequest": "{"query":"\\nquery topLanguages($login: String!, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) {\\n nodes {\\n ...TopLanguagesRepository\\n }\\n }\\n }\\n}\\nfragment TopLanguagesRepository on Repository {\\n name\\n languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {\\n edges {\\n ...TopLanguage\\n }\\n }\\n}\\nfragment TopLanguage on LanguageEdge {\\n size\\n node {\\n color\\n name\\n }\\n}","variables":{"login":"anuraghazra","ownerAffiliations":["OWNER","COLLABORATOR"]}}", "headers": [ [ "Cache-Control", diff --git a/apps/frontend/src/axios-override.ts b/apps/frontend/src/axios-override.ts index 96267c61db716..c518926f3fb79 100644 --- a/apps/frontend/src/axios-override.ts +++ b/apps/frontend/src/axios-override.ts @@ -88,9 +88,7 @@ axios.defaults.adapter = async (config) => { if ( config.url === "https://api.github.com/graphql" && - params.query?.includes( - "query userInfo($login: String!, $after: String, $includeMergedPullRequests:", - ) && + params.query?.includes("query userInfo(") && params.variables?.login === DEMO_USER ) { return createMockResponse(userStats, config); @@ -98,9 +96,7 @@ axios.defaults.adapter = async (config) => { if ( config.url === "https://api.github.com/graphql" && - params.query?.includes( - "query userInfo($login: String!, $after: String, $ownerAffiliations:", - ) && + params.query?.includes("query userRepos(") && params.variables?.login === DEMO_USER ) { return createMockResponse(additionalUserStars, config); @@ -108,9 +104,7 @@ axios.defaults.adapter = async (config) => { if ( config.url === "https://api.github.com/graphql" && - params.query?.includes( - "query userInfo($login: String!, $ownerAffiliations:", - ) && + params.query?.includes("query topLanguages(") && params.variables?.login === DEMO_USER ) { return createMockResponse(topLanguages, config); @@ -118,7 +112,7 @@ axios.defaults.adapter = async (config) => { if ( config.url === "https://api.github.com/graphql" && - params.query?.includes("fragment RepoInfo on Repository {") && + params.query?.includes("query getRepo(") && params.variables && params.variables.login === DEMO_REPO.split("/")[0] && params.variables.repo === DEMO_REPO.split("/")[1] diff --git a/eslint.config.js b/eslint.config.js index 909e6e84bc50f..9c78ad877fcde 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,10 @@ const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url)); export default defineConfig( includeIgnoreFile(gitignorePath, "Imported .gitignore patterns"), + { + name: "Generated GraphQL types", + ignores: ["packages/core/src/graphql/generated/**"], + }, js.configs.recommended, { diff --git a/knip.jsonc b/knip.jsonc index c4d3bde028b08..638fb6ecf7560 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -4,6 +4,10 @@ "entry": ["tests/**/*.{test,test-d,bench}.{js,ts}"] }, "workspaces": { + "packages/core": { + // generated by scripts/generate-graphql-types.js + "ignore": ["src/graphql/generated/**"] + }, "apps/backend": { "entry": ["api-renamed/*.js", "express.js"] }, diff --git a/packages/core/package.json b/packages/core/package.json index 8bff5723ec176..56397184a45e0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -75,14 +75,21 @@ "bench": "vitest bench --run --config vitest.config.bench.ts", "lint": "eslint", "typecheck": "tsc -p tsconfig.typecheck.json", + "generate-graphql-types": "node scripts/generate-graphql-types", + "check-graphql-types": "node scripts/generate-graphql-types --check", "generate-theme-readme": "node scripts/generate-theme-readme", "generate-language-colors": "node scripts/generate-language-colors" }, "devDependencies": { + "@graphql-codegen/core": "6.2.0", + "@graphql-codegen/typescript": "6.1.0", + "@graphql-codegen/typescript-operations": "6.1.2", + "@octokit/graphql-schema": "15.26.1", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", "@uppercod/css-to-object": "1.1.1", "axios-mock-adapter": "2.1.0", + "graphql": "16.11.0", "js-yaml": "5.2.1", "jsdom": "catalog:default", "vitest": "catalog:default" diff --git a/packages/core/scripts/generate-graphql-types.js b/packages/core/scripts/generate-graphql-types.js new file mode 100644 index 0000000000000..503003ed35eb9 --- /dev/null +++ b/packages/core/scripts/generate-graphql-types.js @@ -0,0 +1,282 @@ +/** + * Generates TypeScript types for `src/graphql/queries` from GitHub's published schema: + * one file per query, plus a `common.ts` for the types their variables name. + * + * Pass `--check` to fail on drift instead of writing. + */ +import fs from "node:fs/promises"; +import path from "node:path"; + +import { codegen } from "@graphql-codegen/core"; +import * as typescriptPlugin from "@graphql-codegen/typescript"; +import * as typescriptOperationsPlugin from "@graphql-codegen/typescript-operations"; +import { schema as githubSchema } from "@octokit/graphql-schema"; +import { + GraphQLSchema, + Kind, + buildSchema, + getNamedType, + isSpecifiedScalarType, + parse, + print, + printSchema, + typeFromAST, + visit, +} from "graphql"; +import { format, resolveConfig } from "prettier"; + +/** + * @typedef {Parameters[0]} CodegenOptions + * @typedef {CodegenOptions["pluginMap"][string]} CodegenPlugin + * @typedef {import("@graphql-codegen/typescript").TypeScriptPluginConfig} TypeScriptPluginConfig + * @typedef {import("@graphql-codegen/typescript-operations").TypeScriptDocumentsPluginConfig} TypeScriptDocumentsPluginConfig + * @typedef {import("graphql").ASTNode} ASTNode + * @typedef {import("graphql").FragmentDefinitionNode} FragmentDefinitionNode + * @typedef {import("graphql").GraphQLNamedType} GraphQLNamedType + * @typedef {import("graphql").OperationDefinitionNode} OperationDefinitionNode + * @typedef {Map} FragmentMap Fragment definitions by name. + */ + +const PACKAGE_ROOT = path.join(import.meta.dirname, ".."); +const QUERIES_DIR = path.join(PACKAGE_ROOT, "src/graphql/queries"); +const OUT_DIR = path.join(PACKAGE_ROOT, "src/graphql/generated"); +const COMMON_FILE = path.join(OUT_DIR, "common.ts"); +// `typescript-operations` resolves this against the working directory +const COMMON_IMPORT_PATH = path.relative( + process.cwd(), + COMMON_FILE.replace(/\.ts$/, ".js"), +); + +/** + * @param {string} file Absolute path. + * @returns {string} The same path relative to the package, for log messages. + */ +const pathRelativeFromRoot = (file) => path.relative(PACKAGE_ROOT, file); + +// CI mode: regenerate in memory, fail on any difference +const checkOnly = process.argv.includes("--check"); + +/** @type {TypeScriptPluginConfig & TypeScriptDocumentsPluginConfig} */ +const config = { + emitLegacyCommonJSImports: false, + enumsAsTypes: true, + skipTypename: true, + useTypeImports: true, + // enums and input types come from the common file, imported only where used + importSchemaTypesFrom: COMMON_IMPORT_PATH, + scalars: { DateTime: "string" }, +}; + +// GitHub's SDL declares a few fields twice, which trips SDL validation +const schemaAst = buildSchema(githubSchema.idl, { assumeValidSDL: true }); +// printed once: every query output reuses it +const schemaDocument = parse(printSchema(schemaAst)); + +// a missing folder is a broken checkout, not an empty query set: generating from it would +// delete every existing output +const queryDir = await fs.readdir(QUERIES_DIR).catch(() => { + console.error(`No queries in ${pathRelativeFromRoot(QUERIES_DIR)}`); + process.exit(1); +}); +const queryFiles = queryDir.filter((file) => file.endsWith(".graphql")).sort(); + +const documents = await Promise.all( + queryFiles.map(async (file) => { + const location = path.join(QUERIES_DIR, file); + const output = path.join(OUT_DIR, `${path.basename(file, ".graphql")}.ts`); + const document = parse(await fs.readFile(location, "utf8")); + return { location, output, document }; + }), +); + +/** + * Every fragment a node spreads, directly or through another fragment. + * + * @param {ASTNode} node Operation or fragment definition to walk. + * @param {FragmentMap} fragments Fragments defined in the same file. + * @param {FragmentMap} found Fragments collected so far. + * @returns {FragmentMap} The fragment definitions the node needs. + */ +const spreadFragments = (node, fragments, found = new Map()) => { + visit(node, { + FragmentSpread(spread) { + const name = spread.name.value; + if (found.has(name)) { + return; + } + const fragment = fragments.get(name); + if (!fragment) { + throw new Error(`No definition found for fragment "${name}"`); + } + found.set(name, fragment); + spreadFragments(fragment, fragments, found); + }, + }); + return found; +}; + +/** + * @param {string} value Word to capitalize. + * @returns {string} The capitalized word. + */ +const pascalCase = (value) => value.charAt(0).toUpperCase() + value.slice(1); + +/** + * The names `typescript-operations` gives an operation's types. + * + * @param {OperationDefinitionNode} operation Operation definition. + * @returns {{ documentName: string; resultType: string; variablesType: string }} Names to reference in the emitted document. + */ +const operationNames = (operation) => { + if (!operation.name) { + throw new Error("Every query needs a name to generate types from"); + } + const name = pascalCase(operation.name.value); + // `typescript-operations` appends the operation type unconditionally + const resultType = `${name}${pascalCase(operation.operation)}`; + return { + documentName: `${name}Document`, + resultType, + variablesType: `${resultType}Variables`, + }; +}; + +/** + * Emits one typed document per operation, beside its types. + * + * @type {CodegenPlugin['plugin']} + */ +const documentsPlugin = (_schema, files) => { + // `document` is optional on codegen's file type; ours are always parsed + const definitions = files.flatMap((file) => file.document?.definitions ?? []); + const { FRAGMENT_DEFINITION, OPERATION_DEFINITION } = Kind; + + const fragments = new Map( + definitions + .filter((definition) => definition.kind === FRAGMENT_DEFINITION) + .map((definition) => [definition.name.value, definition]), + ); + + const operations = definitions + .filter((definition) => definition.kind === OPERATION_DEFINITION) + .map((operation) => { + const { documentName, resultType, variablesType } = + operationNames(operation); + const text = [ + print(operation), + ...[...spreadFragments(operation, fragments).values()].map(print), + ].join("\n"); + return `export const ${documentName} = graphqlDocument<${resultType}, ${variablesType}>(\`\n${text}\`);`; + }); + + return { + prepend: [`import { graphqlDocument } from "../graphqlDocument.js";`], + content: operations.join("\n\n"), + }; +}; + +// only the types the queries name in their variables; an unknown name yields undefined, +// which codegen reports later against the query +/** @type {Set} */ +const commonTypes = new Set(); +for (const { document } of documents) { + visit(document, { + VariableDefinition({ type }) { + // `getNamedType` strips the `[…]!` wrappers the variable declares + const named = getNamedType(typeFromAST(schemaAst, type)); + if (named && !isSpecifiedScalarType(named)) { + commonTypes.add(named); + } + }, + }); +} + +// emitted for the fragment masking we don't generate, with no config to leave it out +const INCREMENTAL_TYPE = + /^\/\*\* Internal type\. DO NOT USE DIRECTLY\. \*\/\nexport type Incremental = [^\n]*\n/m; + +const BANNER = `// Generated file — see .github/CONTRIBUTING.md\n\n`; + +/** @type {Array>} */ +const outputs = documents.map((file) => ({ + filename: file.output, + schema: schemaDocument, + schemaAst, + documents: [file], + plugins: [{ "typescript-operations": {} }, { documents: {} }], + pluginMap: { + "typescript-operations": typescriptOperationsPlugin, + documents: { plugin: documentsPlugin }, + }, +})); + +// queries with only built-in scalar variables need no common file +if (commonTypes.size) { + const commonSchema = new GraphQLSchema({ types: [...commonTypes] }); + outputs.push({ + filename: COMMON_FILE, + schema: parse(printSchema(commonSchema)), + schemaAst: commonSchema, + documents: [], + plugins: [{ typescript: {} }], + pluginMap: { typescript: typescriptPlugin }, + }); +} + +const prettierConfig = await resolveConfig(COMMON_FILE); +const generated = await Promise.all( + outputs.map(async ({ filename, ...options }) => { + const content = await codegen({ filename, config, ...options }); + return { + filename, + content: await format(BANNER + content.replace(INCREMENTAL_TYPE, ""), { + ...prettierConfig, + parser: "typescript", + }), + }; + }), +); + +const queryCount = `${documents.length} ${documents.length === 1 ? "query" : "queries"}`; + +// a deleted query leaves its generated file behind; anything else in the folder is not ours +const expected = new Set(generated.map(({ filename }) => filename)); +const stale = (await fs.readdir(OUT_DIR).catch(() => [])) + .filter((file) => file.endsWith(".ts")) + .map((file) => path.join(OUT_DIR, file)) + .filter((file) => !expected.has(file)); + +if (checkOnly) { + const outdated = stale.map(pathRelativeFromRoot); + for (const { filename, content } of generated) { + const current = await fs.readFile(filename, "utf8").catch(() => null); + if (current !== content) { + outdated.push(pathRelativeFromRoot(filename)); + } + } + + if (outdated.length > 0) { + const files = outdated.map((file) => ` ${file}`).join("\n"); + const message = `GraphQL types are out of date:\n${files}\n\nRun \`pnpm --filter ./packages/core/ run generate-graphql-types\`.`; + console.error(message); + process.exit(1); + } + + const message = `GraphQL types are up to date (${queryCount})`; + console.log(message); +} else { + await fs.mkdir(OUT_DIR, { recursive: true }); + for (const { filename, content } of generated) { + await fs.writeFile(filename, content); + } + + for (const file of stale) { + await fs.rm(file); + const removed = `Removed ${pathRelativeFromRoot(file)} — no query generates it`; + console.log(removed); + } + + const outDir = pathRelativeFromRoot(OUT_DIR); + const message = `Generated ${generated.length} files in ${outDir} from ${queryCount}`; + console.log(message); +} diff --git a/packages/core/src/common/config.ts b/packages/core/src/common/config.ts index 5dfc2d86487a7..9d67bd6dc691c 100644 --- a/packages/core/src/common/config.ts +++ b/packages/core/src/common/config.ts @@ -9,7 +9,8 @@ interface Config { whitelist: Array | undefined; gistWhitelist: Array | undefined; excludeRepositories: Array; - fetchMultiPageStars: string | undefined; + /** Max pages of starred repos to fetch; `Infinity` means every page, `1` only the first. */ + fetchMultiPageStars: number; pats: Array; } @@ -17,11 +18,19 @@ interface Config { * @param value Comma-separated string. * @returns Parsed string values. */ -const parseCsv = (value: string | undefined): Array | undefined => { - if (!value) { - return undefined; +const parseCsv = (value: string | undefined): Array | undefined => + value ? value.split(",") : undefined; + +/** + * @param value Raw `FETCH_MULTI_PAGE_STARS` value. + * @returns Page limit: `"true"` means every page, a positive number caps the pages, anything else means one. + */ +const parseFetchMultiPageStars = (value: string | undefined): number => { + if (value === "true") { + return Infinity; } - return value.split(","); + const limit = Number(value); + return limit > 0 ? limit : 1; }; /** @@ -45,37 +54,21 @@ const getDefaultEnv = (): Env => { return processEnv ?? {}; }; -/** - * @param config (Partial) config values to normalize. - * @returns Normalized config object with defaults applied. - */ -const normalizeConfig = (config: Partial = {}): Config => { - return { - whitelist: config.whitelist, - gistWhitelist: config.gistWhitelist, - excludeRepositories: config.excludeRepositories ?? [], - fetchMultiPageStars: config.fetchMultiPageStars, - pats: config.pats ?? [], - }; -}; - let currentConfig: Config; /** * @param env Environment variables used to build the runtime config. */ export const loadConfigFromEnv = (env: Env = getDefaultEnv()): void => { - const whitelist = parseCsv(env["WHITELIST"]); - const gistWhitelist = parseCsv(env["GIST_WHITELIST"]); - const excludeRepositories = parseCsv(env["EXCLUDE_REPO"]) ?? []; - - currentConfig = normalizeConfig({ - whitelist, - gistWhitelist, - excludeRepositories, - fetchMultiPageStars: env["FETCH_MULTI_PAGE_STARS"], + currentConfig = { + whitelist: parseCsv(env["WHITELIST"]), + gistWhitelist: parseCsv(env["GIST_WHITELIST"]), + excludeRepositories: parseCsv(env["EXCLUDE_REPO"]) ?? [], + fetchMultiPageStars: parseFetchMultiPageStars( + env["FETCH_MULTI_PAGE_STARS"], + ), pats: parsePATsFromEnv(env), - }); + }; }; loadConfigFromEnv(); diff --git a/packages/core/src/common/constants.ts b/packages/core/src/common/constants.ts index 9b40de281b76b..52681782f77c9 100644 --- a/packages/core/src/common/constants.ts +++ b/packages/core/src/common/constants.ts @@ -1,7 +1,9 @@ +import type { RepositoryAffiliation } from "../graphql/generated/common.js"; + /** * Valid owner affiliations for GitHub API queries. */ -const OWNER_AFFILIATIONS: Array = [ +const OWNER_AFFILIATIONS: Array = [ "OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER", diff --git a/packages/core/src/common/http.ts b/packages/core/src/common/http.ts index 4c03701d8380c..71dcb245e1286 100644 --- a/packages/core/src/common/http.ts +++ b/packages/core/src/common/http.ts @@ -1,6 +1,38 @@ import axios from "axios"; import type { AxiosRequestConfig, AxiosResponse } from "axios"; +import type { GraphQLDocument } from "../graphql/graphqlDocument.js"; + +const GITHUB_GRAPHQL_API = "https://api.github.com/graphql"; + +/** Response of a GraphQL call: the envelope the GitHub API wraps results in. */ +type GraphQLResponse = AxiosResponse<{ + data: TResult; + errors?: Array<{ type?: string; message?: string }>; +}>; + +/** + * @param document Generated query document. + * @param scheme `Authorization` scheme for the token. + * @returns A fetcher `retryer` can drive. + */ +const createGraphQLFetcher = ( + document: GraphQLDocument, + scheme: "bearer" | "token", +) => { + return ( + variables: TVariables, + token: string, + ): Promise> => { + return axios({ + url: GITHUB_GRAPHQL_API, + method: "post", + headers: { Authorization: `${scheme} ${token}` }, + data: { query: document.text, variables }, + }); + }; +}; + /** Body of a GraphQL request sent to the GitHub API. */ interface GraphQLRequest { /** The GraphQL query. */ @@ -10,22 +42,30 @@ interface GraphQLRequest { } /** - * Send GraphQL request to GitHub API. + * Send an untyped GraphQL request to the GitHub API. * * @param data Request data. * @param headers Request headers. * @returns Request response. + * + * @description Superseded by {@link createGraphQLFetcher}. + * Still used by the backend status endpoints for their own `rateLimit` query, + * and part of this package's published API. + * + * @todo consider dropping this once those endpoints post through axios directly, + * which they already depend on. */ const request = ( data: GraphQLRequest, headers: NonNullable, ): Promise => { return axios({ - url: "https://api.github.com/graphql", + url: GITHUB_GRAPHQL_API, method: "post", headers, data, }); }; -export { request }; +export { createGraphQLFetcher, request }; +export type { GraphQLResponse }; diff --git a/packages/core/src/common/ops.ts b/packages/core/src/common/ops.ts index 2331e9ec704c6..9f3b1ec9d4662 100644 --- a/packages/core/src/common/ops.ts +++ b/packages/core/src/common/ops.ts @@ -1,5 +1,7 @@ import toEmoji from "emoji-name-map"; +import type { RepositoryAffiliation } from "../graphql/generated/common.js"; + import { OWNER_AFFILIATIONS } from "./constants.js"; import { CustomError } from "./error.js"; @@ -111,6 +113,9 @@ const dateDiff = (d1: Date, d2: Date): number => { return Math.round(diff / (1000 * 60)); }; +const isOwnerAffiliation = (value: string): value is RepositoryAffiliation => + OWNER_AFFILIATIONS.some((affiliation) => affiliation === value); + /** * Parse owner affiliations. * @@ -119,7 +124,9 @@ const dateDiff = (d1: Date, d2: Date): number => { * * @throws {CustomError} If affiliations contains invalid values. */ -const parseOwnerAffiliations = (affiliations: Array): Array => { +const parseOwnerAffiliations = ( + affiliations: Array, +): Array => { // Set default value for ownerAffiliations. // NOTE: Done here since parseArray() will always return an empty array even nothing //was specified. @@ -129,9 +136,7 @@ const parseOwnerAffiliations = (affiliations: Array): Array => { : ["OWNER"]; // Check if ownerAffiliations contains valid values. - if ( - normalized.some((affiliation) => !OWNER_AFFILIATIONS.includes(affiliation)) - ) { + if (!normalized.every(isOwnerAffiliation)) { throw new CustomError( "Invalid query parameter", CustomError.INVALID_AFFILIATION, diff --git a/packages/core/src/common/retryer.ts b/packages/core/src/common/retryer.ts index 4b07f36466b70..4f622264dad89 100644 --- a/packages/core/src/common/retryer.ts +++ b/packages/core/src/common/retryer.ts @@ -36,8 +36,8 @@ function getRandomInt(max: number): number { */ type FetcherResponse = AxiosResponse; -type FetcherFunction = ( - variables: Record, +type FetcherFunction> = ( + variables: TVariables, token: string, retriesForTests?: number, ) => Promise>; @@ -46,14 +46,15 @@ type FetcherFunction = ( * Try to execute the fetcher function until it succeeds or the max number of retries is reached. * * @template TData Shape of `response.data` returned by the fetcher. + * @template TVariables Variables the fetcher accepts. * @param fetcher The fetcher function. * @param variables Object with arguments to pass to the fetcher function. * @param pat Optional PAT override. * @returns The response from the fetcher function. */ -const retryer = async ( - fetcher: FetcherFunction, - variables: Record, +const retryer = async >( + fetcher: FetcherFunction, + variables: TVariables, pat: string | null = null, ): Promise> => { const PATs = pat diff --git a/packages/core/src/fetchers/gist.ts b/packages/core/src/fetchers/gist.ts index 1d8fc520d31bb..6f8cef9720c09 100644 --- a/packages/core/src/fetchers/gist.ts +++ b/packages/core/src/fetchers/gist.ts @@ -1,73 +1,12 @@ -import type { AxiosResponse } from "axios"; - import { MissingParamError } from "../common/error.js"; -import { request } from "../common/http.js"; +import { createGraphQLFetcher } from "../common/http.js"; import { retryer } from "../common/retryer.js"; +import { GistInfoDocument } from "../graphql/generated/gist.js"; +import type { GistFileInfoFragment } from "../graphql/generated/gist.js"; import type { GistData } from "./types.js"; -const QUERY = ` -query gistInfo($gistName: String!) { - viewer { - gist(name: $gistName) { - description - owner { - login - } - stargazerCount - forks { - totalCount - } - files { - name - language { - name - } - size - } - } - } -} -`; - -/** - * Gist data fetcher. - * - * @param variables Fetcher variables. - * @param token GitHub token. - * @returns The response. - */ -const fetcher = ( - variables: Record, - token: string, -): Promise => { - return request( - { query: QUERY, variables }, - { Authorization: `token ${token}` }, - ); -}; - -/** A single file within a gist. */ -interface GistFile { - name: string; - language: { name: string } | null; - size: number; -} - -/** Shape of `response.data` returned by the gist GraphQL query. */ -interface GistQueryResponse { - data: { - viewer: { - gist: { - description: string | null; - owner: { login: string }; - stargazerCount: number; - forks: { totalCount: number }; - files: Array; - } | null; - }; - }; -} +const fetcher = createGraphQLFetcher(GistInfoDocument, "token"); /** * This function calculates the primary language of a gist by files size. @@ -75,13 +14,15 @@ interface GistQueryResponse { * @param files Files. * @returns Primary language, or `null` when no file has a language. */ -const calculatePrimaryLanguage = (files: Array): string | null => { +const calculatePrimaryLanguage = ( + files: Array, +): string | null => { const languages: Record = {}; for (const file of files) { if (file.language) { languages[file.language.name] = - (languages[file.language.name] ?? 0) + file.size; + (languages[file.language.name] ?? 0) + (file.size ?? 0); } } @@ -111,7 +52,7 @@ const fetchGist = async ( if (!id) { throw new MissingParamError(["id"], "/api/gist?id=GIST_ID"); } - const res = await retryer(fetcher, { gistName: id }, pat); + const res = await retryer(fetcher, { gistName: id }, pat); if (res.data.errors) { throw new Error(res.data.errors[0]?.message); } @@ -119,15 +60,17 @@ const fetchGist = async ( if (!gist) { throw new Error("Gist not found"); } - const firstFile = gist.files[0]; - if (!firstFile) { + const firstFile = gist.files?.[0]; + if (!firstFile?.name) { throw new Error("Gist has no files"); } return { name: firstFile.name, - nameWithOwner: `${gist.owner.login}/${firstFile.name}`, + nameWithOwner: `${gist.owner?.login ?? ""}/${firstFile.name}`, description: gist.description, - language: calculatePrimaryLanguage(gist.files), + language: calculatePrimaryLanguage( + gist.files?.filter((file) => !!file) ?? [], + ), starsCount: gist.stargazerCount, forksCount: gist.forks.totalCount, }; diff --git a/packages/core/src/fetchers/repo.ts b/packages/core/src/fetchers/repo.ts index 4287433781b48..b922dbf9567e3 100644 --- a/packages/core/src/fetchers/repo.ts +++ b/packages/core/src/fetchers/repo.ts @@ -1,72 +1,15 @@ -import type { AxiosResponse } from "axios"; - import { MissingParamError } from "../common/error.js"; -import { request } from "../common/http.js"; +import { createGraphQLFetcher } from "../common/http.js"; import { retryer } from "../common/retryer.js"; +import { GetRepoDocument } from "../graphql/generated/repo.js"; import { fetchRepoUserStats } from "./stats.js"; -import type { RepoInfo, RepositoryData } from "./types.js"; +import type { RepositoryData } from "./types.js"; -/** - * Repo data fetcher. - * - * @param variables Fetcher variables. - * @param token GitHub token. - * @returns The response. - */ -const fetcher = ( - variables: Record, - token: string, -): Promise => { - return request( - { - query: ` - fragment RepoInfo on Repository { - name - nameWithOwner - isPrivate - isArchived - isTemplate - stargazerCount - description - primaryLanguage { - color - id - name - } - forkCount - } - query getRepo($login: String!, $repo: String!) { - user(login: $login) { - repository(name: $repo) { - ...RepoInfo - } - } - organization(login: $login) { - repository(name: $repo) { - ...RepoInfo - } - } - } - `, - variables, - }, - { - Authorization: `token ${token}`, - }, - ); -}; +const fetcher = createGraphQLFetcher(GetRepoDocument, "token"); const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME"; -/** Shape of `response.data` returned by the repo query. */ -interface RepoQueryResponse { - data: { - user: { repository: RepoInfo | null } | null; - organization: { repository: RepoInfo | null } | null; - }; -} - /** * Fetch repository data. * @@ -113,11 +56,7 @@ const fetchRepo = async ( throw new MissingParamError(["repo"], urlExample); } - const res = await retryer( - fetcher, - { login: owner, repo: reponame }, - pat, - ); + const res = await retryer(fetcher, { login: owner, repo: reponame }, pat); const data = res.data.data; diff --git a/packages/core/src/fetchers/stats.ts b/packages/core/src/fetchers/stats.ts index ace2c8733b6f4..c0f082fa7f718 100644 --- a/packages/core/src/fetchers/stats.ts +++ b/packages/core/src/fetchers/stats.ts @@ -6,125 +6,32 @@ import { calculateRank } from "../calculateRank.js"; import { getConfig } from "../common/config.js"; import { CustomError, MissingParamError } from "../common/error.js"; import { wrapTextMultiline } from "../common/fmt.js"; -import { request } from "../common/http.js"; +import { createGraphQLFetcher } from "../common/http.js"; +import type { GraphQLResponse } from "../common/http.js"; import { logger } from "../common/log.js"; import { buildSearchFilter, parseOwnerAffiliations } from "../common/ops.js"; import { retryer } from "../common/retryer.js"; +import { + UserInfoDocument, + UserReposDocument, +} from "../graphql/generated/stats.js"; +import type { + RepoNodeFragment, + UserInfoQuery, + UserInfoQueryVariables, +} from "../graphql/generated/stats.js"; import type { RepoUserStats, StatsData } from "./types.js"; -// GraphQL queries. -const GRAPHQL_REPOS_FIELD = ` - repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) { - totalCount - nodes { - name - stargazerCount - } - pageInfo { - hasNextPage - endCursor - } - } -`; - -const GRAPHQL_REPOS_QUERY = ` - query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login) { - ${GRAPHQL_REPOS_FIELD} - } - } -`; - -const GRAPHQL_STATS_QUERY = ` - query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login) { - name - login - commits: contributionsCollection (from: $startTime) { - totalCommitContributions, - } - reviews: contributionsCollection { - totalPullRequestReviewContributions - } - repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { - totalCount - } - pullRequests(first: 1) { - totalCount - } - mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) { - totalCount - } - openIssues: issues(states: OPEN) { - totalCount - } - closedIssues: issues(states: CLOSED) { - totalCount - } - followers { - totalCount - } - repositoryDiscussions @include(if: $includeDiscussions) { - totalCount - } - repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) { - totalCount - } - ${GRAPHQL_REPOS_FIELD} - } - } -`; - -/** The `user` object returned by the stats GraphQL query. */ -interface StatsUser { - name: string | null; - login: string; - commits: { totalCommitContributions: number }; - reviews: { totalPullRequestReviewContributions: number }; - repositoriesContributedTo: { totalCount: number }; - pullRequests: { totalCount: number }; - // conditionally included via @include(if: …), so absent when their flag is off - mergedPullRequests?: { totalCount: number }; - openIssues: { totalCount: number }; - closedIssues: { totalCount: number }; - followers: { totalCount: number }; - repositoryDiscussions?: { totalCount: number }; - repositoryDiscussionComments?: { totalCount: number }; - repositories: { - totalCount: number; - nodes: Array<{ name: string; stargazerCount: number }>; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; - }; -} - -/** Shape of `response.data` returned by the stats GraphQL query. */ -interface StatsQueryResponse { - data: { user: StatsUser }; -} - /** The subset of the stats response `statsFetcher` returns and threads on. */ -interface StatsFetcherResponse { - data: StatsQueryResponse & { - errors?: Array<{ type?: string; message?: string }>; - }; - statusText: string; -} +type StatsFetcherResponse = Pick< + GraphQLResponse, + "data" | "statusText" +>; -/** - * Stats fetcher object. - * - * @param variables Fetcher variables. - * @param token GitHub token. - * @returns Axios response. - */ -const fetcher = ( - variables: Record, - token: string, -): Promise => { - const query = variables["after"] ? GRAPHQL_REPOS_QUERY : GRAPHQL_STATS_QUERY; - return request({ query, variables }, { Authorization: `bearer ${token}` }); -}; +const fetcher = createGraphQLFetcher(UserInfoDocument, "bearer"); +/** Fetcher for the pages after the first, which only need `repositories`. */ +const reposFetcher = createGraphQLFetcher(UserReposDocument, "bearer"); /** * Fetch stats information for a given username. @@ -137,7 +44,7 @@ const fetcher = ( * @param variables.startTime Time to start the count of total commits. * @param variables.ownerAffiliations The owner affiliations to filter by. Default: OWNER. * @param variables.pat PAT override or null. - * @returns Axios response. + * @returns The stats response, with every fetched page's repos merged in. * * @description Supports multi-page fetching when the `FETCH_MULTI_PAGE_STARS` * env variable is `true` or a fetch limit. @@ -156,61 +63,72 @@ const statsFetcher = async ({ includeDiscussions: boolean; includeDiscussionsAnswers: boolean; startTime: string | undefined; - ownerAffiliations: Array; + ownerAffiliations: UserInfoQueryVariables["ownerAffiliations"]; pat: string | null; }): Promise => { - let stats: StatsFetcherResponse | undefined; - let hasNextPage = true; - let endCursor: string | null = null; - let fetchedPages = 0; - while (hasNextPage) { - const variables: Record = { + // only the first request carries the stats themselves + let stats: StatsFetcherResponse = await retryer( + fetcher, + { login: username, - first: 100, - after: endCursor, + after: null, includeMergedPullRequests, includeDiscussions, includeDiscussionsAnswers, startTime, ownerAffiliations, - }; - const res = await retryer(fetcher, variables, pat); - if (res.data.errors) { - return res; - } + }, + pat, + ); + if (stats.data.errors) { + return stats; + } - // Store stats data. - const repoNodes = res.data.data.user.repositories.nodes; - if (stats) { - if (fetchedPages === 1) { - // deep copy to avoid mutating the response cached by the frontend - stats = structuredClone({ - data: stats.data, - statusText: stats.statusText, - }); - } - stats.data.data.user.repositories.nodes.push(...repoNodes); - } else { - stats = res; + const pageLimit = getConfig().fetchMultiPageStars; + + const extraRepoNodes: Array = []; + let pageRepositories = stats.data.data.user?.repositories; + let previousCursor: string | null = null; + let fetchedPages = 1; + while ( + fetchedPages < pageLimit && + // an unstarred repo on the page means the starred ones are exhausted + !pageRepositories?.nodes?.some((node) => node?.stargazerCount === 0) && + pageRepositories?.pageInfo.hasNextPage + ) { + const after = pageRepositories.pageInfo.endCursor; + // a null or non-advancing cursor would refetch the same page forever + if (after === null || after === previousCursor) { + break; } + previousCursor = after; - fetchedPages++; - const repoNodesWithStars = repoNodes.filter( - (node) => node.stargazerCount !== 0, + const page = await retryer( + reposFetcher, + { login: username, after, ownerAffiliations }, + pat, ); + if (page.data.errors) { + return { + data: { ...stats.data, errors: page.data.errors }, + statusText: page.statusText, + }; + } - hasNextPage = - (getConfig().fetchMultiPageStars === "true" || - Number(getConfig().fetchMultiPageStars) > fetchedPages) && - repoNodes.length === repoNodesWithStars.length && - res.data.data.user.repositories.pageInfo.hasNextPage; - - endCursor = res.data.data.user.repositories.pageInfo.endCursor; + pageRepositories = page.data.data.user?.repositories; + extraRepoNodes.push(...(pageRepositories?.nodes ?? [])); + fetchedPages++; } - if (!stats) { - throw new Error("No stats data fetched."); + if (extraRepoNodes.length > 0) { + // deep copy to avoid mutating the response cached by the frontend + stats = structuredClone({ + data: stats.data, + statusText: stats.statusText, + }); + stats.data.data.user?.repositories.nodes?.push(...extraRepoNodes); } + return stats; }; @@ -251,7 +169,9 @@ const fetchTotalItems = ( * @param username GitHub username. * @returns Total count. * - * @see #92#issuecomment-661026467 and #211 — the GraphQL API can't return this. + * The GraphQL API can't return this. + * @see https://github.com/anuraghazra/github-readme-stats/issues/92#issuecomment-661026467 + * @see https://github.com/anuraghazra/github-readme-stats/pull/211 */ const totalItemsFetcher = async ( username: string, @@ -451,6 +371,9 @@ const fetchStats = async ( } const user = res.data.data.user; + if (!user) { + throw new CustomError("Could not fetch user.", CustomError.USER_NOT_FOUND); + } stats.name = user.name || user.login; @@ -505,9 +428,9 @@ const fetchStats = async ( ]; const repoToHide = new Set(allExcludedRepos); - stats.totalStars = user.repositories.nodes - .filter((data) => !repoToHide.has(data.name)) - .reduce((prev, curr) => prev + curr.stargazerCount, 0); + stats.totalStars = (user.repositories.nodes ?? []) + .filter((data) => !!data && !repoToHide.has(data.name)) + .reduce((prev, curr) => prev + (curr?.stargazerCount ?? 0), 0); stats.rank = calculateRank({ all_commits: include_all_commits, diff --git a/packages/core/src/fetchers/top-languages.ts b/packages/core/src/fetchers/top-languages.ts index a5ad24c618cfe..ff4e921b953c9 100644 --- a/packages/core/src/fetchers/top-languages.ts +++ b/packages/core/src/fetchers/top-languages.ts @@ -1,79 +1,19 @@ -import type { AxiosResponse } from "axios"; - import { getConfig } from "../common/config.js"; import { CustomError, MissingParamError } from "../common/error.js"; import { wrapTextMultiline } from "../common/fmt.js"; -import { request } from "../common/http.js"; +import { createGraphQLFetcher } from "../common/http.js"; import { logger } from "../common/log.js"; import { parseOwnerAffiliations } from "../common/ops.js"; import { retryer } from "../common/retryer.js"; +import { TopLanguagesDocument } from "../graphql/generated/top-languages.js"; +import type { + TopLanguageFragment, + TopLanguagesRepositoryFragment, +} from "../graphql/generated/top-languages.js"; import type { Lang, TopLangData } from "./types.js"; -/** - * Top languages fetcher object. - * - * @param variables Fetcher variables. - * @param token GitHub token. - * @returns Languages fetcher response. - */ -const fetcher = ( - variables: Record, - token: string, -): Promise => { - return request( - { - query: ` - query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login) { - # do not fetch forks - repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) { - nodes { - name - languages(first: 10, orderBy: {field: SIZE, direction: DESC}) { - edges { - size - node { - color - name - } - } - } - } - } - } - } - `, - variables, - }, - { - Authorization: `token ${token}`, - }, - ); -}; - -/** A language edge within a repository's `languages` connection. */ -interface LanguageEdge { - size: number; - node: { color: string; name: string }; -} - -/** A repository node returned by the query. */ -interface RepositoryNode { - name: string; - /** Not selected by the query; only here so the no-op sort below type-checks. */ - size?: number; - languages: { edges: Array }; -} - -/** Shape of `response.data` returned by the top-languages query. */ -interface TopLanguagesQueryResponse { - data: { - user: { - repositories: { nodes: Array }; - }; - }; -} +const fetcher = createGraphQLFetcher(TopLanguagesDocument, "token"); /** * Fetch top languages for a given username. @@ -99,7 +39,7 @@ const fetchTopLanguages = async ( } const affiliations = parseOwnerAffiliations(ownerAffiliations); - const res = await retryer( + const res = await retryer( fetcher, { login: username, @@ -141,18 +81,22 @@ const fetchTopLanguages = async ( }); // filter out repositories to be hidden - const repoNodes = res.data.data.user.repositories.nodes - .sort((a, b) => (b.size ?? 0) - (a.size ?? 0)) - .filter((node) => !repoToHide[node.name]); + const repoNodes = (res.data.data.user?.repositories.nodes ?? []).filter( + (node): node is TopLanguagesRepositoryFragment => + !!node && !repoToHide[node.name], + ); // flatten edges across repos. Order matters: `concat(acc)` prepends, and the // shared repoCount below depends on visitation order. - const languageEdges = repoNodes - .filter((node) => node.languages.edges.length > 0) - .reduce>( - (acc, curr) => curr.languages.edges.concat(acc), - [], - ); + const languageEdges = repoNodes.reduce>( + (acc, repo) => { + const edges = (repo.languages?.edges ?? []).filter( + (edge): edge is TopLanguageFragment => !!edge, + ); + return edges.length > 0 ? edges.concat(acc) : acc; + }, + [], + ); // accumulate size and repo count per language const languageMap: Record = {}; diff --git a/packages/core/src/fetchers/types.ts b/packages/core/src/fetchers/types.ts index 5951d2b937544..da070f3daa61d 100644 --- a/packages/core/src/fetchers/types.ts +++ b/packages/core/src/fetchers/types.ts @@ -1,3 +1,5 @@ +import type { RepoInfoFragment } from "../graphql/generated/repo.js"; + export interface GistData { name: string; nameWithOwner: string; @@ -7,21 +9,17 @@ export interface GistData { forksCount: number; } -export interface RepoInfo { - name: string; - nameWithOwner: string; - isPrivate: boolean; - isArchived: boolean; - isTemplate: boolean; - stargazerCount: number; - description: string | null; +/** + * What the repo query returns, with `primaryLanguage` loosened: + * the schema says `name` is non-null, but the card falls back to defaults for callers passing untyped data. + */ +type RepoInfo = Omit & { primaryLanguage: { color: string | null; id?: string; name: string | null; } | null; - forkCount: number; -} +}; export interface RepoUserStats { // only present when the matching include_* flag is set (see fetchRepoUserStats) diff --git a/packages/core/src/graphql/generated/common.ts b/packages/core/src/graphql/generated/common.ts new file mode 100644 index 0000000000000..33a09a5cbec1e --- /dev/null +++ b/packages/core/src/graphql/generated/common.ts @@ -0,0 +1,26 @@ +// Generated file — see .github/CONTRIBUTING.md + +export type Maybe = T | null; +export type InputMaybe = Maybe; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + /** An ISO-8601 encoded UTC date string. */ + DateTime: { input: string; output: string }; +}; + +/** The affiliation of a user to a repository */ +export type 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"; diff --git a/packages/core/src/graphql/generated/gist.ts b/packages/core/src/graphql/generated/gist.ts new file mode 100644 index 0000000000000..da4a9491d9212 --- /dev/null +++ b/packages/core/src/graphql/generated/gist.ts @@ -0,0 +1,59 @@ +// Generated file — see .github/CONTRIBUTING.md + +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +import { graphqlDocument } from "../graphqlDocument.js"; +export type GistFileInfoFragment = { + name: string | null; + size: number | null; + language: { name: string } | null; +}; + +export type GistInfoQueryVariables = Exact<{ + gistName: string; +}>; + +export type GistInfoQuery = { + viewer: { + gist: { + description: string | null; + stargazerCount: number; + owner: { login: string } | { login: string } | null; + forks: { totalCount: number }; + files: Array<{ + name: string | null; + size: number | null; + language: { name: string } | null; + } | null> | null; + } | null; + }; +}; + +export const GistInfoDocument = graphqlDocument< + GistInfoQuery, + GistInfoQueryVariables +>(` +query gistInfo($gistName: String!) { + viewer { + gist(name: $gistName) { + description + owner { + login + } + stargazerCount + forks { + totalCount + } + files { + ...GistFileInfo + } + } + } +} +fragment GistFileInfo on GistFile { + name + language { + name + } + size +}`); diff --git a/packages/core/src/graphql/generated/repo.ts b/packages/core/src/graphql/generated/repo.ts new file mode 100644 index 0000000000000..3711456cc0cd3 --- /dev/null +++ b/packages/core/src/graphql/generated/repo.ts @@ -0,0 +1,90 @@ +// Generated file — see .github/CONTRIBUTING.md + +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +import { graphqlDocument } from "../graphqlDocument.js"; +export type RepoInfoFragment = { + name: string; + nameWithOwner: string; + isPrivate: boolean; + isArchived: boolean; + isTemplate: boolean; + stargazerCount: number; + description: string | null; + forkCount: number; + primaryLanguage: { color: string | null; id: string; name: string } | null; +}; + +export type GetRepoQueryVariables = Exact<{ + login: string; + repo: string; +}>; + +export type GetRepoQuery = { + user: { + repository: { + name: string; + nameWithOwner: string; + isPrivate: boolean; + isArchived: boolean; + isTemplate: boolean; + stargazerCount: number; + description: string | null; + forkCount: number; + primaryLanguage: { + color: string | null; + id: string; + name: string; + } | null; + } | null; + } | null; + organization: { + repository: { + name: string; + nameWithOwner: string; + isPrivate: boolean; + isArchived: boolean; + isTemplate: boolean; + stargazerCount: number; + description: string | null; + forkCount: number; + primaryLanguage: { + color: string | null; + id: string; + name: string; + } | null; + } | null; + } | null; +}; + +export const GetRepoDocument = graphqlDocument< + GetRepoQuery, + GetRepoQueryVariables +>(` +query getRepo($login: String!, $repo: String!) { + user(login: $login) { + repository(name: $repo) { + ...RepoInfo + } + } + organization(login: $login) { + repository(name: $repo) { + ...RepoInfo + } + } +} +fragment RepoInfo on Repository { + name + nameWithOwner + isPrivate + isArchived + isTemplate + stargazerCount + description + primaryLanguage { + color + id + name + } + forkCount +}`); diff --git a/packages/core/src/graphql/generated/stats.ts b/packages/core/src/graphql/generated/stats.ts new file mode 100644 index 0000000000000..e8d39af0df2ed --- /dev/null +++ b/packages/core/src/graphql/generated/stats.ts @@ -0,0 +1,169 @@ +// Generated file — see .github/CONTRIBUTING.md + +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +import type * as Types from "./common.js"; + +import { graphqlDocument } from "../graphqlDocument.js"; +export type RepoNodeFragment = { name: string; stargazerCount: number }; + +export type RepoStarsFragment = { + repositories: { + totalCount: number; + nodes: Array<{ name: string; stargazerCount: number } | null> | null; + pageInfo: { hasNextPage: boolean; endCursor: string | null }; + }; +}; + +export type UserReposQueryVariables = Exact<{ + login: string; + after?: string | null | undefined; + ownerAffiliations?: + | Array + | Types.RepositoryAffiliation + | null + | undefined; +}>; + +export type UserReposQuery = { + user: { + repositories: { + totalCount: number; + nodes: Array<{ name: string; stargazerCount: number } | null> | null; + pageInfo: { hasNextPage: boolean; endCursor: string | null }; + }; + } | null; +}; + +export type UserInfoQueryVariables = Exact<{ + login: string; + after?: string | null | undefined; + includeMergedPullRequests: boolean; + includeDiscussions: boolean; + includeDiscussionsAnswers: boolean; + startTime?: string | null | undefined; + ownerAffiliations?: + | Array + | Types.RepositoryAffiliation + | null + | undefined; +}>; + +export type UserInfoQuery = { + user: { + name: string | null; + login: string; + commits: { totalCommitContributions: number }; + reviews: { totalPullRequestReviewContributions: number }; + repositoriesContributedTo: { totalCount: number }; + pullRequests: { totalCount: number }; + mergedPullRequests?: { totalCount: number }; + openIssues: { totalCount: number }; + closedIssues: { totalCount: number }; + followers: { totalCount: number }; + repositoryDiscussions?: { totalCount: number }; + repositoryDiscussionComments?: { totalCount: number }; + repositories: { + totalCount: number; + nodes: Array<{ name: string; stargazerCount: number } | null> | null; + pageInfo: { hasNextPage: boolean; endCursor: string | null }; + }; + } | null; +}; + +export const UserReposDocument = graphqlDocument< + UserReposQuery, + UserReposQueryVariables +>(` +query userRepos($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) { + user(login: $login) { + ...RepoStars + } +} +fragment RepoStars on User { + repositories( + first: 100 + after: $after + ownerAffiliations: $ownerAffiliations + orderBy: {direction: DESC, field: STARGAZERS} + ) { + totalCount + nodes { + ...RepoNode + } + pageInfo { + hasNextPage + endCursor + } + } +} +fragment RepoNode on Repository { + name + stargazerCount +}`); + +export const UserInfoDocument = graphqlDocument< + UserInfoQuery, + UserInfoQueryVariables +>(` +query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) { + user(login: $login) { + name + login + commits: contributionsCollection(from: $startTime) { + totalCommitContributions + } + reviews: contributionsCollection { + totalPullRequestReviewContributions + } + repositoriesContributedTo( + first: 1 + contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY] + ) { + totalCount + } + pullRequests(first: 1) { + totalCount + } + mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) { + totalCount + } + openIssues: issues(states: OPEN) { + totalCount + } + closedIssues: issues(states: CLOSED) { + totalCount + } + followers { + totalCount + } + repositoryDiscussions @include(if: $includeDiscussions) { + totalCount + } + repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) { + totalCount + } + ...RepoStars + } +} +fragment RepoStars on User { + repositories( + first: 100 + after: $after + ownerAffiliations: $ownerAffiliations + orderBy: {direction: DESC, field: STARGAZERS} + ) { + totalCount + nodes { + ...RepoNode + } + pageInfo { + hasNextPage + endCursor + } + } +} +fragment RepoNode on Repository { + name + stargazerCount +}`); diff --git a/packages/core/src/graphql/generated/top-languages.ts b/packages/core/src/graphql/generated/top-languages.ts new file mode 100644 index 0000000000000..03ff1245ea923 --- /dev/null +++ b/packages/core/src/graphql/generated/top-languages.ts @@ -0,0 +1,75 @@ +// Generated file — see .github/CONTRIBUTING.md + +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +import type * as Types from "./common.js"; + +import { graphqlDocument } from "../graphqlDocument.js"; +export type TopLanguageFragment = { + size: number; + node: { color: string | null; name: string }; +}; + +export type TopLanguagesRepositoryFragment = { + name: string; + languages: { + edges: Array<{ + size: number; + node: { color: string | null; name: string }; + } | null> | null; + } | null; +}; + +export type TopLanguagesQueryVariables = Exact<{ + login: string; + ownerAffiliations?: + | Array + | Types.RepositoryAffiliation + | null + | undefined; +}>; + +export type TopLanguagesQuery = { + user: { + repositories: { + nodes: Array<{ + name: string; + languages: { + edges: Array<{ + size: number; + node: { color: string | null; name: string }; + } | null> | null; + } | null; + } | null> | null; + }; + } | null; +}; + +export const TopLanguagesDocument = graphqlDocument< + TopLanguagesQuery, + TopLanguagesQueryVariables +>(` +query topLanguages($login: String!, $ownerAffiliations: [RepositoryAffiliation]) { + user(login: $login) { + repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) { + nodes { + ...TopLanguagesRepository + } + } + } +} +fragment TopLanguagesRepository on Repository { + name + languages(first: 10, orderBy: {field: SIZE, direction: DESC}) { + edges { + ...TopLanguage + } + } +} +fragment TopLanguage on LanguageEdge { + size + node { + color + name + } +}`); diff --git a/packages/core/src/graphql/graphqlDocument.ts b/packages/core/src/graphql/graphqlDocument.ts new file mode 100644 index 0000000000000..355f121db9789 --- /dev/null +++ b/packages/core/src/graphql/graphqlDocument.ts @@ -0,0 +1,26 @@ +/** + * A GraphQL query paired with the types of its result and variables. + * + * `text` is what goes over the wire; the other two members only carry types and are + * never assigned. + * Documents are generated from `queries/*.graphql` + * See `scripts/generate-graphql-types.js`. + */ +interface GraphQLDocument { + readonly text: string; + readonly __result?: TResult; + readonly __variables?: TVariables; +} + +/** + * Build a typed document. + * + * @param text The query text, including every fragment it spreads. + * @returns The document to hand to `httpGraphQLRequest`. + */ +const graphqlDocument = ( + text: string, +): GraphQLDocument => ({ text }); + +export { graphqlDocument }; +export type { GraphQLDocument }; diff --git a/packages/core/src/graphql/queries/gist.graphql b/packages/core/src/graphql/queries/gist.graphql new file mode 100644 index 0000000000000..7ea29f2da636f --- /dev/null +++ b/packages/core/src/graphql/queries/gist.graphql @@ -0,0 +1,25 @@ +fragment GistFileInfo on GistFile { + name + language { + name + } + size +} + +query gistInfo($gistName: String!) { + viewer { + gist(name: $gistName) { + description + owner { + login + } + stargazerCount + forks { + totalCount + } + files { + ...GistFileInfo + } + } + } +} diff --git a/packages/core/src/graphql/queries/repo.graphql b/packages/core/src/graphql/queries/repo.graphql new file mode 100644 index 0000000000000..4369a6510aae2 --- /dev/null +++ b/packages/core/src/graphql/queries/repo.graphql @@ -0,0 +1,28 @@ +fragment RepoInfo on Repository { + name + nameWithOwner + isPrivate + isArchived + isTemplate + stargazerCount + description + primaryLanguage { + color + id + name + } + forkCount +} + +query getRepo($login: String!, $repo: String!) { + user(login: $login) { + repository(name: $repo) { + ...RepoInfo + } + } + organization(login: $login) { + repository(name: $repo) { + ...RepoInfo + } + } +} diff --git a/packages/core/src/graphql/queries/stats.graphql b/packages/core/src/graphql/queries/stats.graphql new file mode 100644 index 0000000000000..8a4114fd0cb00 --- /dev/null +++ b/packages/core/src/graphql/queries/stats.graphql @@ -0,0 +1,83 @@ +fragment RepoNode on Repository { + name + stargazerCount +} + +fragment RepoStars on User { + repositories( + first: 100 + after: $after + ownerAffiliations: $ownerAffiliations + orderBy: { direction: DESC, field: STARGAZERS } + ) { + totalCount + nodes { + ...RepoNode + } + pageInfo { + hasNextPage + endCursor + } + } +} + +query userRepos( + $login: String! + $after: String + $ownerAffiliations: [RepositoryAffiliation] +) { + user(login: $login) { + ...RepoStars + } +} + +query userInfo( + $login: String! + $after: String + $includeMergedPullRequests: Boolean! + $includeDiscussions: Boolean! + $includeDiscussionsAnswers: Boolean! + $startTime: DateTime = null + $ownerAffiliations: [RepositoryAffiliation] +) { + user(login: $login) { + name + login + commits: contributionsCollection(from: $startTime) { + totalCommitContributions + } + reviews: contributionsCollection { + totalPullRequestReviewContributions + } + repositoriesContributedTo( + first: 1 + contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY] + ) { + totalCount + } + pullRequests(first: 1) { + totalCount + } + mergedPullRequests: pullRequests(states: MERGED) + @include(if: $includeMergedPullRequests) { + totalCount + } + openIssues: issues(states: OPEN) { + totalCount + } + closedIssues: issues(states: CLOSED) { + totalCount + } + followers { + totalCount + } + repositoryDiscussions @include(if: $includeDiscussions) { + totalCount + } + repositoryDiscussionComments(onlyAnswers: true) + @include(if: $includeDiscussionsAnswers) { + totalCount + } + ...RepoStars + } +} diff --git a/packages/core/src/graphql/queries/top-languages.graphql b/packages/core/src/graphql/queries/top-languages.graphql new file mode 100644 index 0000000000000..4dacc030ca0e0 --- /dev/null +++ b/packages/core/src/graphql/queries/top-languages.graphql @@ -0,0 +1,34 @@ +fragment TopLanguage on LanguageEdge { + size + node { + color + name + } +} + +fragment TopLanguagesRepository on Repository { + name + languages(first: 10, orderBy: { field: SIZE, direction: DESC }) { + edges { + ...TopLanguage + } + } +} + +query topLanguages( + $login: String! + $ownerAffiliations: [RepositoryAffiliation] +) { + user(login: $login) { + # do not fetch forks + repositories( + ownerAffiliations: $ownerAffiliations + isFork: false + first: 100 + ) { + nodes { + ...TopLanguagesRepository + } + } + } +} diff --git a/packages/core/tests/fetchStats.test.ts b/packages/core/tests/fetchStats.test.ts index 513b924ed7a51..ac968d24eebfa 100644 --- a/packages/core/tests/fetchStats.test.ts +++ b/packages/core/tests/fetchStats.test.ts @@ -76,6 +76,11 @@ const data_repo = { }, }; +const data_repo_more_pages = structuredClone(data_repo); +data_repo_more_pages.data.user.repositories.pageInfo.hasNextPage = true; +// distinct from data_stats' cursor so the chaining assertion can see advancement +data_repo_more_pages.data.user.repositories.pageInfo.endCursor = "cursor-2"; + const data_repo_zero_stars = { data: { user: { @@ -111,7 +116,7 @@ const error = { const mock = new MockAdapter(axios); beforeEach(() => { - process.env["FETCH_MULTI_PAGE_STARS"] = "false"; // Set to `false` to fetch only one page of stars. + vi.stubEnv("FETCH_MULTI_PAGE_STARS", "false"); // Set to `false` to fetch only one page of stars. loadConfigFromEnv(); mock.onPost("https://api.github.com/graphql").reply((cfg) => { const req = JSON.parse(cfg.data as string) as { @@ -131,6 +136,7 @@ beforeEach(() => { afterEach(() => { mock.reset(); + vi.unstubAllEnvs(); }); describe("Test fetchStats", () => { @@ -317,7 +323,7 @@ describe("Test fetchStats", () => { }); it("should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`", async () => { - process.env["FETCH_MULTI_PAGE_STARS"] = "true"; + vi.stubEnv("FETCH_MULTI_PAGE_STARS", "true"); loadConfigFromEnv(); const stats = await fetchStats("anuraghazra"); @@ -354,7 +360,7 @@ describe("Test fetchStats", () => { }); it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `false`", async () => { - process.env["FETCH_MULTI_PAGE_STARS"] = "false"; + vi.stubEnv("FETCH_MULTI_PAGE_STARS", "false"); loadConfigFromEnv(); const stats = await fetchStats("anuraghazra"); @@ -391,7 +397,7 @@ describe("Test fetchStats", () => { }); it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is not set", async () => { - process.env["FETCH_MULTI_PAGE_STARS"] = undefined; + vi.stubEnv("FETCH_MULTI_PAGE_STARS", undefined); loadConfigFromEnv(); const stats = await fetchStats("anuraghazra"); @@ -427,6 +433,51 @@ describe("Test fetchStats", () => { }); }); + it("should fetch at most 'FETCH_MULTI_PAGE_STARS' pages when it is a number", async () => { + vi.stubEnv("FETCH_MULTI_PAGE_STARS", "3"); + loadConfigFromEnv(); + mock.reset(); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_stats) + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_repo_more_pages) + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_repo_more_pages) + // a fourth page is available but must not be requested + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_repo); + + const stats = await fetchStats("anuraghazra"); + + // the stats page plus two repo pages, even though every page has a next one + expect(mock.history.post).toHaveLength(3); + expect(stats.totalStars).toBe(500); + // each page is requested with the cursor the previous one returned + const cursors = mock.history.post.map( + (req) => + (JSON.parse(req.data as string) as { variables: { after: unknown } }) + .variables.after, + ); + expect(cursors).toStrictEqual([null, "cursor", "cursor-2"]); + }); + + it("should throw when a page after the first returns an error", async () => { + vi.stubEnv("FETCH_MULTI_PAGE_STARS", "true"); + loadConfigFromEnv(); + mock.reset(); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_stats) + .onPost("https://api.github.com/graphql") + .replyOnce(200, error); + + await expect(fetchStats("anuraghazra")).rejects.toThrow( + "Could not resolve to a User with the login of 'noname'.", + ); + expect(mock.history.post).toHaveLength(2); + }); + it("should not fetch additional stats data when it not requested", async () => { const stats = await fetchStats("anuraghazra"); const rank = calculateRank({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc153a921e890..db4b3b676f80e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,13 +31,13 @@ importers: devDependencies: '@eslint-react/eslint-plugin': specifier: 5.17.1 - version: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@eslint/compat': specifier: 2.1.0 - version: 2.1.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0)) + version: 2.1.0(eslint@10.7.0(jiti@2.7.0)) '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0)) + version: 10.0.1(eslint@10.7.0(jiti@2.7.0)) '@playwright/test': specifier: 1.61.1 version: 1.61.1 @@ -49,16 +49,16 @@ importers: version: 4.1.10(vitest@4.1.10) eslint: specifier: 10.7.0 - version: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + version: 10.7.0(jiti@2.7.0) eslint-import-resolver-typescript: specifier: 4.4.5 - version: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) + version: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)))(eslint@10.7.0(jiti@2.7.0)) eslint-plugin-import-x: specifier: 4.17.1 - version: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) + version: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)) eslint-plugin-jsdoc: specifier: 63.1.0 - version: 63.1.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) + version: 63.1.0(eslint@10.7.0(jiti@2.7.0)) globals: specifier: 17.7.0 version: 17.7.0 @@ -82,7 +82,7 @@ importers: version: 6.0.3 typescript-eslint: specifier: 8.65.0 - version: 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) vitest: specifier: catalog:default version: 4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)(yaml@2.9.0)) @@ -94,17 +94,17 @@ importers: version: link:../../packages/core axios: specifier: catalog:default - version: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + version: 1.18.1 pg: specifier: ^8.22.0 version: 8.22.0 devDependencies: axios-mock-adapter: specifier: 2.1.0 - version: 2.1.0(axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) + version: 2.1.0(axios@1.18.1) express: specifier: 5.2.1 - version: 5.2.1(supports-color@7.2.0) + version: 5.2.1 jsdom: specifier: catalog:default version: 29.1.1 @@ -128,10 +128,10 @@ importers: version: 4.3.3(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)(yaml@2.9.0)) axios: specifier: catalog:default - version: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + version: 1.18.1 axios-cache-interceptor: specifier: ^1.12.0 - version: 1.12.1(axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) + version: 1.12.1(axios@1.18.1) daisyui: specifier: ^5.6.18 version: 5.7.8 @@ -192,7 +192,7 @@ importers: dependencies: axios: specifier: catalog:default - version: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + version: 1.18.1 emoji-name-map: specifier: ^2.0.3 version: 2.0.3 @@ -200,6 +200,18 @@ importers: specifier: ^1.0.0 version: 1.0.0 devDependencies: + '@graphql-codegen/core': + specifier: 6.2.0 + version: 6.2.0(graphql@16.11.0) + '@graphql-codegen/typescript': + specifier: 6.1.0 + version: 6.1.0(graphql@16.11.0) + '@graphql-codegen/typescript-operations': + specifier: 6.1.2 + version: 6.1.2(graphql@16.11.0) + '@octokit/graphql-schema': + specifier: 15.26.1 + version: 15.26.1 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -211,7 +223,10 @@ importers: version: 1.1.1 axios-mock-adapter: specifier: 2.1.0 - version: 2.1.0(axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) + version: 2.1.0(axios@1.18.1) + graphql: + specifier: 16.11.0 + version: 16.11.0 js-yaml: specifier: 5.2.1 version: 5.2.1 @@ -227,6 +242,11 @@ packages: '@adobe/css-tools@4.5.0': resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@ardatan/relay-compiler@13.0.2': + resolution: {integrity: sha512-VFpv9UP820SiwDUPYtq7PmD3jifzZlevkQ26bhbSzFeruSTys0eHzQCZyKg+IhgmZzwPI9AFjPe26ABNjGeIKg==} + peerDependencies: + graphql: '*' + '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -263,6 +283,9 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/runtime@8.0.0': + resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -449,6 +472,81 @@ packages: '@noble/hashes': optional: true + '@graphql-codegen/core@6.2.0': + resolution: {integrity: sha512-RZadhhwYhuy2ZdIGK40vYVBMzXEFGkCC+58MUC/F2af/gKznEYNzHgmNBUBCk/BTklyUsNu0mIXmyGE4tTA0PA==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/plugin-helpers@7.1.0': + resolution: {integrity: sha512-ieJH7kZ5oSZKBPJs7CvHMrFY/CLYLklqv74ir93qMwRna6geZsbIMoJzTDBXohxcQTITiProiYSGrEtZjIpYGg==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/schema-ast@6.1.0': + resolution: {integrity: sha512-/xuGkM5gUNFRoaQLumKbENdX7Hc8ha49z9OXsEZY8E+46mMjqzXGF0NtCJ892cmoX7EUgI5c8T+LZqS2upx2Aw==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/typescript-operations@6.1.2': + resolution: {integrity: sha512-EP9xry09q4cOVaf/aC4NO3/SwvXRNzlJIe4dhfA0xyy45Taix5yDL3jeJpmJIv03sa7nK5udVs5vZqdHFU8Xmw==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + graphql-sock: ^1.0.0 + peerDependenciesMeta: + graphql-sock: + optional: true + + '@graphql-codegen/typescript@6.1.0': + resolution: {integrity: sha512-2Hu3111O/AwV28Ap7tNsixlmXSAJuQbQArQklx+IC/tNswpckZnCfmlcBtTJrGU1+mJXEneJXGfb2XWvKjbhlQ==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/visitor-plugin-common@7.2.2': + resolution: {integrity: sha512-nOkAVd8J8r2YdHm9Z4YrBiy+3IQgE8Ndn/EiRWTvWuemMEhioBWyvdlnbq5rHmIo2bNdRQ5ghs0Q3jw7YBrwLQ==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/merge@9.2.2': + resolution: {integrity: sha512-DSLLAztOIQId7QE3m8Ehk5lV+0pjxNSSRDHPzlYQ9E4KJ9AoUMBprC4C+eX3v4srh05S2ujm5/veqAr5yEWFSQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/optimize@2.0.0': + resolution: {integrity: sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/relay-operation-optimizer@7.1.8': + resolution: {integrity: sha512-s16NYT+66VSLCITBURoGNTwh+YvZRSlW3MtFJC2gsvrHZHf6KpCvIR3Qju4yh0FtCoN7Wg7yOI/JT/UzaMEOwQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/schema@10.0.38': + resolution: {integrity: sha512-Kckk2/vm+rELJ7ijvFaAn9ouWSVUTD0D4SJIvYF4rKeuQHAfCzE/jzMFonZVpTXleqJjPXLZjVbuaMorw7A5Og==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@11.2.2': + resolution: {integrity: sha512-Do2A/t6ayqOma8m7PvpYMsCBswL+NB35BQSng4GWSBqafL2/BnfAZy/NSENPwV721Rm2fG0BHETFC0+FJJZmLw==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -492,6 +590,9 @@ packages: '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + '@octokit/graphql-schema@15.26.1': + resolution: {integrity: sha512-RFDC2MpRBd4AxSRvUeBIVeBU7ojN/SxDfALUd7iVYOSeEK3gZaqR2MGOysj4Zh2xj2RY5fQAUT+Oqq7hWTraMA==} + '@oxc-parser/binding-android-arm-eabi@0.137.0': resolution: {integrity: sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1252,6 +1353,10 @@ packages: '@vitest/utils@4.1.10': resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@whatwg-node/promise-helpers@1.3.2': + resolution: {integrity: sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==} + engines: {node: '>=16.0.0'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1317,6 +1422,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + axios-cache-interceptor@1.12.1: resolution: {integrity: sha512-0CMA7US/V9tLfJzZyxY5NBzRKvWBkOXeNJLGO1XB/B8xglp36Kdvt72i2Xr3JVrYyhWEGayUFzsSs6piHZadCw==} engines: {node: '>=12'} @@ -1368,6 +1477,12 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + change-case-all@2.1.0: + resolution: {integrity: sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1388,6 +1503,10 @@ packages: resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} engines: {node: '>= 12.0.0'} + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -1414,6 +1533,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cross-inspect@1.0.1: + resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} + engines: {node: '>=16.0.0'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1458,6 +1581,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dependency-graph@1.0.0: + resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} + engines: {node: '>=4'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1785,6 +1912,16 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql-tag@2.12.7: + resolution: {integrity: sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1842,6 +1979,13 @@ packages: immer@11.1.15: resolution: {integrity: sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==} + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + + import-from@4.0.0: + resolution: {integrity: sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==} + engines: {node: '>=12.2'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1853,10 +1997,17 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} @@ -1882,6 +2033,18 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2036,6 +2199,10 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -2054,6 +2221,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2161,6 +2332,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + parse-filepath@1.0.2: + resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} + engines: {node: '>=0.8'} + parse-imports-exports@0.2.4: resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} @@ -2182,6 +2357,14 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-root-regex@0.1.2: + resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==} + engines: {node: '>=0.10.0'} + + path-root@0.1.1: + resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} + engines: {node: '>=0.10.0'} + path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -2473,6 +2656,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sponge-case@2.0.3: + resolution: {integrity: sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==} + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -2518,6 +2704,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + swap-case@3.0.3: + resolution: {integrity: sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -2543,6 +2732,9 @@ packages: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} + title-case@3.0.3: + resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} + tldts-core@7.4.9: resolution: {integrity: sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==} @@ -2609,6 +2801,10 @@ packages: resolution: {integrity: sha512-60m9IVGbavD6jholbxt0jVBXZkEB/HsMZq7Tyaghseve2/Sf0zQRAIfWsD34sde+DKP2tBxJS2wP88ZM0D1FhA==} engines: {node: '>=14'} + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -2795,6 +2991,13 @@ snapshots: '@adobe/css-tools@4.5.0': {} + '@ardatan/relay-compiler@13.0.2(graphql@16.11.0)': + dependencies: + '@babel/runtime': 8.0.0 + graphql: 16.11.0 + immutable: 5.1.9 + invariant: 2.2.4 + '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -2831,6 +3034,8 @@ snapshots: '@babel/runtime@7.29.7': {} + '@babel/runtime@8.0.0': {} + '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -2919,110 +3124,110 @@ snapshots: '@es-joy/resolve.exports@1.2.0': {} - '@eslint-community/eslint-utils@4.10.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.7.0(jiti@2.7.0))': dependencies: - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/ast@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) string-ts: 2.3.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/core@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/core@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/eslint-plugin@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/eslint-plugin@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) - eslint-plugin-react-dom: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-jsx: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-naming-convention: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-rsc: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-web-api: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-x: 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) + eslint-plugin-react-dom: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-react-jsx: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-react-naming-convention: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-react-rsc: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-react-web-api: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-react-x: 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/eslint@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/eslint@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/jsx@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/jsx@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/shared@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/shared@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 zod: 4.4.3 transitivePeerDependencies: - supports-color - '@eslint-react/var@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/var@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint/compat@2.1.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))': + '@eslint/compat@2.1.0(eslint@10.7.0(jiti@2.7.0))': dependencies: '@eslint/core': 1.2.1 optionalDependencies: - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) - '@eslint/config-array@0.23.5(supports-color@7.2.0)': + '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 10.2.6 transitivePeerDependencies: - supports-color @@ -3035,9 +3240,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.7.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -3048,6 +3253,99 @@ snapshots: '@exodus/bytes@1.15.1': {} + '@graphql-codegen/core@6.2.0(graphql@16.11.0)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.1.0(graphql@16.11.0) + '@graphql-tools/schema': 10.0.38(graphql@16.11.0) + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-codegen/plugin-helpers@7.1.0(graphql@16.11.0)': + dependencies: + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + change-case-all: 2.1.0 + common-tags: 1.8.2 + graphql: 16.11.0 + import-from: 4.0.0 + tslib: 2.8.1 + + '@graphql-codegen/schema-ast@6.1.0(graphql@16.11.0)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.1.0(graphql@16.11.0) + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-codegen/typescript-operations@6.1.2(graphql@16.11.0)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.1.0(graphql@16.11.0) + '@graphql-codegen/schema-ast': 6.1.0(graphql@16.11.0) + '@graphql-codegen/visitor-plugin-common': 7.2.2(graphql@16.11.0) + auto-bind: 5.0.1 + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-codegen/typescript@6.1.0(graphql@16.11.0)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.1.0(graphql@16.11.0) + '@graphql-codegen/schema-ast': 6.1.0(graphql@16.11.0) + '@graphql-codegen/visitor-plugin-common': 7.2.2(graphql@16.11.0) + auto-bind: 5.0.1 + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-codegen/visitor-plugin-common@7.2.2(graphql@16.11.0)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.1.0(graphql@16.11.0) + '@graphql-tools/optimize': 2.0.0(graphql@16.11.0) + '@graphql-tools/relay-operation-optimizer': 7.1.8(graphql@16.11.0) + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + auto-bind: 5.0.1 + change-case-all: 2.1.0 + dependency-graph: 1.0.0 + graphql: 16.11.0 + graphql-tag: 2.12.7(graphql@16.11.0) + parse-filepath: 1.0.2 + tslib: 2.8.1 + + '@graphql-tools/merge@9.2.2(graphql@16.11.0)': + dependencies: + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-tools/optimize@2.0.0(graphql@16.11.0)': + dependencies: + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-tools/relay-operation-optimizer@7.1.8(graphql@16.11.0)': + dependencies: + '@ardatan/relay-compiler': 13.0.2(graphql@16.11.0) + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-tools/schema@10.0.38(graphql@16.11.0)': + dependencies: + '@graphql-tools/merge': 9.2.2(graphql@16.11.0) + '@graphql-tools/utils': 11.2.2(graphql@16.11.0) + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-tools/utils@11.2.2(graphql@16.11.0)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.11.0) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.11.0 + tslib: 2.8.1 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.11.0)': + dependencies: + graphql: 16.11.0 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -3104,6 +3402,11 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@octokit/graphql-schema@15.26.1': + dependencies: + graphql: 16.11.0 + graphql-tag: 2.12.7(graphql@16.11.0) + '@oxc-parser/binding-android-arm-eabi@0.137.0': optional: true @@ -3446,15 +3749,15 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.65.0 - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -3462,23 +3765,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.65.0 - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + debug: 4.4.3 + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.65.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.65.0(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3492,13 +3795,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.7.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -3506,13 +3809,13 @@ snapshots: '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.65.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.65.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/project-service': 8.65.0(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 '@typescript-eslint/visitor-keys': 8.65.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 @@ -3521,13 +3824,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3669,6 +3972,10 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.1 + '@whatwg-node/promise-helpers@1.3.2': + dependencies: + tslib: 2.8.1 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -3680,9 +3987,9 @@ snapshots: acorn@8.18.0: {} - agent-base@6.0.2(supports-color@7.2.0): + agent-base@6.0.2: dependencies: - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -3725,26 +4032,28 @@ snapshots: asynckit@0.4.0: {} - axios-cache-interceptor@1.12.1(axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)): + auto-bind@5.0.1: {} + + axios-cache-interceptor@1.12.1(axios@1.18.1): dependencies: - axios: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + axios: 1.18.1 cache-parser: 1.2.6 fast-defer: 1.1.9 http-vary: 1.0.3 object-code: 2.0.0 try: 1.0.3 - axios-mock-adapter@2.1.0(axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)): + axios-mock-adapter@2.1.0(axios@1.18.1): dependencies: - axios: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + axios: 1.18.1 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 - axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): + axios@1.18.1: dependencies: - follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0)) + follow-redirects: 1.16.0 form-data: 4.0.6 - https-proxy-agent: 5.0.1(supports-color@7.2.0) + https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -3758,11 +4067,11 @@ snapshots: birecord@0.1.2: {} - body-parser@2.3.0(supports-color@7.2.0): + body-parser@2.3.0: dependencies: bytes: 3.1.2 content-type: 2.0.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 @@ -3792,6 +4101,15 @@ snapshots: chai@6.2.2: {} + change-case-all@2.1.0: + dependencies: + change-case: 5.4.4 + sponge-case: 2.0.3 + swap-case: 3.0.3 + title-case: 3.0.3 + + change-case@5.4.4: {} + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -3809,6 +4127,8 @@ snapshots: comment-parser@1.4.7: {} + common-tags@1.8.2: {} + compare-versions@6.1.1: {} content-disposition@1.1.0: {} @@ -3823,6 +4143,10 @@ snapshots: cookie@0.7.2: {} + cross-inspect@1.0.1: + dependencies: + tslib: 2.8.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3847,11 +4171,9 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - debug@4.4.3(supports-color@7.2.0): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 decimal.js@10.6.0: {} @@ -3861,6 +4183,8 @@ snapshots: depd@2.0.0: {} + dependency-graph@1.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -3920,10 +4244,10 @@ snapshots: optionalDependencies: unrs-resolver: 1.12.2 - eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)))(eslint@10.7.0(jiti@2.7.0)): dependencies: - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + debug: 4.4.3 + eslint: 10.7.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 @@ -3931,16 +4255,16 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) + eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)): dependencies: '@typescript-eslint/types': 8.65.0 comment-parser: 1.4.7 - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + debug: 4.4.3 + eslint: 10.7.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.6 @@ -3948,19 +4272,19 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - supports-color - eslint-plugin-jsdoc@63.1.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-jsdoc@63.1.0(eslint@10.7.0(jiti@2.7.0)): dependencies: '@es-joy/jsdoccomment': 0.88.0 '@es-joy/resolve.exports': 1.2.0 are-docs-informative: 0.0.2 comment-parser: 1.4.7 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -3972,93 +4296,93 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-dom@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-jsx@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-jsx@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-naming-convention@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-naming-convention@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-rsc@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-rsc@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-web-api@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) birecord: 0.1.2 - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) ts-pattern: 5.9.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-x@5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-x@5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/core': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/eslint': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/jsx': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/shared': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint-react/var': 5.17.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + eslint: 10.7.0(jiti@2.7.0) string-ts: 2.3.1 ts-api-utils: 2.5.0(typescript@6.0.3) ts-pattern: 5.9.0 @@ -4077,11 +4401,11 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0): + eslint@10.7.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5(supports-color@7.2.0) + '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 @@ -4091,7 +4415,7 @@ snapshots: '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -4142,20 +4466,20 @@ snapshots: expect-type@1.4.0: {} - express@5.2.1(supports-color@7.2.0): + express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.3.0(supports-color@7.2.0) + body-parser: 2.3.0 content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1(supports-color@7.2.0) + finalhandler: 2.1.1 fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -4166,9 +4490,9 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.3 range-parser: 1.3.0 - router: 2.2.0(supports-color@7.2.0) - send: 1.2.1(supports-color@7.2.0) - serve-static: 2.2.1(supports-color@7.2.0) + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -4195,9 +4519,9 @@ snapshots: dependencies: flat-cache: 4.0.1 - finalhandler@2.1.1(supports-color@7.2.0): + finalhandler@2.1.1: dependencies: - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -4218,9 +4542,7 @@ snapshots: flatted@3.4.3: {} - follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)): - optionalDependencies: - debug: 4.4.3(supports-color@7.2.0) + follow-redirects@1.16.0: {} form-data@4.0.6: dependencies: @@ -4282,6 +4604,13 @@ snapshots: graceful-fs@4.2.11: {} + graphql-tag@2.12.7(graphql@16.11.0): + dependencies: + graphql: 16.11.0 + tslib: 2.8.1 + + graphql@16.11.0: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -4314,10 +4643,10 @@ snapshots: http-vary@1.0.3: {} - https-proxy-agent@5.0.1(supports-color@7.2.0): + https-proxy-agent@5.0.1: dependencies: - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4333,14 +4662,27 @@ snapshots: immer@11.1.15: {} + immutable@5.1.9: {} + + import-from@4.0.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} inherits@2.0.4: {} + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + ipaddr.js@1.9.1: {} + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + is-buffer@2.0.5: {} is-bun-module@2.0.0: @@ -4361,6 +4703,16 @@ snapshots: is-promise@4.0.0: {} + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + + is-windows@1.0.2: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -4523,6 +4875,10 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@11.5.2: {} lz-string@1.5.0: {} @@ -4541,6 +4897,8 @@ snapshots: dependencies: semver: 7.8.5 + map-cache@0.2.2: {} + math-intrinsics@1.1.0: {} mdn-data@2.27.1: {} @@ -4663,6 +5021,12 @@ snapshots: dependencies: p-limit: 3.1.0 + parse-filepath@1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + parse-imports-exports@0.2.4: dependencies: parse-statements: 1.0.11 @@ -4679,6 +5043,12 @@ snapshots: path-key@3.1.1: {} + path-root-regex@0.1.2: {} + + path-root@0.1.1: + dependencies: + path-root-regex: 0.1.2 + path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -4863,9 +5233,9 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 - router@2.2.0(supports-color@7.2.0): + router@2.2.0: dependencies: - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -4885,9 +5255,9 @@ snapshots: semver@7.8.5: {} - send@1.2.1(supports-color@7.2.0): + send@1.2.1: dependencies: - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -4901,12 +5271,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.1(supports-color@7.2.0): + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1(supports-color@7.2.0) + send: 1.2.1 transitivePeerDependencies: - supports-color @@ -4975,6 +5345,8 @@ snapshots: split2@4.2.0: {} + sponge-case@2.0.3: {} + stable-hash-x@0.2.0: {} stackback@0.0.2: {} @@ -5012,6 +5384,8 @@ snapshots: dependencies: has-flag: 4.0.0 + swap-case@3.0.3: {} + symbol-tree@3.2.4: {} tailwindcss@4.3.3: {} @@ -5029,6 +5403,10 @@ snapshots: tinyrainbow@3.1.1: {} + title-case@3.0.3: + dependencies: + tslib: 2.8.1 + tldts-core@7.4.9: {} tldts@7.4.9: @@ -5058,8 +5436,7 @@ snapshots: ts-pattern@5.9.0: {} - tslib@2.8.1: - optional: true + tslib@2.8.1: {} turbo@2.10.5: optionalDependencies: @@ -5080,13 +5457,13 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.7.0(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5095,6 +5472,8 @@ snapshots: unbash@4.0.4: {} + unc-path-regex@0.1.2: {} + undici-types@7.18.2: {} undici@7.29.0: {}