/** * Liste les issues Gitea ouvertes pour un milestone donné (ex. 0.1.0). * Usage : node scripts/gitea-list-open-issues-by-milestone.js [milestone] * Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md */ const https = require('https'); const fs = require('fs'); const path = require('path'); const repoRoot = path.join(__dirname, '..'); const REPO = 'jmartin/petitspas'; const milestoneWanted = (process.argv[2] || '0.1.0').trim(); let token = process.env.GITEA_TOKEN; if (!token) { try { const tokenFile = path.join(repoRoot, '.gitea-token'); if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim(); } catch (_) {} } if (!token) { try { const briefing = fs.readFileSync( path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'), 'utf8', ); const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/); if (m) token = m[1].trim(); } catch (_) {} } if (!token) { console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN'); process.exit(1); } function getJson(apiPath) { return new Promise((resolve, reject) => { const opts = { hostname: 'git.ptits-pas.fr', path: `/api/v1/repos/${REPO}${apiPath}`, method: 'GET', headers: { Authorization: 'token ' + token, Accept: 'application/json', }, }; const req = https.request(opts, (res) => { let d = ''; res.on('data', (c) => (d += c)); res.on('end', () => { if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}: ${d.slice(0, 500)}`)); return; } try { resolve(JSON.parse(d)); } catch (e) { reject(e); } }); }); req.on('error', reject); req.end(); }); } async function fetchAllOpenIssues() { const out = []; let page = 1; const limit = 50; for (;;) { const qs = new URLSearchParams({ state: 'open', type: 'all', page: String(page), limit: String(limit), }); const batch = await getJson(`/issues?${qs}`); if (!Array.isArray(batch) || batch.length === 0) break; out.push(...batch); if (batch.length < limit) break; page += 1; if (page > 40) break; } return out; } function milestoneMatches(m, wanted) { if (!m) return false; const t = (m.title || '').trim(); return t === wanted || t === `v${wanted}`; } async function main() { let milestones; try { milestones = await getJson('/milestones?state=all'); } catch (e) { milestones = []; console.warn('Milestones non lisibles:', e.message); } const known = Array.isArray(milestones) ? milestones.map((m) => m.title).filter(Boolean) : []; if (known.length) { console.log('Milestones connus sur le dépôt :', known.join(', ')); } const issues = await fetchAllOpenIssues(); const filtered = issues.filter((i) => milestoneMatches(i.milestone, milestoneWanted)); console.log(''); console.log(`## Issues ouvertes — milestone « ${milestoneWanted} » (${filtered.length})`); console.log(''); if (filtered.length === 0) { console.log( 'Aucune issue ouverte avec ce milestone. Vérifier sur Gitea que les tickets ' + '0.1.0 portent bien le milestone, ou élargir la requête.', ); console.log(''); console.log(`(Total issues ouvertes sans filtre milestone : ${issues.length})`); const withM = issues.filter((i) => i.milestone); if (withM.length) { console.log(''); console.log('Issues ouvertes qui ont *un* milestone :'); for (const i of withM) { console.log(`- #${i.number} [${i.milestone.title}] ${i.title}`); } } process.exit(0); } for (const i of filtered.sort((a, b) => a.number - b.number)) { const labels = (i.labels || []).map((l) => l.name).join(', '); console.log(`- **#${i.number}** — ${i.title}`); if (labels) console.log(` - Labels : ${labels}`); } } main().catch((e) => { console.error(e); process.exit(1); });