Files
AriWeb-Suite/modules/webmaker/scripts/collect-backlog.js
T

51 lines
4.3 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
'use strict';
// ============================================================================
// 작업 서버용 "다음 작업 반영 목록" 수집기 (v1.1.6)
//
// 관리자가 AI 분석을 보고 "다음 작업에 반영" 으로 결정하고 반영할 내용을 적은 사용자 제안·오류를
// 각 운영 서버에서 가져와 하나의 작업 목록(BACKLOG.md + backlog.json)으로 만든다.
// 다음 릴리스 작업을 시작할 때 이 파일을 읽어 요구 사항에 포함한다.
//
// 설정: ~/.config/ai-webmaker/backlog-sources.json (권한 600)
// [ { "name": "webmaker", "url": "https://webmaker.ibit.kr", "key": "<관리자 콘솔 › 설정 › 작업 서버 수집 키>" }, ... ]
// 출력: $BACKLOG_DIR (기본 ~/work/ai-webmaker-backlog) 아래 BACKLOG.md, backlog.json
// 사용: node scripts/collect-backlog.js [--mark] --mark 를 주면 가져간 글에 "수집됨" 표시를 남긴다
// node scripts/collect-backlog.js --implemented <id,id,...> --version 1.2.0 [--note "…"]
// → 릴리스에 반영한 항목을 각 서버에 "반영됨" 으로 표시 (목록에서 빠지고 사용자 화면에 버전과 함께 표시)
// 주기 실행: crontab 예) 0 * * * * node /home/master/work/WebHoting_AI_Agent/scripts/collect-backlog.js --mark >> ~/.local/state/ai-webmaker-backlog.log 2>&1
// ============================================================================
const fs = require('fs'); const path = require('path'); const os = require('os');
const CONF = process.env.BACKLOG_SOURCES || path.join(os.homedir(), '.config', 'ai-webmaker', 'backlog-sources.json');
const OUT = process.env.BACKLOG_DIR || path.join(os.homedir(), 'work', 'ai-webmaker-backlog');
const MARK = process.argv.includes('--mark');
const feedback = require('../lib/feedback'); // backlogMarkdown 재사용 (데이터 파일은 읽지 않음)
const argOf = (name) => { const i = process.argv.indexOf(name); return i > 0 ? process.argv[i + 1] : null; };
(async () => {
let sources; try { sources = JSON.parse(fs.readFileSync(CONF, 'utf8')); } catch (e) { console.error(`설정 파일을 읽지 못했습니다: ${CONF} (${e.message})`); process.exit(1); }
const impl = argOf('--implemented');
if (impl) {
// 반영 완료 표시 모드: 각 서버에 같은 ID 목록을 보낸다(그 서버에 없는 ID 는 무시됨)
const ids = impl.split(',').map((x) => x.trim()).filter(Boolean); const version = argOf('--version') || ''; const note = argOf('--note') || '';
for (const src of sources) { try { const r = await fetch(`${src.url.replace(/\/$/, '')}/api/service/backlog/implemented`, { method: 'POST', headers: { 'X-Backlog-Key': src.key, 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, version, note }) }); const j = await r.json(); console.log(`${src.name}: ${r.ok ? `${j.marked}건 반영됨 표시 (v${version})` : '실패 — ' + (j.error || r.status)}`); } catch (e) { console.log(`${src.name}: 실패 — ${e.message}`); } }
return;
}
const all = []; const report = [];
for (const src of sources) {
try {
const r = await fetch(`${src.url.replace(/\/$/, '')}/api/service/backlog`, { headers: { 'X-Backlog-Key': src.key } });
const j = await r.json(); if (!r.ok) throw new Error(j.error || `HTTP ${r.status}`);
for (const x of j.items) all.push({ ...x, server: src.name, serverUrl: src.url, serverVersion: j.version });
report.push(`${src.name}: ${j.items.length}건 (v${j.version})`);
if (MARK && j.items.length) await fetch(`${src.url.replace(/\/$/, '')}/api/service/backlog/collected`, { method: 'POST', headers: { 'X-Backlog-Key': src.key, 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: j.items.map((x) => x.id), by: `work-server ${os.hostname()}` }) });
} catch (e) { report.push(`${src.name}: 실패 — ${e.message}`); }
}
fs.mkdirSync(OUT, { recursive: true });
const at = new Date().toISOString();
fs.writeFileSync(path.join(OUT, 'backlog.json'), JSON.stringify({ at, sources: report, items: all }, null, 1));
const md = feedback.backlogMarkdown(all.map((x) => ({ ...x, title: `${x.title} (${x.server})` })), { server: sources.map((s) => s.name).join(', '), at });
fs.writeFileSync(path.join(OUT, 'BACKLOG.md'), md);
console.log(`[${at}] ${report.join(' / ')} → ${path.join(OUT, 'BACKLOG.md')} (${all.length}건)`);
})();