Skip to content

Commit 1619cc3

Browse files
authored
Merge pull request #346 from acekyd/ace/revamp
Update structure for tracking projects
2 parents c2c8d84 + 71b6a17 commit 1619cc3

13 files changed

Lines changed: 8519 additions & 112 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Enrich Projects
2+
3+
on:
4+
schedule:
5+
# Every Monday at 02:00 UTC
6+
- cron: "0 2 * * 1"
7+
workflow_dispatch: {}
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
enrich:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
27+
- name: Run enrichment
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
run: node scripts/enrich-projects.mjs
31+
32+
- name: Check for changes
33+
id: diff
34+
run: |
35+
git diff --quiet data/projects.enriched.json && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT
36+
37+
- name: Commit enriched data
38+
if: steps.diff.outputs.changed == 'true'
39+
run: |
40+
git config user.name "github-actions[bot]"
41+
git config user.email "github-actions[bot]@users.noreply.github.com"
42+
git add data/projects.enriched.json
43+
git commit -m "chore: update enriched project data [skip ci]"
44+
git push
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Generate README
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- "data/projects.json"
9+
workflow_dispatch: {}
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
generate:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: 20
24+
25+
- name: Install dependencies
26+
run: npm ci
27+
28+
- name: Regenerate README
29+
run: node scripts/generate-readme.mjs
30+
31+
- name: Check for changes
32+
id: diff
33+
run: |
34+
git diff --quiet README.MD && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT
35+
36+
- name: Commit README
37+
if: steps.diff.outputs.changed == 'true'
38+
run: |
39+
git config user.name "github-actions[bot]"
40+
git config user.email "github-actions[bot]@users.noreply.github.com"
41+
git add README.MD
42+
git commit -m "chore: regenerate README from projects.json [skip ci]"
43+
git push

.github/workflows/validate-pr.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Validate PR
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "data/projects.json"
7+
8+
jobs:
9+
validate:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
19+
- name: Install dependencies
20+
run: npm ci
21+
22+
- name: Validate projects.json
23+
run: node scripts/validate-projects.mjs

app/utils/projects.ts

Lines changed: 79 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,92 @@
1-
import { marked } from "marked";
2-
import * as cheerio from "cheerio";
31
import { cache } from "react";
2+
import projectsData from "../../data/projects.enriched.json";
3+
4+
export type ProjectStatus =
5+
| "active"
6+
| "stale"
7+
| "inactive"
8+
| "archived"
9+
| "deprecated"
10+
| "deleted"
11+
| "unknown";
12+
13+
export type ManualStatus = "inactive" | "archived" | "deprecated" | "deleted";
14+
15+
export type Author = {
16+
name: string;
17+
link: string;
18+
};
419

5-
const getData = cache(async () => {
6-
const res = await fetch(
7-
"https://raw.githubusercontent.com/acekyd/made-in-nigeria/main/README.MD",
8-
{ next: { revalidate: false } }
9-
);
10-
11-
if (!res.ok) {
12-
// This will activate the closest `error.js` Error Boundary
13-
throw new Error("Failed to fetch data");
14-
}
15-
16-
const markdownData = await res.text();
17-
18-
// const markdownData = await fs.readFile(process.cwd() + '/README.MD', 'utf8');
19-
20-
const html = marked(markdownData);
21-
const $ = cheerio.load(html); // load the html string into cheerio
22-
23-
// Select all <li> elements using jQuery-like syntax and extract their text
24-
const liTextArray = $("li")
25-
.map((index, element) => $(element).html())
26-
.get();
20+
export type Project = {
21+
name: string;
22+
repoUrl: string;
23+
description: string;
24+
authors: Author[];
25+
manualStatus?: ManualStatus;
26+
computed?: {
27+
status: ProjectStatus;
28+
stars: number | null;
29+
lastPushed: string | null;
30+
language: string | null;
31+
checkedAt: string;
32+
error?: string;
33+
};
34+
// Legacy shape — keeps existing components working without changes
35+
repoName: string;
36+
repoLink: string;
37+
repoDescription: string;
38+
repoAuthor: string;
39+
repoAuthorLink: string;
40+
isInactive: boolean;
41+
isArchived: boolean;
42+
};
2743

28-
// process the text to get the data you want
29-
const repositories = convertToJSON(liTextArray);
44+
function normalizeProject(raw: (typeof projectsData)[number]): Project {
45+
const primaryAuthor = raw.authors?.[0];
46+
const status = raw.computed?.status as ProjectStatus;
3047

31-
return repositories;
32-
});
48+
return {
49+
...raw,
50+
authors: (raw.authors ?? []) as Author[],
51+
computed: raw.computed as Project["computed"],
52+
manualStatus: raw.manualStatus as ManualStatus | undefined,
53+
repoName: raw.name,
54+
repoLink: raw.repoUrl,
55+
repoDescription: raw.description,
56+
repoAuthor: primaryAuthor?.name ?? "",
57+
repoAuthorLink: primaryAuthor?.link ?? "",
58+
isInactive: status === "inactive" || status === "stale",
59+
isArchived: status === "archived",
60+
};
61+
}
3362

34-
function convertToJSON(repositories: string[]) {
35-
return repositories.map((repository) => {
36-
const $ = cheerio.load(repository);
63+
const getData = cache((): Project[] =>
64+
(projectsData as (typeof projectsData)[number][]).map(normalizeProject)
65+
);
3766

38-
// Extract text content and href from <a> element
39-
const repoName = $("a").first().text();
40-
const repoLink = $("a").first().attr("href");
67+
export const useProjects = () => {
68+
const data = getData();
4169

42-
// Status of the repo
43-
const status = $("span").first().text();
70+
const filterProjects = () => ({
71+
byName: (input: string) => {
72+
const q = input.toLocaleLowerCase();
73+
return data.filter((p) => p.repoName.toLocaleLowerCase().includes(q));
74+
},
4475

45-
const isInactive = status?.includes("Inactive");
46-
const isArchived = status?.includes("Archived");
76+
byAuthor: (input: string) => {
77+
const normalized = input.trim().toLocaleLowerCase().replace(/^@/, "");
78+
const q = `@${normalized}`;
79+
return data.filter((p) => p.repoAuthor.toLocaleLowerCase().includes(q));
80+
},
4781

48-
// @ts-ignore
49-
let description = $("*").contents()[3].data; // I don't know why the fuck this works but if it's not broken, don't touch it.
50-
const repoDescription = description.replace(/^ - /, "");
51-
const repoAuthor = $("strong a").text();
52-
const repoAuthorLink = $("strong a").attr("href");
82+
byLetter: (input: string) => {
83+
const q = input.toLocaleLowerCase();
84+
return data.filter((p) => p.repoName.toLocaleLowerCase().startsWith(q));
85+
},
5386

54-
// Create JSON object
55-
return {
56-
repoName,
57-
repoLink,
58-
repoDescription,
59-
repoAuthor,
60-
repoAuthorLink,
61-
isInactive,
62-
isArchived,
63-
};
87+
byStatus: (status: ProjectStatus) =>
88+
data.filter((p) => p.computed?.status === status),
6489
});
65-
}
66-
67-
export const useProjects = async () => {
68-
const data = await getData();
69-
70-
const filterProjects = () => {
71-
return {
72-
byName: (input: string) => {
73-
return data.filter(
74-
(projects) =>
75-
projects.repoName
76-
.toLocaleLowerCase()
77-
.includes(input.toLocaleLowerCase()) ||
78-
projects.repoName.toLocaleLowerCase() === input.toLocaleLowerCase()
79-
);
80-
},
81-
82-
byAuthor: (input: string) => {
83-
return data.filter(
84-
(projects) =>
85-
projects.repoAuthor.toLocaleLowerCase() ===
86-
`@${input.toLocaleLowerCase()}` ||
87-
projects.repoAuthor
88-
.toLocaleLowerCase()
89-
.includes(`@${input.toLocaleLowerCase()}`)
90-
);
91-
},
9290

93-
byLetter: (input: string) => {
94-
return data.filter((projects) =>
95-
projects.repoName
96-
.toLocaleLowerCase()
97-
.startsWith(input.toLocaleLowerCase())
98-
);
99-
},
100-
};
101-
};
102-
103-
return {
104-
projects: data,
105-
filterProjects,
106-
};
91+
return { projects: data, filterProjects };
10792
};

contributing.md

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,77 @@
1-
# Made in Nigeria Github collection Guidelines
1+
# Made in Nigeria — Contribution Guide
22

3-
## Contributing a Project
3+
## What qualifies a project for inclusion?
4+
To be included in the Made in Nigeria list, a project must meet the following criteria:
45

5-
To add a new project to the collection, please ensure they meet the following requirements arranged in order of importance
6+
- **Made in Nigeria** — created or primarily maintained by a Nigerian developer.
7+
- **Open source** — publicly available source code.
8+
- **Global use** — not limited to a Nigerian audience or use case.
9+
- **At least 20 GitHub stars** — a signal that others find it useful.
10+
- **Not a list** — curated lists (alone) of links don't qualify.
11+
- Provide a social media or personal site link for the author(s) - outside GitHub is preferred.
612

7-
* The project must be **made in Nigeria** as the name implies :grin:.
813

9-
* The project must be open source.
14+
## Adding a project
15+
Open `data/projects.json` and add a new entry anywhere in the array (it will be sorted alphabetically on merge):
1016

11-
* 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.
17+
```json
18+
{
19+
"name": "Your Project Name",
20+
"repoUrl": "https://github.com/your-username/your-repo",
21+
"description": "One or two sentences describing what the project does.",
22+
"authors": [
23+
{ "name": "@yourhandle", "link": "https://twitter.com/yourhandle" }
24+
]
25+
}
26+
```
1227

13-
* 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: ].
28+
That's all. The automated pipeline will:
29+
- validate your entry on the PR
30+
- fetch real GitHub data (stars, last push, language) on the next weekly run
31+
- regenerate `README.MD` automatically after your PR is merged
1432

15-
* The project should not be a list of some sort.
33+
### Multiple authors
1634

17-
* Ensure to provide a social media url of the creator of the project outside GitHub.
35+
```json
36+
"authors": [
37+
{ "name": "@alice", "link": "https://twitter.com/alice" },
38+
{ "name": "@bob", "link": "https://github.com/bob" }
39+
]
40+
```
1841

19-
* Try to add the project to it's appropriate alphabetical location.
42+
### Optional: manual status
2043

21-
* You might want to include the **made in Nigeria** badge to your project. Place the following code in your **README** file:
22-
`[![Made in Nigeria](https://img.shields.io/badge/made%20in-nigeria-008751.svg?style=flat-square)](https://github.com/acekyd/made-in-nigeria)`
44+
If you know a project has been deprecated or deleted, you can include:
2345

24-
More requirements may be added as time goes on.
46+
```json
47+
"manualStatus": "deprecated"
48+
```
2549

50+
Valid values: `inactive`, `archived`, `deprecated`, `deleted`.
51+
Leave it out for active projects — the weekly enrichment job infers status automatically from GitHub activity.
2652

27-
## Contributing to Repo showcase website
53+
---
2854

29-
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:
55+
## How status is tracked automatically
3056

31-
* If you are fixing a bug or adding a new feature, please create a branch for it.
57+
A scheduled GitHub Action runs every Monday, queries the GitHub API for every project, and writes computed data to `data/projects.enriched.json`:
3258

33-
* Make Pull requests as descriptive as possible.
59+
| Condition | Computed status |
60+
|---|---|
61+
| GitHub repo is archived | `archived` |
62+
| No push in > 2 years | `inactive` |
63+
| No push in > 6 months | `stale` |
64+
| Active | `active` |
65+
| Non-GitHub or API error | `unknown` |
3466

35-
* Every other best practice recommended when contributing to a project.
67+
`manualStatus` always overrides the computed status for `archived`, `deprecated`, and `deleted`.
3668

69+
---
70+
71+
## Contributing to the website
72+
73+
- Create a branch for each bug fix or feature.
74+
- Make pull requests as descriptive as possible.
75+
- Run `npm run build` and check for errors before opening a PR.
76+
- Remove all `console.log` statements.
77+
- For UI changes, include screenshots or a short recording.

0 commit comments

Comments
 (0)