Initial import
This commit is contained in:
176
scripts/worklog-capture-utils.mjs
Executable file
176
scripts/worklog-capture-utils.mjs
Executable 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 = ``;
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user