Files
firefrost-website/_data/tasks.js
Claude 35f48d38bb feat: Read task_number from YAML frontmatter
Updated data fetcher to:
1. Prioritize task_number from frontmatter (new)
2. Fall back to folder/file name pattern (task-XXX-*)
3. Fall back to title pattern (#XX)

Also handles standalone .md files (task-098, task-099)

Chronicler #69
2026-04-08 14:33:09 +00:00

174 lines
6.0 KiB
JavaScript

const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
module.exports = async function() {
const GITEA_API = 'https://git.firefrostgaming.com/api/v1';
const REPO_OWNER = 'firefrost-gaming';
const REPO_NAME = 'firefrost-operations-manual';
const TOKEN = process.env.GITEA_TOKEN || 'e0e330cba1749b01ab505093a160e4423ebbbe36';
const tasks = [];
try {
// Get list of task directories
const response = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/docs/tasks?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
},
timeout: 30000
}
);
if (!response.ok) {
console.error('Failed to fetch task list:', response.status);
return [];
}
const items = await response.json();
// Include both directories AND standalone .md files (like task-098, task-099)
const taskItems = items.filter(d =>
(d.type === 'dir' && d.name !== '_archive') ||
(d.type === 'file' && d.name.startsWith('task-') && d.name.endsWith('.md'))
);
for (const item of taskItems) {
try {
let readmePath;
if (item.type === 'file') {
// Standalone task file
readmePath = `docs/tasks/${item.name}`;
} else {
// Directory - try README.md first
readmePath = `docs/tasks/${item.name}/README.md`;
}
let readmeResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${encodeURIComponent(readmePath)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
// If README.md not found in directory, try to find any .md file
if (!readmeResponse.ok && item.type === 'dir') {
const filesResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/docs/tasks/${encodeURIComponent(item.name)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
if (filesResponse.ok) {
const files = await filesResponse.json();
const mdFile = files.find(f => f.name.endsWith('.md'));
if (mdFile) {
readmePath = `docs/tasks/${item.name}/${mdFile.name}`;
readmeResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${encodeURIComponent(readmePath)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
}
}
}
if (readmeResponse && readmeResponse.ok) {
const fileData = await readmeResponse.json();
const content = Buffer.from(fileData.content, 'base64').toString('utf-8');
// Parse YAML frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (frontmatterMatch) {
const frontmatter = {};
frontmatterMatch[1].split('\n').forEach(line => {
const match = line.match(/^(\w+):\s*(.+)$/);
if (match) {
frontmatter[match[1]] = match[2].trim();
}
});
// Extract title from first H1
const titleMatch = content.match(/^#\s+(.+)$/m);
let title = titleMatch ? titleMatch[1] : item.name;
// Get task number - priority: frontmatter > folder name > title > content
let taskNumber = null;
// 1. YAML frontmatter task_number (preferred source)
if (frontmatter.task_number) {
taskNumber = String(frontmatter.task_number);
}
// 2. Folder/file name pattern (task-048-*)
if (!taskNumber) {
const nameNumMatch = item.name.match(/^task-0*(\d+)/);
if (nameNumMatch) {
taskNumber = nameNumMatch[1];
}
}
// 3. Title pattern (#XX)
if (!taskNumber) {
const titleNumMatch = title.match(/#(\d+)/);
if (titleNumMatch) {
taskNumber = titleNumMatch[1];
}
}
// Build Gitea URL
const giteaPath = item.type === 'file'
? `docs/tasks/${item.name}`
: `docs/tasks/${item.name}`;
tasks.push({
slug: item.name,
title: title,
taskNumber: taskNumber,
status: frontmatter.status || 'open',
priority: frontmatter.priority || 'P3',
owner: frontmatter.owner || 'Michael',
created: frontmatter.created || '2026-01-01',
giteaUrl: `https://git.firefrostgaming.com/${REPO_OWNER}/${REPO_NAME}/src/branch/master/${giteaPath}`
});
}
}
} catch (err) {
console.error(`Error processing ${item.name}:`, err.message);
}
}
// Sort by priority (P1 first) then by task number then by title
tasks.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority.localeCompare(b.priority);
}
if (a.taskNumber && b.taskNumber) {
return parseInt(a.taskNumber) - parseInt(b.taskNumber);
}
if (a.taskNumber && !b.taskNumber) return -1;
if (!a.taskNumber && b.taskNumber) return 1;
return a.title.localeCompare(b.title);
});
console.log(`[11ty] Loaded ${tasks.length} tasks from Gitea`);
return tasks;
} catch (err) {
console.error('[11ty] Error fetching tasks:', err.message);
return [];
}
};