Initial import

This commit is contained in:
how2ice
2026-04-21 03:33:23 +09:00
commit 9e4b70f1f1
495 changed files with 94680 additions and 0 deletions

176
scripts/worklog-capture-utils.mjs Executable file
View File

@@ -0,0 +1,176 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
const KST_DATE_FORMATTER = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Seoul',
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
const WORKLOG_TEMPLATE = `# {date} 작업일지
## 오늘 작업
- 화면 캡처 추가 예정
## 스크린샷
- 저장소 기준 연결된 스크린샷 없음
## 소스
### 파일 1: \`path/to/file.tsx\`
- 변경 목적과 핵심 수정 내용을 한 줄로 정리
\`\`\`diff
# 이 파일의 핵심 diff
- before
+ after
\`\`\`
### 파일 2: \`path/to/another-file.ts\`
- 필요 없으면 이 섹션은 삭제
## 실행 커맨드
\`\`\`bash
\`\`\`
## 변경 파일
-
`;
const OBSOLETE_WORKLOG_SECTION_TITLES = new Set([
'커밋 목록',
'변경 요약',
'변경 통계',
'라인 통계',
]);
function normalizeLevelTwoHeading(section) {
const match = section.match(/^##\s+([^\n]+)$/m);
if (!match) {
return '';
}
return match[1]?.replace(/\s+\(.*\)\s*$/u, '').trim() ?? '';
}
function stripObsoleteWorklogSections(content) {
const sections = content.split(/\n(?=##\s+)/);
const preservedSections = sections.filter((section) => {
const normalizedHeading = normalizeLevelTwoHeading(section);
return !OBSOLETE_WORKLOG_SECTION_TITLES.has(normalizedHeading);
});
return `${preservedSections.join('\n\n').replace(/\n{3,}/g, '\n\n').trimEnd()}\n`;
}
export async function ensureDirectory(dirPath) {
await fs.mkdir(dirPath, { recursive: true });
}
export function getKstDate(date = new Date()) {
return KST_DATE_FORMATTER.format(date);
}
export async function ensureWorklogFile(worklogPath, captureDate) {
try {
await fs.access(worklogPath);
const content = await fs.readFile(worklogPath, 'utf8');
const normalizedContent = stripObsoleteWorklogSections(content);
if (normalizedContent !== content) {
await fs.writeFile(worklogPath, normalizedContent, 'utf8');
}
} catch {
await ensureDirectory(path.dirname(worklogPath));
const content = WORKLOG_TEMPLATE.replaceAll('{date}', captureDate);
await fs.writeFile(worklogPath, content, 'utf8');
}
}
export async function updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt,
markdownImagePath,
}) {
await ensureWorklogFile(worklogPath, captureDate);
const imageLine = `![${imageAlt}](${markdownImagePath})`;
let content = await fs.readFile(worklogPath, 'utf8');
if (content.includes(imageLine)) {
return;
}
if (!content.includes('## 스크린샷') && !content.includes('## 화면 캡처')) {
content += `\n\n## 스크린샷\n\n${imageLine}\n`;
await fs.writeFile(worklogPath, content, 'utf8');
return;
}
const sections = content.split(/\n(?=##\s+)/);
const screenshotSections = [];
const remainingSections = [];
for (const section of sections) {
if (/^##\s+(?:스크린샷|화면 캡처)\s*$/m.test(section)) {
screenshotSections.push(section);
} else {
remainingSections.push(section);
}
}
const mergedScreenshotBody = screenshotSections
.map((section) => section.replace(/^##\s+(?:스크린샷|화면 캡처)\s*$/m, '').trim())
.filter(Boolean)
.join('\n\n');
const screenshotLines = Array.from(
new Set(
[mergedScreenshotBody, imageLine]
.join('\n')
.split('\n')
.map((line) => line.trim())
.filter((line) => line && line !== '- 저장소 기준 연결된 스크린샷 없음'),
),
);
const mergedScreenshotSection = `## 스크린샷\n\n${screenshotLines.join('\n')}`.trim();
const sourceSectionIndex = remainingSections.findIndex((section) => /^##\s+소스\s*$/m.test(section));
if (sourceSectionIndex === -1) {
remainingSections.push(mergedScreenshotSection);
} else {
remainingSections.splice(Math.max(1, sourceSectionIndex), 0, mergedScreenshotSection);
}
content = remainingSections.join('\n\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
await fs.writeFile(worklogPath, content, 'utf8');
}
export function resolveCapturePaths({
cwd = process.cwd(),
captureDate,
screenshotFileName,
}) {
const screenshotDir = path.join(cwd, 'docs', 'assets', 'worklogs', captureDate);
const screenshotPath = path.join(screenshotDir, screenshotFileName);
const worklogPath = path.join(cwd, 'docs', 'worklogs', `${captureDate}.md`);
const markdownImagePath = `../assets/worklogs/${captureDate}/${screenshotFileName}`;
return {
screenshotDir,
screenshotPath,
worklogPath,
markdownImagePath,
};
}