Code chi tiết
Để tạo một workflow GitHub giúp đồng bộ ghi chú từ vault repository sang quartz repository, bạn có thể sử dụng GitHub Actions. Dưới đây là hướng dẫn chi tiết về cách thiết lập workflow này.
- Bước 1: Tạo Personal Access Token (PAT)
Trước tiên, bạn cần tạo một Personal Access Token để workflow có quyền truy cập vào repository đích:
- Truy cập vào Settings của tài khoản GitHub của bạn
- Chọn Developer settings > Personal access tokens
- Tạo một token mới với quyền truy cập
repo
đầy đủ - Sao chép token này để sử dụng trong bước tiếp theo
- Bước 2: Thiết lập Secret trong repository nguồn
- Truy cập vào repository vault (repository nguồn)
- Vào phần Settings > Secrets and variables > Actions
- Tạo một secret mới với tên
GH_PAT
và giá trị là token bạn đã tạo ở bước 1
- Bước 3: Tạo workflow file
Tạo một file mới trong repository vault của bạn tại đường dẫn
.github/workflows/sync-published-notes.yml
với nội dung sau:
name: Sync Published Notes to Quartz
on:
push:
branches:
- main
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Source Repository
uses: actions/checkout@v4
with:
repository: hoangphuctran93/public-notes
path: ./source-repo
- name: Checkout Target Repository
uses: actions/checkout@v4
with:
repository: hoangphuctran93/kb.agentc.asia
token: ${{ secrets.GH_PAT }}
path: ./target-repo
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Dependencies
run: |
npm install gray-matter fs-extra
- name: Sync Published Notes
run: |
# Tạo script để đồng bộ các file có publish: true và xóa các file không còn publish
cat > sync_notes.js << 'EOF'
const fs = require('fs');
const fsExtra = require('fs-extra');
const path = require('path');
const matter = require('gray-matter');
const sourceDir = process.argv[2];
const destDir = process.argv[3];
// Đảm bảo thư mục đích tồn tại
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
// Lưu trữ danh sách các file đã được đồng bộ
const syncedFiles = new Set();
// Hàm để kiểm tra và sao chép file
function processFile(filePath, relativePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const { data } = matter(content);
// Chỉ sao chép file có publish: true
if (data.publish === true) {
const destPath = path.join(destDir, relativePath);
// Tạo thư mục đích nếu cần
const destDirPath = path.dirname(destPath);
if (!fs.existsSync(destDirPath)) {
fs.mkdirSync(destDirPath, { recursive: true });
}
// Sao chép file
fs.copyFileSync(filePath, destPath);
console.log(`Copied: ${relativePath}`);
// Thêm vào danh sách đã đồng bộ
syncedFiles.add(relativePath);
return true;
}
return false;
} catch (error) {
console.error(`Error processing ${filePath}: ${error.message}`);
return false;
}
}
// Hàm đệ quy để duyệt qua tất cả các thư mục
function processDirectory(dirPath, baseDir) {
const files = fs.readdirSync(dirPath);
let copiedCount = 0;
for (const file of files) {
const filePath = path.join(dirPath, file);
const relativePath = path.relative(baseDir, filePath);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
copiedCount += processDirectory(filePath, baseDir);
} else if (file.endsWith('.md')) {
if (processFile(filePath, relativePath)) {
copiedCount++;
}
}
}
return copiedCount;
}
// Hàm để xóa các file trong thư mục đích không còn trong danh sách đồng bộ
function cleanupDestination(destDir) {
function scanDirectory(dirPath, baseDir) {
if (!fs.existsSync(dirPath)) return;
const files = fs.readdirSync(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const relativePath = path.relative(baseDir, filePath);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
scanDirectory(filePath, baseDir);
// Kiểm tra và xóa thư mục rỗng
const dirFiles = fs.readdirSync(filePath);
if (dirFiles.length === 0) {
fs.rmdirSync(filePath);
console.log(`Removed empty directory: ${relativePath}`);
}
} else if (file.endsWith('.md') && !syncedFiles.has(relativePath)) {
// Xóa file không còn trong danh sách đồng bộ
fs.unlinkSync(filePath);
console.log(`Removed: ${relativePath}`);
}
}
}
scanDirectory(destDir, destDir);
}
// Bắt đầu xử lý từ thư mục nguồn
const totalCopied = processDirectory(sourceDir, sourceDir);
console.log(`Total published notes copied: ${totalCopied}`);
// Xóa các file không còn được publish
cleanupDestination(destDir);
EOF
# Đường dẫn thư mục ghi chú trong source repo
SOURCE_DIR="./source-repo"
# Đường dẫn thư mục đích trong target repo
DEST_DIR="./target-repo/content"
# Chạy script để đồng bộ và xóa các file
node sync_notes.js "$SOURCE_DIR" "$DEST_DIR"
- name: Commit and Push to Target Repository
run: |
cd ./target-repo
git config user.name "GitHub Action"
git config user.email "[email protected]"
# Lấy thời gian hiện tại theo múi giờ +7
TIMESTAMP=$(TZ=Asia/Bangkok date +'%Y-%m-%d %H:%M:%S')
git add .
# Kiểm tra xem có thay đổi không
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "🔄 Sync published notes from public-notes: $TIMESTAMP (GMT+7)"
git push
fi
Giải thích chi tiết Workflow
1. Cấu hình cơ bản
name: Sync Published Notes to Quartz
on:
push:
branches:
- main
workflow_dispatch:
- name: Tên hiển thị của workflow trong giao diện GitHub Actions
- on: Xác định khi nào workflow được kích hoạt:
push: branches: - main
: Chạy khi có commit được đẩy lên nhánh mainworkflow_dispatch
: Cho phép kích hoạt thủ công từ giao diện GitHub
2. Định nghĩa Job
jobs:
sync:
runs-on: ubuntu-latest
- jobs: Định nghĩa các công việc cần thực hiện
- sync: Tên của job
- runs-on: ubuntu-latest: Chỉ định job chạy trên hệ điều hành Ubuntu phiên bản mới nhất
3. Checkout Repositories
steps:
- name: Checkout Source Repository
uses: actions/checkout@v4
with:
repository: hoangphuctran93/public-notes
path: ./source-repo
- name: Checkout Target Repository
uses: actions/checkout@v4
with:
repository: hoangphuctran93/kb.agentc.asia
token: ${{ secrets.GH_PAT }}
path: ./target-repo
- steps: Danh sách các bước thực hiện trong job
- Checkout Source Repository: Tải mã nguồn từ repository
hoangphuctran93/public-notes
vào thư mục./source-repo
- Checkout Target Repository: Tải mã nguồn từ repository
hoangphuctran93/kb.agentc.asia
vào thư mục./target-repo
token: ${{ secrets.GH_PAT }}
: Sử dụng Personal Access Token đã lưu trong secrets của repository để xác thực
4. Cài đặt Node.js và Dependencies
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Dependencies
run: |
npm install gray-matter fs-extra
- Setup Node.js: Cài đặt Node.js phiên bản 18
- Install Dependencies: Cài đặt các thư viện cần thiết:
gray-matter
: Để phân tích front matter trong file markdownfs-extra
: Cung cấp các hàm bổ sung cho thao tác với hệ thống file
5. Script đồng bộ ghi chú
- name: Sync Published Notes
run: |
# Tạo script để đồng bộ các file có publish: true và xóa các file không còn publish
cat > sync_notes.js << 'EOF'
// [Nội dung script JavaScript]
EOF
# Đường dẫn thư mục ghi chú trong source repo
SOURCE_DIR="./source-repo"
# Đường dẫn thư mục đích trong target repo
DEST_DIR="./target-repo/content"
# Chạy script để đồng bộ và xóa các file
node sync_notes.js "$SOURCE_DIR" "$DEST_DIR"
- Tạo script: Sử dụng
cat
và heredoc (<<EOF
) để tạo file JavaScript - Định nghĩa đường dẫn: Xác định thư mục nguồn và đích
- Chạy script: Thực thi script với Node.js, truyền đường dẫn nguồn và đích làm tham số
6. Chi tiết Script JavaScript
Script JavaScript thực hiện các chức năng chính sau:
-
Quét thư mục nguồn:
- Duyệt đệ quy qua tất cả các thư mục và file
- Phân tích front matter của mỗi file markdown
- Nếu
publish: true
, sao chép file sang thư mục đích và lưu đường dẫn vào danh sáchsyncedFiles
-
Xóa file không còn được publish:
- Duyệt qua tất cả các file trong thư mục đích
- Nếu file không có trong danh sách
syncedFiles
, xóa file đó - Xóa các thư mục rỗng sau khi xóa file
7. Commit và Push thay đổi
- name: Commit and Push to Target Repository
run: |
cd ./target-repo
git config user.name "GitHub Action"
git config user.email "[email protected]"
# Lấy thời gian hiện tại theo múi giờ +7
TIMESTAMP=$(TZ=Asia/Bangkok date +'%Y-%m-%d %H:%M:%S')
git add .
# Kiểm tra xem có thay đổi không
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "🔄 Sync published notes from public-notes: $TIMESTAMP (GMT+7)"
git push
fi
- cd ./target-repo: Di chuyển vào thư mục repository đích
- git config: Cấu hình thông tin người dùng Git cho commit
- Lấy thời gian: Tạo timestamp theo múi giờ Việt Nam (GMT+7)
- git add .: Thêm tất cả thay đổi vào staging area
- Kiểm tra thay đổi: Chỉ commit nếu có thay đổi
- git commit: Tạo commit với thông điệp bao gồm emoji và timestamp
- git push: Đẩy thay đổi lên repository đích
Cách hoạt động tổng thể
- Workflow được kích hoạt khi có push lên nhánh main hoặc khi được kích hoạt thủ công
- Tải mã nguồn từ cả hai repository (nguồn và đích)
- Cài đặt Node.js và các thư viện cần thiết
- Chạy script để:
- Sao chép các file có
publish: true
từ repository nguồn sang repository đích - Xóa các file trong repository đích không còn tương ứng với file có
publish: true
trong repository nguồn
- Sao chép các file có
- Commit và push các thay đổi lên repository đích với thông điệp bao gồm timestamp theo múi giờ GMT+7
Workflow này đảm bảo rằng chỉ những ghi chú được đánh dấu publish: true
mới xuất hiện trên trang web Quartz, và khi một ghi chú bị xóa hoặc không còn được đánh dấu publish, nó cũng sẽ tự động bị xóa khỏi trang web.