mirror of
https://github.com/chenasraf/github-repos-astro-loader.git
synced 2026-05-18 01:38:59 +00:00
feat: project overrides
This commit is contained in:
23
README.md
23
README.md
@@ -3,6 +3,8 @@
|
||||
This is a content loader for Astro that fetches GitHub repositories and their README files, so you
|
||||
can list them easily in your Astro site.
|
||||
|
||||
[See a demo on my website](https://casraf.dev/projects)
|
||||
|
||||
## Usage
|
||||
|
||||
In your `src/content/config.ts` file, add a new collection and use the loader:
|
||||
@@ -19,6 +21,7 @@ const project = defineCollection({
|
||||
orgs: ['myorg'], // A list of GitHub orgs to fetch repositories from
|
||||
debug: true, // Output debug logs during processing
|
||||
force: false, // Ignore cache and force a full re-fetch
|
||||
overridesDir: 'src/content/project-overrides', // Directory to look for overrides
|
||||
filter: (repo) => // Filter repositories to include in the collection
|
||||
[
|
||||
!repo.fork,
|
||||
@@ -46,6 +49,26 @@ const projects = await getCollection('project')
|
||||
</div>
|
||||
```
|
||||
|
||||
### Overrides
|
||||
|
||||
You might want to add, or modify data for a project. To keep this easy to maintain, and to allow
|
||||
markdown parsing, you can create a `my_project.md` file inside your `overridesDir`.
|
||||
|
||||
The default directory is `src/content/project-overrides`.
|
||||
|
||||
Just create an md file with all the related fields you want to override. Insert the readme Markdown
|
||||
content under the properties.
|
||||
|
||||
```md
|
||||
---
|
||||
# src/content/project-overrides/my_project.md
|
||||
title: My Project # instead of my_project
|
||||
featured: true
|
||||
---
|
||||
|
||||
This is the README content for my project. Hooray!
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
I am developing this package on my free time, so any support, whether code, issues, or just stars is
|
||||
|
||||
@@ -3,9 +3,12 @@ import path from 'node:path'
|
||||
import { logger } from './logger.js'
|
||||
import { GitHubRepositoryAPIResponse, InternalLoaderOptions } from './types.js'
|
||||
|
||||
export const overridesDir = path.join(process.cwd(), 'src', 'content', 'project-overrides')
|
||||
export const projectIgnoreFile = path.join(overridesDir, '.projectignore')
|
||||
export const projectKeepFile = path.join(overridesDir, '.projectkeep')
|
||||
export function getProjectIgnoreFile(options: Pick<InternalLoaderOptions, 'overridesDir'>) {
|
||||
return path.resolve(options.overridesDir, '.projectignore')
|
||||
}
|
||||
export function getProjectKeepFile(options: Pick<InternalLoaderOptions, 'overridesDir'>) {
|
||||
return path.resolve(options.overridesDir, '.projectkeep')
|
||||
}
|
||||
|
||||
export let projectIgnore: string[] = []
|
||||
export let projectKeep: string[] = []
|
||||
@@ -58,9 +61,9 @@ async function loadFileList(file: string): Promise<string[]> {
|
||||
.filter((x) => x[0] !== '#')
|
||||
}
|
||||
|
||||
export async function reloadOverrides() {
|
||||
projectIgnore = await loadFileList(projectIgnoreFile)
|
||||
projectKeep = await loadFileList(projectKeepFile)
|
||||
export async function reloadOverrides(options: InternalLoaderOptions) {
|
||||
projectIgnore = await loadFileList(getProjectIgnoreFile(options))
|
||||
projectKeep = await loadFileList(getProjectKeepFile(options))
|
||||
}
|
||||
|
||||
export function getAuthorization(options: InternalLoaderOptions): Headers {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { Loader, LoaderContext } from 'astro/loaders'
|
||||
import { projectIgnoreFile, projectKeepFile, reloadOverrides } from './github.js'
|
||||
import { getProjectIgnoreFile, getProjectKeepFile, reloadOverrides } from './github.js'
|
||||
import { parseJSON as parseDate } from 'date-fns/parseJSON'
|
||||
import { formatISO as formatDate } from 'date-fns/formatISO'
|
||||
import { LoaderOptions, InternalLoaderOptions } from './types.js'
|
||||
import { logger } from './logger.js'
|
||||
import { getProjectsList } from './parser.js'
|
||||
import path from 'path'
|
||||
|
||||
const defaultOverridesDir = path.join(process.cwd(), 'src', 'content', 'project-overrides')
|
||||
|
||||
async function reloadProjects(
|
||||
{ store, meta }: Pick<LoaderContext, 'store' | 'meta'>,
|
||||
@@ -15,13 +18,14 @@ async function reloadProjects(
|
||||
(opts.force ? undefined : meta.get('lastUpdated')) ?? '1970-01-01T00:00:00Z',
|
||||
)
|
||||
|
||||
await reloadOverrides()
|
||||
const options = InternalLoaderOptions.parse({
|
||||
debug: false,
|
||||
orgs: [],
|
||||
overridesDir: defaultOverridesDir,
|
||||
...opts,
|
||||
lastUpdated,
|
||||
})
|
||||
await reloadOverrides(options)
|
||||
const projects = await getProjectsList(options)
|
||||
|
||||
logger.log(projects.length, 'projects loaded')
|
||||
@@ -50,7 +54,7 @@ export function githubProjectsLoader(opts: LoaderOptions): Loader {
|
||||
|
||||
watcher?.on('change', async (filename) => {
|
||||
logger.log('Change detected:', filename)
|
||||
if ([projectIgnoreFile, projectKeepFile].includes(filename)) {
|
||||
if (path.dirname(filename) === (opts.overridesDir ?? defaultOverridesDir)) {
|
||||
await reloadProjects({ store, meta }, opts)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import { formatISO as formatDate } from 'date-fns/formatISO'
|
||||
import { GitHubProjectSchema, GitHubRepositoryAPIResponse, InternalLoaderOptions } from './types.js'
|
||||
import { fileExists, parseMarkdown } from './utils.js'
|
||||
import { logger } from './logger.js'
|
||||
import { fetchRepos, getAuthorization, overridesDir, projectIgnore, projectKeep } from './github.js'
|
||||
import { fetchRepos, getAuthorization, projectIgnore, projectKeep } from './github.js'
|
||||
|
||||
export async function getProjectsList(
|
||||
options: InternalLoaderOptions,
|
||||
@@ -44,7 +44,7 @@ export async function getProjectsList(
|
||||
raw: repo,
|
||||
})
|
||||
|
||||
const overridesFile = path.join(overridesDir, `${project.name}.md`)
|
||||
const overridesFile = path.join(options.overridesDir, `${project.name}.md`)
|
||||
|
||||
if (await fileExists(overridesFile)) {
|
||||
const content = await fs.readFile(overridesFile, 'utf8')
|
||||
|
||||
22
src/types.ts
22
src/types.ts
@@ -9,6 +9,10 @@ export const LinkSchema = z.object({
|
||||
href: z.string(),
|
||||
icon: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for a link.
|
||||
*/
|
||||
export type LinkSchema = {
|
||||
/** The title of the link. */
|
||||
title: string
|
||||
@@ -35,6 +39,9 @@ export const GitHubProjectSchema = z.object({
|
||||
raw: z.any() as z.ZodType<GitHubRepositoryAPIResponse>,
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for a GitHub project.
|
||||
*/
|
||||
export type GitHubProjectSchema = {
|
||||
/** The name of the GitHub project. */
|
||||
name: string
|
||||
@@ -69,23 +76,22 @@ export type GitHubRepositoryAPIResponse = NonNullable<
|
||||
* Schema for loader options.
|
||||
*/
|
||||
export const LoaderOptions = z.object({
|
||||
/** The username from GitHub to fetch the repositories from. */
|
||||
username: z.string(),
|
||||
/** Debug flag - if true, the loader will output debug information. */
|
||||
debug: z.boolean().optional(),
|
||||
/** GitHub organizations to fetch projects from. */
|
||||
orgs: z.array(z.string()).optional(),
|
||||
/** A function to filter out projects. Filtered projects will not be parsed or saved in the collection. */
|
||||
filter: z
|
||||
.function()
|
||||
.args(z.any() as z.ZodType<GitHubRepositoryAPIResponse>)
|
||||
.returns(z.boolean())
|
||||
.optional(),
|
||||
/** The GitHub API token to use for fetching the repositories. */
|
||||
apiToken: z.string(),
|
||||
/** Whether to force a reload of the projects, regardless of cache status. */
|
||||
force: z.boolean().optional(),
|
||||
overridesDir: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for loader options.
|
||||
*/
|
||||
export type LoaderOptions = {
|
||||
/** The username from GitHub to fetch the repositories from. */
|
||||
username: string
|
||||
@@ -100,6 +106,8 @@ export type LoaderOptions = {
|
||||
apiToken: string
|
||||
/** Whether to force a reload of the projects, regardless of cache status. */
|
||||
force?: boolean
|
||||
/** The directory where project overrides are stored (default: `src/content/project-overrides`). */
|
||||
overridesDir?: string
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -108,7 +116,7 @@ export const InternalLoaderOptions = LoaderOptions.required().extend({
|
||||
})
|
||||
|
||||
/** @internal */
|
||||
export type InternalLoaderOptions = LoaderOptions & {
|
||||
export type InternalLoaderOptions = Required<LoaderOptions> & {
|
||||
/** The last time the projects were updated. */
|
||||
lastUpdated: Date
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user