mirror of
https://github.com/tiennm99/blog.git
synced 2026-10-11 03:13:10 +00:00
feat(ci): replace github-pages shortcode with daily workflow
Fetch repos server-side via GitHub API using PAGES_FETCH_TOKEN and inject the pages list into content/page/pages/index.md between HTML markers. Runs daily at 00:00 Asia/Saigon and on manual dispatch.
This commit is contained in:
1 parent
1763bc2acb
commit
f9927e6098
4 files changed
+147
-70
No files matched your search
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
// Fetch all repos for $GH_USER with has_pages=true and inject a markdown
|
||||
// table into content/page/pages/index.md between the PAGES_LIST markers.
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const token = process.env.GH_TOKEN;
|
||||
const user = process.env.GH_USER;
|
||||
|
||||
if (!token || !user) {
|
||||
console.error("GH_TOKEN and GH_USER env vars are required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": `${user}-pages-updater`,
|
||||
};
|
||||
|
||||
async function fetchAllRepos() {
|
||||
const all = [];
|
||||
for (let page = 1; page <= 20; page += 1) {
|
||||
const url = `https://api.github.com/users/${user}/repos?type=all&sort=updated&per_page=100&page=${page}`;
|
||||
const res = await fetch(url, { headers });
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
throw new Error(`GitHub API ${res.status} ${res.statusText}: ${body}`);
|
||||
}
|
||||
const batch = await res.json();
|
||||
if (!Array.isArray(batch) || batch.length === 0) break;
|
||||
all.push(...batch);
|
||||
if (batch.length < 100) break;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
function toRow(repo) {
|
||||
const pagesUrl =
|
||||
repo.homepage && repo.homepage.trim().length > 0
|
||||
? repo.homepage.trim()
|
||||
: `https://${user}.github.io/${repo.name}`;
|
||||
const description = (repo.description || "")
|
||||
.replace(/\|/g, "\\|")
|
||||
.replace(/\r?\n/g, " ")
|
||||
.trim();
|
||||
return `| [${repo.name}](${repo.html_url}) | ${description} | [${pagesUrl}](${pagesUrl}) |`;
|
||||
}
|
||||
|
||||
const START = "<!-- PAGES_LIST_START -->";
|
||||
const END = "<!-- PAGES_LIST_END -->";
|
||||
|
||||
function buildBlock(rows) {
|
||||
return [
|
||||
START,
|
||||
"",
|
||||
"| Name | Description | Pages URL |",
|
||||
"| --- | --- | --- |",
|
||||
...rows,
|
||||
"",
|
||||
END,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const repos = await fetchAllRepos();
|
||||
const pages = repos
|
||||
.filter((r) => r.has_pages)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const rows = pages.map(toRow);
|
||||
const block = buildBlock(rows);
|
||||
|
||||
const file = path.resolve("content/page/pages/index.md");
|
||||
const current = await fs.readFile(file, "utf8");
|
||||
|
||||
const marker = /<!-- PAGES_LIST_START -->[\s\S]*?<!-- PAGES_LIST_END -->/;
|
||||
const next = marker.test(current)
|
||||
? current.replace(marker, block)
|
||||
: `${current.trimEnd()}\n\n${block}\n`;
|
||||
|
||||
if (next === current) {
|
||||
console.log(`No changes (${pages.length} pages).`);
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.writeFile(file, next);
|
||||
console.log(`Updated ${file} with ${pages.length} pages.`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Update GitHub Pages list
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 00:00 Asia/Saigon (UTC+7) = 17:00 UTC
|
||||
- cron: "0 17 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: update-pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TZ: Asia/Ho_Chi_Minh
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Use PAT so the subsequent commit+push triggers the build workflow.
|
||||
token: ${{ secrets.PAGES_FETCH_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Refresh pages list
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PAGES_FETCH_TOKEN }}
|
||||
GH_USER: tiennm99
|
||||
run: node .github/scripts/update-pages-list.mjs
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
if [[ -z "$(git status --porcelain content/page/pages/index.md)" ]]; then
|
||||
echo "No changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add content/page/pages/index.md
|
||||
git commit -m "chore(pages): refresh GitHub Pages list"
|
||||
git push
|
||||
@@ -12,4 +12,5 @@ Danh sách những trang mình host trên [GitHub Pages](https://pages.github.co
|
||||
|
||||
_(Danh sách này được tạo tự động, thứ tự không quan trọng nhé)_
|
||||
|
||||
{{< github-pages >}}
|
||||
<!-- PAGES_LIST_START -->
|
||||
<!-- PAGES_LIST_END -->
|
||||
@@ -1,69 +0,0 @@
|
||||
<div id="github-pages">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Pages URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="repos">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const username = "tiennm99";
|
||||
const type = "all";
|
||||
const sort = "updated";
|
||||
const perPage = 100;
|
||||
let page = 1;
|
||||
|
||||
function fetchRepos(page) {
|
||||
const url = `https://api.github.com/users/${username}/repos?type=${type}&sort=${sort}&per_page=${perPage}&page=${page}`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("HTTP error " + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.length === 0) {
|
||||
// No more repositories to fetch
|
||||
return;
|
||||
}
|
||||
|
||||
const table = document.getElementById("repos");
|
||||
data.forEach(repo => {
|
||||
if (repo.has_pages) {
|
||||
const row = table.insertRow(-1);
|
||||
const nameCell = row.insertCell(0);
|
||||
const descriptionCell = row.insertCell(1);
|
||||
const urlCell = row.insertCell(2);
|
||||
|
||||
const repoUrl = repo.html_url;
|
||||
let pagesUrl;
|
||||
if (repo.homepage !== null) {
|
||||
pagesUrl = repo.homepage;
|
||||
} else {
|
||||
pagesUrl = `https://${username}.github.io/${repo.name}`
|
||||
console.warn(`${repo.name}'s homepage is null. Using ${pagesUrl} instead.`)
|
||||
}
|
||||
|
||||
nameCell.innerHTML = `<a href="${repoUrl}" target="_blank">${repo.name}</a>`;
|
||||
descriptionCell.textContent = repo.description;
|
||||
urlCell.innerHTML = `<a href="${pagesUrl}" target="_blank">${pagesUrl}</a>`;
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the next page
|
||||
fetchRepos(page + 1);
|
||||
})
|
||||
.catch(error => console.error("Error:", error));
|
||||
}
|
||||
|
||||
// Start fetching repositories
|
||||
fetchRepos(page);
|
||||
</script>
|
||||
Reference in new issue
Block a user