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

View File

@@ -0,0 +1,63 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const cwd = process.cwd();
const componentId = process.argv[2];
const captureDate = process.argv[3] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
if (!componentId) {
console.error('Usage: node scripts/capture-component-screenshot.mjs <component-id> [YYYY-MM-DD]');
process.exit(1);
}
const screenshotFileName = `${componentId}.png`;
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName,
});
const targetSelector = `#component-sample-${componentId}`;
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: {
width: 1600,
height: 1200,
},
deviceScaleFactor: 2,
});
try {
await ensureDirectory(screenshotDir);
const targetUrl = new URL(baseUrl);
targetUrl.searchParams.set('topMenu', 'apis');
await page.goto(targetUrl.toString(), {
waitUntil: 'domcontentloaded',
});
await page.waitForLoadState('networkidle').catch(() => {});
const target = page.locator(targetSelector).first();
await target.waitFor({ state: 'visible', timeout: 30000 });
await target.scrollIntoViewIfNeeded();
await target.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: componentId,
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await browser.close();
}

View File

@@ -0,0 +1,117 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const FEATURE_CAPTURE_PRESETS = {
'docs-worklogs': {
topMenu: 'docs',
screenshotFileName: 'feature-docs-worklogs.png',
targetSelector: '.app-main-card',
},
'play-layout': {
topMenu: 'play',
screenshotFileName: 'feature-play-layout.png',
targetSelector: '.app-main-card',
query: { playSection: 'layout' },
},
'apis-components': {
topMenu: 'apis',
screenshotFileName: 'feature-apis-components.png',
targetSelector: '.app-main-card',
},
'apis-widgets': {
topMenu: 'apis',
screenshotFileName: 'feature-apis-widgets.png',
targetSelector: '.app-main-card',
afterNavigation: async (page) => {
await page.getByRole('menuitem', { name: 'Widgets' }).click();
},
},
'plans-board': {
topMenu: 'plans',
screenshotFileName: 'feature-plans-board.png',
targetSelector: '.app-main-card',
},
'plans-charts': {
topMenu: 'plans',
screenshotFileName: 'feature-plans-charts.png',
targetSelector: '.app-main-card',
query: { planSection: 'charts' },
},
'chat-live': {
topMenu: 'chat',
screenshotFileName: 'feature-chat-live.png',
targetSelector: '.app-main-card, .app-chat-panel',
},
'chat-errors': {
topMenu: 'chat',
screenshotFileName: 'feature-chat-errors.png',
targetSelector: '.app-main-card, .app-chat-panel',
afterNavigation: async (page) => {
await page.getByRole('menuitem', { name: '에러 로그' }).click();
},
},
};
const cwd = process.cwd();
const presetKey = process.argv[2];
const captureDate = process.argv[3] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
if (!presetKey || !(presetKey in FEATURE_CAPTURE_PRESETS)) {
console.error(`Usage: node scripts/capture-feature-screenshot.mjs <${Object.keys(FEATURE_CAPTURE_PRESETS).join('|')}> [YYYY-MM-DD]`);
process.exit(1);
}
const preset = FEATURE_CAPTURE_PRESETS[presetKey];
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName: preset.screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1600, height: 1200 },
deviceScaleFactor: 2,
});
try {
await ensureDirectory(screenshotDir);
const targetUrl = new URL(baseUrl);
targetUrl.searchParams.set('topMenu', preset.topMenu);
if (preset.query) {
for (const [key, value] of Object.entries(preset.query)) {
targetUrl.searchParams.set(key, value);
}
}
await page.goto(targetUrl.toString(), { waitUntil: 'networkidle' });
if (preset.afterNavigation) {
await preset.afterNavigation(page);
await page.waitForLoadState('networkidle').catch(() => {});
}
const target = page.locator(preset.targetSelector).first();
await target.waitFor({ state: 'visible', timeout: 30000 });
await target.scrollIntoViewIfNeeded();
await target.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: preset.screenshotFileName.replace('.png', ''),
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await browser.close();
}

View File

@@ -0,0 +1,45 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const cwd = process.cwd();
const captureDate = process.argv[2] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
const screenshotFileName = 'main-content-fullscreen-toggle.png';
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1600, height: 1200 },
deviceScaleFactor: 2,
});
try {
await ensureDirectory(screenshotDir);
await page.goto(baseUrl, { waitUntil: 'networkidle' });
await page.getByLabel('콘텐츠 최대화').click();
const target = page.locator('.app-main-content--expanded').first();
await target.waitFor({ state: 'visible', timeout: 30000 });
await target.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: 'main-content-fullscreen-toggle',
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await browser.close();
}

View File

@@ -0,0 +1,61 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const cwd = process.cwd();
const menuGroup = process.argv[2] ?? 'docs';
const captureDate = process.argv[3] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
const supportedMenuGroups = new Set(['docs', 'plans']);
if (!supportedMenuGroups.has(menuGroup)) {
console.error('Usage: node scripts/capture-menu-screenshot.mjs [docs|plans] [YYYY-MM-DD]');
process.exit(1);
}
const screenshotFileName = `${menuGroup}-menu.png`;
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1600, height: 1200 },
deviceScaleFactor: 2,
});
try {
await ensureDirectory(screenshotDir);
const targetUrl = new URL(baseUrl);
targetUrl.searchParams.set('topMenu', menuGroup === 'docs' ? 'docs' : 'plans');
await page.goto(targetUrl.toString(), { waitUntil: 'networkidle' });
if (menuGroup === 'plans') {
await page.getByLabel('설정').click();
await page.locator('.app-header__settings-menu').first().waitFor({ state: 'visible', timeout: 30000 });
}
const target = page.locator('.app-shell').first();
await target.waitFor({ state: 'visible', timeout: 30000 });
await target.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: screenshotFileName.replace('.png', ''),
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await browser.close();
}

View File

@@ -0,0 +1,50 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const cwd = process.cwd();
const captureDate = process.argv[2] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
const screenshotFileName = 'plan-board-mobile-memo-detail.png';
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 430, height: 932 },
isMobile: true,
hasTouch: true,
deviceScaleFactor: 3,
});
const page = await context.newPage();
try {
await ensureDirectory(screenshotDir);
await page.goto(baseUrl, { waitUntil: 'networkidle' });
await page.getByText('Plans').click();
await page.getByRole('button', { name: '새 메모' }).click();
const overlayCard = page.locator('.plan-board-page__overlay-card').first();
await overlayCard.waitFor({ state: 'visible', timeout: 30000 });
await overlayCard.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: 'plan-board-mobile-memo-detail',
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await context.close();
await browser.close();
}

View File

@@ -0,0 +1,98 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const cwd = process.cwd();
const captureDate = process.argv[2] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:5174';
const screenshotFileName = 'search-command.png';
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 430, height: 932 },
isMobile: true,
hasTouch: true,
deviceScaleFactor: 3,
});
const page = await context.newPage();
try {
await ensureDirectory(screenshotDir);
await page.goto(baseUrl, { waitUntil: 'networkidle' });
await page.evaluate(() => {
const sensor = Array.from(document.querySelectorAll('div')).find((element) => {
const style = window.getComputedStyle(element);
return (
style.position === 'fixed' &&
style.top === '0px' &&
style.right === '0px' &&
style.touchAction === 'none' &&
element.childElementCount === 0
);
});
if (!sensor) {
throw new Error('Search gesture sensor not found.');
}
const rect = sensor.getBoundingClientRect();
const startX = rect.right - 24;
const startY = rect.top + 18;
const endY = startY + 120;
const createTouch = (clientY) =>
new Touch({
identifier: 1,
target: sensor,
clientX: startX,
clientY,
radiusX: 12,
radiusY: 12,
rotationAngle: 0,
force: 1,
});
const dispatch = (type, touches) => {
sensor.dispatchEvent(
new TouchEvent(type, {
bubbles: true,
cancelable: true,
touches,
targetTouches: touches,
changedTouches: touches,
}),
);
};
dispatch('touchstart', [createTouch(startY)]);
dispatch('touchmove', [createTouch(endY)]);
dispatch('touchend', []);
});
const modal = page.locator('.search-command-modal .ant-modal-content').first();
await modal.waitFor({ state: 'visible', timeout: 30000 });
await modal.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: 'search-command',
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await context.close();
await browser.close();
}

View File

@@ -0,0 +1,83 @@
import process from 'node:process';
import { chromium } from 'playwright';
import { ensureDirectory, getKstDate, resolveCapturePaths, updateWorklogCaptureSection } from './worklog-capture-utils.mjs';
const ACCESS_TOKEN = 'usr_7f3a9c2d8e1b4a6f';
const TOKEN_ACCESS_STORAGE_KEY = 'work-app.token-access.registered-token';
const SETTINGS_CAPTURE_PRESETS = {
automation: {
screenshotFileName: 'settings-app.png',
triggerLabel: '앱 설정',
},
notification: {
screenshotFileName: 'settings-notification.png',
triggerLabel: '알림',
},
};
const cwd = process.cwd();
const presetKey = process.argv[2];
const captureDate = process.argv[3] ?? getKstDate();
const baseUrl = process.env.CAPTURE_BASE_URL ?? 'http://127.0.0.1:4173';
if (!presetKey || !(presetKey in SETTINGS_CAPTURE_PRESETS)) {
console.error(`Usage: node scripts/capture-settings-screenshot.mjs <${Object.keys(SETTINGS_CAPTURE_PRESETS).join('|')}> [YYYY-MM-DD]`);
process.exit(1);
}
const preset = SETTINGS_CAPTURE_PRESETS[presetKey];
const { screenshotDir, screenshotPath, worklogPath, markdownImagePath } = resolveCapturePaths({
cwd,
captureDate,
screenshotFileName: preset.screenshotFileName,
});
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1600, height: 1200 },
deviceScaleFactor: 2,
});
await context.addInitScript(
({ tokenAccessStorageKey, accessToken }) => {
window.localStorage.setItem(tokenAccessStorageKey, accessToken);
},
{
tokenAccessStorageKey: TOKEN_ACCESS_STORAGE_KEY,
accessToken: ACCESS_TOKEN,
},
);
const page = await context.newPage();
try {
await ensureDirectory(screenshotDir);
const targetUrl = new URL(baseUrl);
targetUrl.searchParams.set('topMenu', 'plans');
await page.goto(targetUrl.toString(), { waitUntil: 'networkidle' });
await page.getByLabel('설정').click();
await page.getByRole('button', { name: preset.triggerLabel }).click();
const modal = page.locator('.ant-modal-root .ant-modal-content').last();
await modal.waitFor({ state: 'visible', timeout: 30000 });
await modal.screenshot({
path: screenshotPath,
animations: 'disabled',
});
await updateWorklogCaptureSection({
worklogPath,
captureDate,
imageAlt: preset.screenshotFileName.replace('.png', ''),
markdownImagePath,
});
console.log(`Saved: ${screenshotPath}`);
console.log(`Linked in: ${worklogPath}`);
} finally {
await context.close();
await browser.close();
}

1716
scripts/run-plan-codex-once.mjs Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

146
scripts/serve-app-dist.mjs Executable file
View File

@@ -0,0 +1,146 @@
import { createReadStream, existsSync, statSync } from 'node:fs';
import { extname, isAbsolute, join, normalize } from 'node:path';
import { createServer } from 'node:http';
import { Readable } from 'node:stream';
const port = Number(process.env.PORT ?? 5173);
const distDirName = process.env.APP_DIST_DIR ?? 'app-dist';
const rootDir = normalize(isAbsolute(distDirName) ? distDirName : join(process.cwd(), distDirName));
const workServerUrl = new URL(process.env.WORK_SERVER_URL ?? 'http://127.0.0.1:3100');
const proxyPrefixes = ['/api', '/.codex_chat'];
const mimeTypes = {
'.css': 'text/css; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.ico': 'image/x-icon',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.png': 'image/png',
'.svg': 'image/svg+xml',
'.txt': 'text/plain; charset=utf-8',
'.webmanifest': 'application/manifest+json; charset=utf-8',
'.woff2': 'font/woff2',
};
function looksLikeStaticAsset(requestedPath) {
const normalizedPath = requestedPath.split('?')[0] ?? requestedPath;
const extension = extname(normalizedPath);
return extension.length > 0;
}
function resolvePath(urlPath) {
const decodedPath = decodeURIComponent(urlPath.split('?')[0] || '/');
const requestedPath = decodedPath === '/' ? '/index.html' : decodedPath;
const absolutePath = normalize(join(rootDir, requestedPath));
if (!absolutePath.startsWith(rootDir)) {
return null;
}
if (existsSync(absolutePath) && statSync(absolutePath).isFile()) {
return absolutePath;
}
if (looksLikeStaticAsset(requestedPath)) {
return null;
}
return join(rootDir, 'index.html');
}
function shouldProxyRequest(urlPath = '/') {
return proxyPrefixes.some((prefix) => urlPath === prefix || urlPath.startsWith(`${prefix}/`));
}
function readRequestBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on('data', (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
request.on('end', () => {
resolve(chunks.length > 0 ? Buffer.concat(chunks) : null);
});
request.on('error', reject);
});
}
async function proxyRequest(request, response) {
const targetUrl = new URL(request.url ?? '/', workServerUrl);
const headers = new Headers();
Object.entries(request.headers).forEach(([key, value]) => {
if (value == null || key.toLowerCase() === 'host' || key.toLowerCase() === 'connection') {
return;
}
if (Array.isArray(value)) {
value.forEach((item) => headers.append(key, item));
return;
}
headers.set(key, value);
});
const method = request.method ?? 'GET';
const body = method === 'GET' || method === 'HEAD' ? undefined : await readRequestBody(request);
try {
const upstreamResponse = await fetch(targetUrl, {
method,
headers,
body,
});
response.writeHead(
upstreamResponse.status,
Object.fromEntries(upstreamResponse.headers.entries()),
);
if (!upstreamResponse.body) {
response.end();
return;
}
Readable.fromWeb(upstreamResponse.body).pipe(response);
} catch (error) {
response.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
response.end(
JSON.stringify({
message: error instanceof Error ? error.message : 'Failed to proxy request to work server.',
}),
);
}
}
const server = createServer(async (request, response) => {
if (shouldProxyRequest(request.url ?? '/')) {
await proxyRequest(request, response);
return;
}
const resolvedPath = resolvePath(request.url ?? '/');
if (!resolvedPath || !existsSync(resolvedPath)) {
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Not Found');
return;
}
const extension = extname(resolvedPath);
const contentType = mimeTypes[extension] ?? 'application/octet-stream';
response.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': extension === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable',
});
createReadStream(resolvedPath).pipe(response);
});
server.listen(port, '0.0.0.0', () => {
console.log(`${distDirName} server listening on http://0.0.0.0:${port}`);
});

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,
};
}