diff --git a/.changeset/dirty-goats-prove.md b/.changeset/dirty-goats-prove.md new file mode 100644 index 0000000..e2d9f65 --- /dev/null +++ b/.changeset/dirty-goats-prove.md @@ -0,0 +1,8 @@ +--- +"astro-loader-github-prs": minor +--- + +Add `monthsBack` option to specify the recent months for loading pull requests +Support returning the `` component via `render(entry)` to render the PR content +Errors no longer force the entire Astro project to terminate +No longer calls `store.clear()` internally diff --git a/packages/astro-loader-github-prs/README.md b/packages/astro-loader-github-prs/README.md index eb4ad20..9ac77aa 100644 --- a/packages/astro-loader-github-prs/README.md +++ b/packages/astro-loader-github-prs/README.md @@ -4,7 +4,7 @@ [![jsDocs.io][jsdocs-src]][jsdocs-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] -This package provides a GitHub PRs loader for Astro, fetching pull requests via a GitHub search query for use in Astro projects. +This package provides a GitHub PRs loader for Astro, fetching pull requests with a search query for use in Astro projects. ## Installation @@ -21,55 +21,62 @@ export default defineConfig({ experimental: { contentLayer: true, }, -}); +}) ``` -In `src/content/config.ts`, import and configure the GitHub PRs loader to define a new content collection: +In `src/content/config.ts` (for `^4.14.0`) or `src/content.config.ts` (for `^5.0.0`), import and configure the GitHub PRs loader to define a new content collection: ```ts -import { defineCollection } from "astro:content"; -import { githubPrsLoader } from "astro-loader-github-prs"; +import { defineCollection } from "astro:content" +import { githubPrsLoader } from "astro-loader-github-prs" const githubPrs = defineCollection({ loader: githubPrsLoader({ search: 'author:username created:>=2024-10-01', }), -}); +}) -export const collections = { githubPrs }; +export const collections = { githubPrs } ``` [Query the content collection](https://docs.astro.build/en/guides/content-collections/#querying-collections) like any other Astro content collection to render the loaded GitHub PRs: ```astro --- -import { getCollection } from "astro:content"; +import { getCollection } from "astro:content" -const prs = await getCollection("githubPrs"); +const prs = await getCollection("githubPrs") --- - +{ + prs.map(async (pr) => { + const { Content } = await render(pr) + return ( +
+ + {pr.data.repository.nameWithOwner} - #{pr.data.number} + +

+ + {/*

*/} +
+ ) + }) +} ``` -To update the data, trigger a site rebuild, as [the loader fetches data only at build time](https://docs.astro.build/en/reference/content-loader-reference/#object-loaders). +To update the data, trigger a site rebuild (e.g., using a third-party cron job service), as [the loader fetches data only at build time](https://docs.astro.build/en/reference/content-loader-reference/#object-loaders). ## Configuration -The loader fetches PRs via the GitHub GraphQL API with a search string, requiring a `repo`-scoped PAT, and returns up to 1,000 results. Options include: +The loader fetches PRs via the GitHub GraphQL API with a search string, requiring a repo-scoped PAT, and [returns up to 1,000 results](https://docs.github.com/en/graphql/reference/objects#searchresultitemconnection). Options include: -| Option (* required) | Type (defaults) | Description | -| ------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `search`* | `string` | The search string for querying pull requests on GitHub. This string will be concatenated with `type:pr` to form the complete search query. See [how to search pull requests](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests). For examples:
`'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. | -| `githubToken` | `string` (defaults: `'import.meta.env.GITHUB_TOKEN'`) | A GitHub PAT with at least `repo` scope permissions. Defaults to the `GITHUB_TOKEN` environment variable. **If configured here, keep confidential and avoid public exposure.** See [how to create one](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) and [configure env vars in an Astro project](https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables). | +| Option (* required) | Type (default) | Description | +| ------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `search`* | `string` | The search string for querying pull requests on GitHub. This string will be concatenated with `type:pr` to form the complete search query. See [how to search pull requests](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests). For examples:
`'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. | +| `monthsBack` | `number` | 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 `'created'` qualifier is defined in search option, it will override this value. | +| `githubToken` | `string` (Defaults to the `GITHUB_TOKEN` environment variable) | A GitHub PAT with at least `repo` scope permissions. Defaults to the `GITHUB_TOKEN` environment variable. **If configured here, keep confidential and avoid public exposure.** See [how to create one](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) and [configure env vars in an Astro project](https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables). | ## Schema @@ -109,15 +116,20 @@ const GithubPrSchema = z.object({ }) ``` -Astro automatically applies this schema to generate TypeScript interfaces, providing full support for autocompletion and type-checking when querying the collection. +Astro automatically applies these schemas to generate TypeScript interfaces, enabling autocompletion and type-checking for collection queries. If you [customize the collection schema](https://docs.astro.build/en/guides/content-collections/#defining-the-collection-schema), ensure compatibility with the loader's built-in Zod schema to prevent errors. For additional fields, consider opening an issue. + +## Changelog -If you need to [customize the collection schema](https://docs.astro.build/en/guides/content-collections/#defining-the-collection-schema), ensure it remains compatible with the built-in Zod schema of the loader to avoid errors. For additional fields you'd like to fetch, feel free to [open an issue](https://github.com/lin-stephanie/astro-loaders/issues). +See [CHANGELOG.md](CHANGELOG.md) for the change history of this loader. +## Contribution -[version-badge]: https://img.shields.io/npm/v/astro-loader-github-prs?label=release&style=flat&colorA=080f12&colorB=ef7575 +If you see any errors or room for improvement, feel free to open an [issues](https://github.com/lin-stephanie/astro-loaders/issues) or [pull request](https://github.com/lin-stephanie/astro-loaders/pulls) . Thank you in advance for contributing! ❤️ + + +[version-badge]: https://img.shields.io/npm/v/astro-loader-github-prs?label=release&style=flat&colorA=080f12&colorB=f87171 [version-link]: https://www.npmjs.com/package/astro-loader-github-prs -[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=ef7575 +[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=f87171 [jsdocs-href]: https://www.jsdocs.io/package/astro-loader-github-prs -[npm-downloads-src]: https://img.shields.io/npm/dm/astro-loader-github-prs?style=flat&colorA=080f12&colorB=ef7575 +[npm-downloads-src]: https://img.shields.io/npm/dm/astro-loader-github-prs?style=flat&colorA=080f12&colorB=f87171 [npm-downloads-href]: https://npmjs.com/package/astro-loader-github-prs - diff --git a/packages/astro-loader-github-prs/package.json b/packages/astro-loader-github-prs/package.json index 2f6d296..700c567 100644 --- a/packages/astro-loader-github-prs/package.json +++ b/packages/astro-loader-github-prs/package.json @@ -1,7 +1,7 @@ { "name": "astro-loader-github-prs", "version": "1.0.2", - "description": "Aatro loader for loading GitHub pull reuqests from a given search string.", + "description": "Astro loader for loading GitHub pull requests with a search query.", "author": "Stephanie Lin ", "license": "MIT", "keywords": [ diff --git a/packages/astro-loader-github-prs/src/config.ts b/packages/astro-loader-github-prs/src/config.ts index e46bf1f..c281331 100644 --- a/packages/astro-loader-github-prs/src/config.ts +++ b/packages/astro-loader-github-prs/src/config.ts @@ -15,6 +15,21 @@ export const GithubPrsLoaderConfigSchema = z.object({ */ 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. @@ -27,7 +42,7 @@ export const GithubPrsLoaderConfigSchema = z.object({ * - {@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().default(import.meta.env.GITHUB_TOKEN), + githubToken: z.string().optional(), }) export type GithubPrsLoaderUserConfig = z.input< diff --git a/packages/astro-loader-github-prs/src/index.ts b/packages/astro-loader-github-prs/src/index.ts index dba9712..1baedcd 100644 --- a/packages/astro-loader-github-prs/src/index.ts +++ b/packages/astro-loader-github-prs/src/index.ts @@ -1,11 +1,11 @@ -import { AstroError } from 'astro/errors' -import { Octokit } from 'octokit' - import { readFileSync } from 'node:fs' +import { Octokit } from 'octokit' + import pkg from '../package.json' with { type: 'json' } import { GithubPrsLoaderConfigSchema } from './config.js' import { GithubPrSchema } from './schema.js' +import { getQueryWithMonthsBack } from './utils.js' import type { Loader } from 'astro/loaders' import type { GithubPrsLoaderUserConfig } from './config.js' @@ -13,47 +13,52 @@ import type { GetPrsQuery, GetPrsQueryVariables } from './graphql/types.js' import type { GithubPr } from './schema.js' /** - * Aatro loader for loading GitHub pull reuqests from a given search string. + * Astro loader for loading GitHub pull requests with a search query. * * @see https://github.com/lin-stephanie/astro-loaders/tree/main/packages/astro-loader-github-prs */ function githubPrsLoader(userConfig: GithubPrsLoaderUserConfig): Loader { - const parsedConfig = GithubPrsLoaderConfigSchema.safeParse(userConfig) - if (!parsedConfig.success) { - throw new AstroError( - `The configuration provided in '${pkg.name}' is invalid. Refer to the following error report or access configuration details for this loader here: ${pkg.homepage}#configuration.`, - `${parsedConfig.error.issues.map((issue) => issue.message).join('\n')}` - ) - } - const parsedUserConfig = parsedConfig.data - return { name: pkg.name, schema: GithubPrSchema, - async load({ logger, store, parseData }) { - const { search, githubToken } = parsedUserConfig - const prs: GithubPr[] = [] - const query = `type:pr ${search}` - logger.info( - `Loading GitHub pull reuqests based on the search string: '${query}'` - ) + 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: ${pkg.homepage}README.md#configuration.` + ) + return + } + const { search, monthsBack, githubToken } = 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) { - throw new AstroError( - 'No GitHub token provided. Please provide a `githubToken` or set the `GITHUB_TOKEN` environment variable.', - `How 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. -How to store GitHub PAT in Astro project environment variables: https://docs.astro.build/en/guides/environment-variables/#setting-environment-variables` + logger.error( + '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 octokit = new Octokit({ - auth: githubToken || import.meta.env.GITHUB_TOKEN, - }) + const prs: GithubPr[] = [] + 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 @@ -61,7 +66,7 @@ How to store GitHub PAT in Astro project environment variables: https://docs.ast while (hasNextPage) { const variables: GetPrsQueryVariables = { - search: query, + search: parsedSearch, first: 100, cursor, } @@ -115,20 +120,23 @@ How to store GitHub PAT in Astro project environment variables: https://docs.ast hasNextPage = res.search.pageInfo.hasNextPage || false cursor = res.search.pageInfo.endCursor || null } - } catch (error) { - throw new AstroError( - `Failed to load GitHub pull requests: ${(error as Error).message}` - ) - } - 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), + }) + } - for (const item of prs) { - const parsedItem = await parseData({ id: item.id, data: item }) - store.set({ id: item.id, data: parsedItem }) + logger.info('Successfully loaded GitHub pull requests') + } catch (error) { + logger.error( + `Failed to load GitHub pull requests. ${(error as Error).message}` + ) } - - logger.info('Successfully loaded GitHub pull requests') }, } } diff --git a/packages/astro-loader-github-prs/src/utils.ts b/packages/astro-loader-github-prs/src/utils.ts new file mode 100644 index 0000000..84efe62 --- /dev/null +++ b/packages/astro-loader-github-prs/src/utils.ts @@ -0,0 +1,16 @@ +/** + * Get the search query with `monthsBack`. + */ +export function getQueryWithMonthsBack( + search: string, + monthsBack: number | undefined +) { + if (!search.includes('created') && monthsBack) { + const startDate = new Date() + startDate.setMonth(startDate.getMonth() - monthsBack + 1) + startDate.setDate(1) + + return `${search} created:>=${startDate.toISOString().split('T')[0]}` + } + return search +}