UNPKG

astro-loader-github-prs

Version:
201 lines (196 loc) 8.1 kB
import { readFileSync } from 'node:fs'; import { Octokit } from 'octokit'; import { z } from 'astro/zod'; // src/index.ts var GithubPrsLoaderConfigSchema = z.object({ /** * The user-defined search string for querying pull requests on GitHub. * This string will be concatenated with "type:pr" to form the complete search query. * * For more information: * - {@link https://docs.github.com/en/graphql/reference/queries#search GitHub GraphQL API - Perform a search across resources} * - {@link https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests How to search pull requests} * * @example * - 'author:xxx created:>=2024-01-01': matches prs written by xxx that were created after 2024. * - 'author:xxx -user:xxx': matches prs written by xxx, but not to their own repositories. */ search: z.string(), /** * The number of recent months to load pull requests, including the current month. * The loader automatically converts this to a date for the 'created' qualifier in the search query. * * If the {@link https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests#search-by-when-an-issue-or-pull-request-was-created-or-last-updated 'created'} * qualifier is defined in `search` option, it will override this value. * * For example, setting to `3` on December 4, 2024, would yield: 'type:pr created:>=2024-10-01 ...'. */ monthsBack: z.number().int({ message: "`monthsBack` must be an integer" }).positive({ message: "`monthsBack` must be a positive integer" }).optional(), /** * You need to {@link https://github.com/settings/tokens create a GitHub PAT} * with at least `repo` scope permissions to authenticate requests to the GraphQL API. * * This is optional; by default, it reads from the `GITHUB_TOKEN` environment variable. * You may also configure it directly here (not recommended; if you do, ensure it is not exposed * in public code repositories). * * @see * - {@link https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic How to create a GitHub PAT (classic)} * - {@link https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables How to store GitHub PAT in Astro project environment variables} */ githubToken: z.string().optional(), /** * Whether to clear the {@link https://docs.astro.build/en/reference/content-loader-reference/#store store} * scoped to the collection before storing newly loaded data. * * @default false */ clearStore: z.boolean().default(false) }); var GithubPrSchema = z.object({ id: z.string(), url: z.string(), title: z.string(), titleHTML: z.string(), number: z.number(), state: z.enum(["CLOSED", "MERGED", "OPEN"]), isDraft: z.boolean(), body: z.string(), bodyHTML: z.string(), bodyText: z.string(), author: z.object({ login: z.string(), url: z.string(), avatarUrl: z.string() }), repository: z.object({ name: z.string(), nameWithOwner: z.string(), url: z.string(), stargazerCount: z.number(), isInOrganization: z.boolean(), owner: z.object({ login: z.string(), url: z.string(), avatarUrl: z.string() }) }), createdAt: z.string(), mergedAt: z.string() }); // src/utils.ts function getQueryWithMonthsBack(search, monthsBack) { if (!search.includes("created") && monthsBack) { const startDate = /* @__PURE__ */ new Date(); startDate.setUTCMonth(startDate.getMonth() - monthsBack + 1); startDate.setUTCDate(1); return `${search} created:>=${startDate.toISOString().split("T")[0]}`; } return search; } // src/index.ts function githubPrsLoader(userConfig) { return { name: "astro-loader-github-prs", schema: GithubPrSchema, async load({ logger, store, parseData, generateDigest }) { const parsedConfig = GithubPrsLoaderConfigSchema.safeParse(userConfig); if (!parsedConfig.success) { logger.error( `The configuration provided is invalid. ${parsedConfig.error.issues.map((issue) => issue.message).join("\n")}. Check out the configuration: https://github.com/lin-stephanie/astro-loaders/blob/main/packages/astro-loader-github-prs/README.md#configuration.` ); return; } const { search, monthsBack, githubToken, clearStore } = parsedConfig.data; const token = githubToken || import.meta.env.GITHUB_TOKEN; if (search.length === 0) { logger.warn( "Search string is empty and no pull requests will be loaded" ); return; } if (!token) { logger.warn( "No GitHub token provided. Please provide a `githubToken` or set the `GITHUB_TOKEN` environment variable.\nHow to create a GitHub PAT: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic.\nHow to store token in Astro project environment variables: https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables." ); return; } const prs = []; const parsedSearch = getQueryWithMonthsBack(search, monthsBack); const getPrsQuery = readFileSync( new URL("./graphql/query.graphql", import.meta.url), "utf8" ); const octokit = new Octokit({ auth: token }); logger.info( `Loading GitHub pull requests with a search query: '${parsedSearch}'` ); try { let hasNextPage = true; let cursor = null; while (hasNextPage) { const variables = { search: parsedSearch, first: 100, cursor }; const res = await octokit.graphql(getPrsQuery, variables); const prsPerPage = res.search.nodes?.filter( (node) => node !== null && node !== void 0 && typeof node === "object" && "id" in node ).map((node) => { return { id: node.id, url: node.url || "", title: node.title, titleHTML: node.titleHTML || "", number: node.number, state: node.state, isDraft: node.isDraft, body: node.body, bodyHTML: node.bodyHTML || "", bodyText: node.bodyText, author: { login: node.author?.login || "", url: node.author?.url || "", avatarUrl: node.author?.avatarUrl || "" }, repository: { name: node.repository.name, nameWithOwner: node.repository.nameWithOwner, url: node.repository.url || "", stargazerCount: node.repository.stargazerCount, isInOrganization: node.repository.isInOrganization, owner: { login: node.repository.owner.login || "", url: node.repository.owner.url || "", avatarUrl: node.repository.owner.avatarUrl || "" } }, createdAt: node.createdAt || "", mergedAt: node.mergedAt || "" }; }) || []; prs.push(...prsPerPage); hasNextPage = res.search.pageInfo.hasNextPage || false; cursor = res.search.pageInfo.endCursor || null; } if (clearStore) store.clear(); for (const item of prs) { const parsedItem = await parseData({ id: item.id, data: item }); store.set({ id: item.id, data: parsedItem, rendered: { html: item.bodyHTML }, digest: generateDigest(parsedItem) }); } logger.info("Successfully loaded GitHub pull requests"); } catch (error) { logger.error( `Failed to load GitHub pull requests. ${error.message}` ); } } }; } export { githubPrsLoader };