-
Notifications
You must be signed in to change notification settings - Fork 426
Update structure for tracking projects #346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
885e30c
Update structure for tracking projects
acekyd 1aafa7d
Potential fix for pull request finding
acekyd c902163
Potential fix for pull request finding
acekyd 2fc9ee3
Potential fix for pull request finding
acekyd 0ee2362
Potential fix for pull request finding
acekyd 71b6a17
Potential fix for pull request finding
acekyd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: Enrich Projects | ||
|
|
||
| on: | ||
| schedule: | ||
| # Every Monday at 02:00 UTC | ||
| - cron: "0 2 * * 1" | ||
| workflow_dispatch: {} | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| enrich: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Run enrichment | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: node scripts/enrich-projects.mjs | ||
|
|
||
| - name: Check for changes | ||
| id: diff | ||
| run: | | ||
| git diff --quiet data/projects.enriched.json && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Commit enriched data | ||
| if: steps.diff.outputs.changed == 'true' | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add data/projects.enriched.json | ||
| git commit -m "chore: update enriched project data [skip ci]" | ||
| git push | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| name: Generate README | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - "data/projects.json" | ||
| workflow_dispatch: {} | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| generate: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Regenerate README | ||
| run: node scripts/generate-readme.mjs | ||
|
|
||
| - name: Check for changes | ||
| id: diff | ||
| run: | | ||
| git diff --quiet README.MD && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Commit README | ||
| if: steps.diff.outputs.changed == 'true' | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add README.MD | ||
| git commit -m "chore: regenerate README from projects.json [skip ci]" | ||
| git push |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| name: Validate PR | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "data/projects.json" | ||
|
|
||
| jobs: | ||
| validate: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Validate projects.json | ||
| run: node scripts/validate-projects.mjs |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,107 +1,92 @@ | ||
| import { marked } from "marked"; | ||
| import * as cheerio from "cheerio"; | ||
| import { cache } from "react"; | ||
| import projectsData from "../../data/projects.enriched.json"; | ||
|
|
||
| export type ProjectStatus = | ||
| | "active" | ||
| | "stale" | ||
| | "inactive" | ||
| | "archived" | ||
| | "deprecated" | ||
| | "deleted" | ||
| | "unknown"; | ||
|
|
||
| export type ManualStatus = "inactive" | "archived" | "deprecated" | "deleted"; | ||
|
|
||
| export type Author = { | ||
| name: string; | ||
| link: string; | ||
| }; | ||
|
|
||
| const getData = cache(async () => { | ||
| const res = await fetch( | ||
| "https://raw.githubusercontent.com/acekyd/made-in-nigeria/main/README.MD", | ||
| { next: { revalidate: false } } | ||
| ); | ||
|
|
||
| if (!res.ok) { | ||
| // This will activate the closest `error.js` Error Boundary | ||
| throw new Error("Failed to fetch data"); | ||
| } | ||
|
|
||
| const markdownData = await res.text(); | ||
|
|
||
| // const markdownData = await fs.readFile(process.cwd() + '/README.MD', 'utf8'); | ||
|
|
||
| const html = marked(markdownData); | ||
| const $ = cheerio.load(html); // load the html string into cheerio | ||
|
|
||
| // Select all <li> elements using jQuery-like syntax and extract their text | ||
| const liTextArray = $("li") | ||
| .map((index, element) => $(element).html()) | ||
| .get(); | ||
| export type Project = { | ||
| name: string; | ||
| repoUrl: string; | ||
| description: string; | ||
| authors: Author[]; | ||
| manualStatus?: ManualStatus; | ||
| computed?: { | ||
| status: ProjectStatus; | ||
| stars: number | null; | ||
| lastPushed: string | null; | ||
| language: string | null; | ||
| checkedAt: string; | ||
| error?: string; | ||
| }; | ||
| // Legacy shape — keeps existing components working without changes | ||
| repoName: string; | ||
| repoLink: string; | ||
| repoDescription: string; | ||
| repoAuthor: string; | ||
| repoAuthorLink: string; | ||
| isInactive: boolean; | ||
| isArchived: boolean; | ||
| }; | ||
|
|
||
| // process the text to get the data you want | ||
| const repositories = convertToJSON(liTextArray); | ||
| function normalizeProject(raw: (typeof projectsData)[number]): Project { | ||
| const primaryAuthor = raw.authors?.[0]; | ||
| const status = raw.computed?.status as ProjectStatus; | ||
|
|
||
| return repositories; | ||
| }); | ||
| return { | ||
| ...raw, | ||
| authors: (raw.authors ?? []) as Author[], | ||
| computed: raw.computed as Project["computed"], | ||
| manualStatus: raw.manualStatus as ManualStatus | undefined, | ||
| repoName: raw.name, | ||
| repoLink: raw.repoUrl, | ||
| repoDescription: raw.description, | ||
| repoAuthor: primaryAuthor?.name ?? "", | ||
| repoAuthorLink: primaryAuthor?.link ?? "", | ||
| isInactive: status === "inactive" || status === "stale", | ||
| isArchived: status === "archived", | ||
| }; | ||
| } | ||
|
|
||
| function convertToJSON(repositories: string[]) { | ||
| return repositories.map((repository) => { | ||
| const $ = cheerio.load(repository); | ||
| const getData = cache((): Project[] => | ||
| (projectsData as (typeof projectsData)[number][]).map(normalizeProject) | ||
| ); | ||
|
|
||
| // Extract text content and href from <a> element | ||
| const repoName = $("a").first().text(); | ||
| const repoLink = $("a").first().attr("href"); | ||
| export const useProjects = () => { | ||
| const data = getData(); | ||
|
|
||
| // Status of the repo | ||
| const status = $("span").first().text(); | ||
| const filterProjects = () => ({ | ||
| byName: (input: string) => { | ||
| const q = input.toLocaleLowerCase(); | ||
| return data.filter((p) => p.repoName.toLocaleLowerCase().includes(q)); | ||
| }, | ||
|
|
||
| const isInactive = status?.includes("Inactive"); | ||
| const isArchived = status?.includes("Archived"); | ||
| byAuthor: (input: string) => { | ||
| const normalized = input.trim().toLocaleLowerCase().replace(/^@/, ""); | ||
| const q = `@${normalized}`; | ||
| return data.filter((p) => p.repoAuthor.toLocaleLowerCase().includes(q)); | ||
| }, | ||
|
|
||
| // @ts-ignore | ||
| let description = $("*").contents()[3].data; // I don't know why the fuck this works but if it's not broken, don't touch it. | ||
| const repoDescription = description.replace(/^ - /, ""); | ||
| const repoAuthor = $("strong a").text(); | ||
| const repoAuthorLink = $("strong a").attr("href"); | ||
| byLetter: (input: string) => { | ||
| const q = input.toLocaleLowerCase(); | ||
| return data.filter((p) => p.repoName.toLocaleLowerCase().startsWith(q)); | ||
| }, | ||
|
|
||
| // Create JSON object | ||
| return { | ||
| repoName, | ||
| repoLink, | ||
| repoDescription, | ||
| repoAuthor, | ||
| repoAuthorLink, | ||
| isInactive, | ||
| isArchived, | ||
| }; | ||
| byStatus: (status: ProjectStatus) => | ||
| data.filter((p) => p.computed?.status === status), | ||
| }); | ||
| } | ||
|
|
||
| export const useProjects = async () => { | ||
| const data = await getData(); | ||
|
|
||
| const filterProjects = () => { | ||
| return { | ||
| byName: (input: string) => { | ||
| return data.filter( | ||
| (projects) => | ||
| projects.repoName | ||
| .toLocaleLowerCase() | ||
| .includes(input.toLocaleLowerCase()) || | ||
| projects.repoName.toLocaleLowerCase() === input.toLocaleLowerCase() | ||
| ); | ||
| }, | ||
|
|
||
| byAuthor: (input: string) => { | ||
| return data.filter( | ||
| (projects) => | ||
| projects.repoAuthor.toLocaleLowerCase() === | ||
| `@${input.toLocaleLowerCase()}` || | ||
| projects.repoAuthor | ||
| .toLocaleLowerCase() | ||
| .includes(`@${input.toLocaleLowerCase()}`) | ||
| ); | ||
| }, | ||
|
|
||
| byLetter: (input: string) => { | ||
| return data.filter((projects) => | ||
| projects.repoName | ||
| .toLocaleLowerCase() | ||
| .startsWith(input.toLocaleLowerCase()) | ||
| ); | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| return { | ||
| projects: data, | ||
| filterProjects, | ||
| }; | ||
| return { projects: data, filterProjects }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,77 @@ | ||
| # Made in Nigeria Github collection Guidelines | ||
| # Made in Nigeria — Contribution Guide | ||
|
|
||
| ## Contributing a Project | ||
| ## What qualifies a project for inclusion? | ||
| To be included in the Made in Nigeria list, a project must meet the following criteria: | ||
|
|
||
| To add a new project to the collection, please ensure they meet the following requirements arranged in order of importance | ||
| - **Made in Nigeria** — created or primarily maintained by a Nigerian developer. | ||
| - **Open source** — publicly available source code. | ||
| - **Global use** — not limited to a Nigerian audience or use case. | ||
| - **At least 20 GitHub stars** — a signal that others find it useful. | ||
| - **Not a list** — curated lists (alone) of links don't qualify. | ||
| - Provide a social media or personal site link for the author(s) - outside GitHub is preferred. | ||
|
|
||
| * The project must be **made in Nigeria** as the name implies :grin:. | ||
|
|
||
| * The project must be open source. | ||
| ## Adding a project | ||
| Open `data/projects.json` and add a new entry anywhere in the array (it will be sorted alphabetically on merge): | ||
|
|
||
| * The project must have global use - meaning it's not made just for use by Nigerians and there really is no geographical limitation for anyone that may be interested in using this project. | ||
| ```json | ||
| { | ||
| "name": "Your Project Name", | ||
| "repoUrl": "https://github.com/your-username/your-repo", | ||
| "description": "One or two sentences describing what the project does.", | ||
| "authors": [ | ||
| { "name": "@yourhandle", "link": "https://twitter.com/yourhandle" } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| * The project should have at least 20 stars. This is to serve as a way to determine that people actually have a use for the project and it does what it says it does. [ Stars likely to go up as time goes on :smiley: ]. | ||
| That's all. The automated pipeline will: | ||
| - validate your entry on the PR | ||
| - fetch real GitHub data (stars, last push, language) on the next weekly run | ||
| - regenerate `README.MD` automatically after your PR is merged | ||
|
|
||
| * The project should not be a list of some sort. | ||
| ### Multiple authors | ||
|
|
||
| * Ensure to provide a social media url of the creator of the project outside GitHub. | ||
| ```json | ||
| "authors": [ | ||
| { "name": "@alice", "link": "https://twitter.com/alice" }, | ||
| { "name": "@bob", "link": "https://github.com/bob" } | ||
| ] | ||
| ``` | ||
|
|
||
| * Try to add the project to it's appropriate alphabetical location. | ||
| ### Optional: manual status | ||
|
|
||
| * You might want to include the **made in Nigeria** badge to your project. Place the following code in your **README** file: | ||
| `[](https://github.com/acekyd/made-in-nigeria)` | ||
| If you know a project has been deprecated or deleted, you can include: | ||
|
|
||
| More requirements may be added as time goes on. | ||
| ```json | ||
| "manualStatus": "deprecated" | ||
| ``` | ||
|
|
||
| Valid values: `inactive`, `archived`, `deprecated`, `deleted`. | ||
| Leave it out for active projects — the weekly enrichment job infers status automatically from GitHub activity. | ||
|
|
||
| ## Contributing to Repo showcase website | ||
| --- | ||
|
|
||
| There is a webpage that has been developed by awesome members of the community used to view the contents of this repo in a very pleasing manner. If you'd like to contribute to this website, please try to follow these steps: | ||
| ## How status is tracked automatically | ||
|
|
||
| * If you are fixing a bug or adding a new feature, please create a branch for it. | ||
| A scheduled GitHub Action runs every Monday, queries the GitHub API for every project, and writes computed data to `data/projects.enriched.json`: | ||
|
|
||
| * Make Pull requests as descriptive as possible. | ||
| | Condition | Computed status | | ||
| |---|---| | ||
| | GitHub repo is archived | `archived` | | ||
| | No push in > 2 years | `inactive` | | ||
| | No push in > 6 months | `stale` | | ||
| | Active | `active` | | ||
| | Non-GitHub or API error | `unknown` | | ||
|
|
||
| * Every other best practice recommended when contributing to a project. | ||
| `manualStatus` always overrides the computed status for `archived`, `deprecated`, and `deleted`. | ||
|
|
||
| --- | ||
|
|
||
| ## Contributing to the website | ||
|
|
||
| - Create a branch for each bug fix or feature. | ||
| - Make pull requests as descriptive as possible. | ||
| - Run `npm run build` and check for errors before opening a PR. | ||
| - Remove all `console.log` statements. | ||
| - For UI changes, include screenshots or a short recording. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
github actions have a terrible reputation when it comes to running them as background jobs. this may end up running at a later time, which can be acceptable to some extent, but sometimes it may not even run at all, due to high demand at gh's end or so.
i'd suggest we expose this via an endpoint that we can hit with upstash. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. Valid concerns. I'd like to give it a go for a bit and see how that goes first? But keeping the upstash option in the pocket if it's needed @kaf-lamed-beyt. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure thing 🫡