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 []; } };