여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
/** 로컬 엔드투엔드 점검 스크립트. */
|
|
const BASE = process.env.BASE_URL ?? 'http://localhost:3100';
|
|
|
|
const j = async (method: string, path: string, body?: unknown) => {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
method,
|
|
headers: body ? { 'content-type': 'application/json' } : undefined,
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
const text = await res.text();
|
|
if (!res.ok) throw new Error(`${method} ${path} → ${res.status} ${text}`);
|
|
return text ? JSON.parse(text) : null;
|
|
};
|
|
|
|
const h = (t: string) => console.log(`\n\x1b[1m${t}\x1b[0m`);
|
|
|
|
async function main() {
|
|
h('0. health');
|
|
console.log(' ', await j('GET', '/health'));
|
|
|
|
h('1. site-1001 생성 (첫 업체 — 전부 신규)');
|
|
const a = await j('POST', '/v1/merchants/site-1001/generate?sync=true');
|
|
printStats(a);
|
|
|
|
h('2. site-1002 생성 (같은 강남/미용실 — 중복제거 발동)');
|
|
const b = await j('POST', '/v1/merchants/site-1002/generate?sync=true');
|
|
printStats(b);
|
|
printDetails(b);
|
|
|
|
h('3. publish 웹훅 + 표기 변형 (trigram 단계)');
|
|
const c = await j('POST', '/v1/merchants/publish', {
|
|
externalId: 'site-1003',
|
|
name: '강남 뷰티랩',
|
|
industryId: 'beauty.hair',
|
|
regionId: 'kr.seoul.gangnam',
|
|
description: '강남 미용실. 염색 전문.',
|
|
profile: { services: ['뿌리염색약', '여성펌'], features: ['주차가능'] },
|
|
sync: true,
|
|
});
|
|
printStats(c.generation);
|
|
printDetails(c.generation);
|
|
|
|
h('4. SEO payload');
|
|
const seo = await j('GET', '/v1/sites/site-1001/seo?limit=8');
|
|
console.log(' title :', seo.title);
|
|
console.log(' description:', seo.description);
|
|
console.log(' keywords :', seo.keywords.join(', '));
|
|
|
|
h('5. AEO payload');
|
|
const aeo = await j('GET', '/v1/sites/site-1001/aeo?limit=3');
|
|
for (const f of aeo.faqs) console.log(` Q. ${f.question}\n A. ${f.answer}`);
|
|
|
|
h('6. 의미 기반 키워드 검색');
|
|
const found = await j('POST', '/v1/keywords/search', { query: '강남 미용실 예약하고 싶어요', limit: 5 });
|
|
for (const r of found) console.log(` ${r.score.toFixed(3)} ${r.canonical} (${r.intent}, ${r.usage_count}개 업체)`);
|
|
|
|
h('7. 성과 피드백 → 저성과 강등');
|
|
console.log(
|
|
' ',
|
|
await j('POST', '/v1/sites/site-1001/performance', {
|
|
items: [
|
|
{ keyword: '강남 미용실 후기', impressions: 500, clicks: 0 },
|
|
{ keyword: '강남 미용실', impressions: 300, clicks: 40 },
|
|
],
|
|
}),
|
|
);
|
|
|
|
h('8. 비동기 큐 (BullMQ)');
|
|
console.log(' ', await j('POST', '/v1/merchants/site-2001/generate'));
|
|
for (let i = 0; i < 30; i++) {
|
|
const s = await j('GET', '/v1/sites/site-2001/seo?limit=5');
|
|
if (s.keywords.length) {
|
|
console.log(' 워커 처리 완료 →', s.keywords.join(', '));
|
|
break;
|
|
}
|
|
await new Promise((r) => setTimeout(r, 500));
|
|
}
|
|
|
|
console.log('\n✅ smoke 완료');
|
|
}
|
|
|
|
function printStats(s: any) {
|
|
console.log(
|
|
` 후보 ${s.candidates} → 신규 ${s.created} / 중복(정확 ${s.matchedExact}, 표기 ${s.matchedTrigram}, 의미 ${s.matchedVector})` +
|
|
` / 차단 ${s.rejected} / 연결 ${s.linked} / QA ${s.qaCreated} (${s.durationMs}ms)`,
|
|
);
|
|
}
|
|
|
|
function printDetails(s: any) {
|
|
for (const d of s.details ?? []) {
|
|
const sim = d.similarity != null ? ` (sim=${d.similarity.toFixed(3)} → '${d.matchedTo}')` : '';
|
|
console.log(` ${d.action.padEnd(16)} ${d.candidate}${sim}`);
|
|
}
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error('\n❌', e.message);
|
|
process.exit(1);
|
|
});
|