# dsmonitor Plugin 개발 가이드 > 본 문서: dsmonitor 의 plugin 시스템 약속과 plugin 개발 방법 > 대상: dsmonitor 의 추가 plugin 을 만들려는 개발자 --- ## 0. 본 가이드 안내 dsmonitor 는 codebase / Figma / Lighthouse 측정 외에 **plugin 시스템**을 통해 외부 측정 결과도 dashboard 에 표시할 수 있습니다. 본 가이드는 plugin 을 만들려는 개발자를 위한 참고 안내입니다. ### 본 가이드로 만들 수 있는 plugin 예시 - 단위 테스트 결과 (vitest / jest 등) - 번들 크기 (webpack / vite / rollup) - 접근성 검사 (axe-core / pa11y) - 다른 도구 결과 (custom 측정 / 외부 API 결과 등) 핵심: **JSON 파일을 약속된 위치에 출력하기만 하면** dsmonitor dashboard 에 자동 표시됩니다. --- ## 1. dsmonitor Plugin 시스템 핵심 ### 1.1 사이드카 흐름 ``` [Plugin 측 - 외부 개발자] [dsmonitor 측] 1. plugin 이 자체 도구 실행 (vitest / 다른 도구 / 자체 측정) ↓ 2. 결과를 JSON 으로 가공 ↓ 3. 약속된 폴더에 JSON 파일 출력 dsmonitor/reports/plugins/{id}/{date}.json ↓ 4. dsmonitor dashboard 실행 ↓ 5. 약속된 폴더 자동 검색 ↓ 6. 발견된 JSON → 자동으로 탭 추가 ↓ 7. 정보 형식 약속대로 자동 표시 ``` ### 1.2 핵심 원칙 - **dsmonitor 는 plugin 의 실행에 관여하지 않습니다.** plugin 은 자체 시점에 자체 명령으로 실행됩니다. - **dsmonitor 는 plugin 의 코드에 의존하지 않습니다.** JSON 파일만 읽습니다. - **plugin 의 시각화는 dsmonitor 가 자동 처리합니다.** plugin 은 JSX / 시각 코드를 작성하지 않습니다. ### 1.3 본 방식의 의미 | 항목 | 본 방식 | |---|---| | plugin ↔ dsmonitor 연결 | JSON 파일 (코드 의존 없음) | | plugin 실행 시점 | plugin 자체 명령 (dsmonitor 와 분리) | | plugin 시각화 | dsmonitor 표준 자동 표시 | | plugin 의 도구 자유도 | 큼 (어떤 도구든 JSON 출력만 하면 됨) | | plugin 시각 자유도 | 낮음 (dsmonitor 표준 형식만) | --- ## 2. 정보 위치 약속 ### 2.1 폴더 경로 ``` {프로젝트 루트}/dsmonitor/reports/plugins/{plugin-id}/{date}.json ``` - `{프로젝트 루트}` = dsmonitor 가 설치된 프로젝트의 루트 - `dsmonitor/reports/plugins/` = plugin 정보 전용 폴더 (dsmonitor 가 자동 생성) - `{plugin-id}` = plugin 고유 이름 (예: `tests`, `bundle-size`, `a11y-audit`) - `{date}` = 측정 날짜 (`YYYY-MM-DD` 형식, 예: `2026-04-30`) ### 2.2 예시 ``` my-project/ ├── dsmonitor/ │ ├── dsmonitor.config.ts │ └── reports/ │ ├── baseline-2026-04-30.json │ ├── figma-instances-2026-04-30.json │ └── plugins/ │ ├── tests/ │ │ ├── 2026-04-29.json │ │ └── 2026-04-30.json ← 최신 파일 │ ├── bundle-size/ │ │ └── 2026-04-30.json │ └── a11y-audit/ │ └── 2026-04-30.json └── ... ``` ### 2.3 plugin-id 명명 규칙 - 영문 소문자 + 숫자 + 하이픈만 허용 (예: `tests`, `bundle-size`, `a11y-axe`) - 다른 plugin 과 충돌하지 않는 고유 이름 사용 - 폴더 이름이 곧 plugin id 입니다. JSON 안의 `id` 필드와 일치해야 합니다. ### 2.4 날짜 파일 처리 - dsmonitor 는 폴더 안의 **가장 최신 날짜 파일**을 읽습니다. - 과거 정보 파일은 그대로 보존됩니다 (이력 보존, 추후 시계열 분석 활용 가능). - 같은 날짜에 여러 번 측정 시 같은 파일 (`{date}.json`) 덮어쓰기 권장. --- ## 3. 정보 형식 약속 ### 3.1 TypeScript 인터페이스 ```typescript interface DSMonitorPluginOutput { /** plugin 고유 id (폴더 이름과 일치) */ id: string; /** 대시보드 탭 표시 이름 */ label: string; /** 측정 시점 (ISO 8601 형식) */ measuredAt: string; /** Summary 탭의 카드 표시 정보 */ summary: { /** 가장 큰 카드 (1개 필수) */ primary: SummaryCard; /** 보조 카드 (0개 이상) */ secondary?: SummaryCard[]; }; /** Plugin 탭의 표 정보 (선택) */ details?: DetailRow[]; /** Plugin 탭에 추가 표시할 정보 (선택, 0.1.0 시점에서 dashboard 표시 없음) */ meta?: Record; } interface SummaryCard { label: string; value: string | number; hint?: string; status?: "good" | "warn" | "bad" | "neutral"; } interface DetailRow { name: string; [key: string]: string | number | boolean; } ``` ### 3.2 필수 / 선택 필드 | 필드 | 필수? | 의미 | |---|---|---| | `id` | 필수 | plugin 고유 id (폴더 이름과 일치) | | `label` | 필수 | 대시보드 탭 이름 | | `measuredAt` | 필수 | ISO 8601 형식 측정 시점 | | `summary.primary` | 필수 | 가장 큰 카드 (1개) | | `summary.secondary` | 선택 | 보조 카드 | | `details` | 선택 | 표 형식 정보 | | `meta` | 선택 | 자유 형식 추가 정보 (0.1.0 시점에서 표시 없음) | ### 3.3 예시 JSON #### 단순 예시 (Tests plugin) ```json { "id": "tests", "label": "Tests", "measuredAt": "2026-04-30T14:23:00+09:00", "summary": { "primary": { "label": "통과율", "value": "97.7%", "hint": "42 / 43 통과", "status": "good" }, "secondary": [ { "label": "통과", "value": 42, "status": "good" }, { "label": "실패", "value": 1, "status": "bad" }, { "label": "총 실행 시간", "value": "12.3s" } ] }, "details": [ { "name": "addUser", "status": "pass", "duration": "5.2ms" }, { "name": "removeUser", "status": "pass", "duration": "3.1ms" }, { "name": "validateEmail", "status": "fail", "duration": "0.3ms" } ] } ``` #### 다른 도메인 예시 (Bundle Size plugin) ```json { "id": "bundle-size", "label": "번들 크기", "measuredAt": "2026-04-30T14:00:00+09:00", "summary": { "primary": { "label": "총 번들 크기", "value": "342 KB", "hint": "gzip 기준", "status": "warn" }, "secondary": [ { "label": "JS", "value": "287 KB", "status": "warn" }, { "label": "CSS", "value": "55 KB", "status": "good" }, { "label": "main 대비", "value": "+12 KB", "status": "warn" } ] }, "details": [ { "name": "main.js", "size": "187 KB", "gzip": "62 KB" }, { "name": "vendor.js", "size": "245 KB", "gzip": "78 KB" }, { "name": "main.css", "size": "120 KB", "gzip": "32 KB" } ] } ``` #### 또 다른 도메인 예시 (A11y Audit plugin) ```json { "id": "a11y-audit", "label": "접근성", "measuredAt": "2026-04-30T15:00:00+09:00", "summary": { "primary": { "label": "위반 항목", "value": 3, "hint": "axe-core 검사", "status": "bad" }, "secondary": [ { "label": "심각", "value": 1, "status": "bad" }, { "label": "심각도 중간", "value": 2, "status": "warn" }, { "label": "검사 페이지", "value": 12 } ] }, "details": [ { "name": "color-contrast", "severity": "serious", "count": 2, "page": "/dashboard" }, { "name": "label", "severity": "critical", "count": 1, "page": "/login" } ] } ``` ### 3.4 status 색상 매핑 | status 값 | 색상 의도 | 활용 케이스 | |---|---|---| | `good` | 초록 | 통과 / 정상 / 개선 | | `warn` | 노랑 | 경계 / 약간 회귀 | | `bad` | 빨강 | 실패 / 회귀 / 위반 | | `neutral` (또는 미지정) | 회색 | 단순 정보 | ### 3.5 details 의 컬럼 결정 흐름 - 컬럼은 **모든 행의 키 합집합**으로 결정됩니다 - 첫 행에서 빠진 키도 컬럼으로 추가됩니다 - 행마다 키가 다를 경우 빈 셀로 표시됩니다 권장: 같은 plugin 안의 모든 행은 같은 컬럼 사용 ### 3.6 meta 부분 — 0.1.0 안 발견 사항 dsmonitor 0.1.0 시점에서는 **dashboard 표시 없음** (정보 형식 약속은 유지). - plugin JSON 안에 meta 필드 자유롭게 두되 dashboard 에서 안 보임 - 정보 형식 보존만 (추후 활용 가능) - 추후 dsmonitor 0.2.0 시점에서 dashboard 표시 추가 검토 → meta 필드는 **선택 사항** — 안 써도 됨. 환경 정보 / 디버깅 정보 등 자유 정보로 활용 가능. --- ## 4. dsmonitor 측 자동 표시 규칙 ### 4.1 탭 추가 - dsmonitor dashboard 가 `dsmonitor/reports/plugins/*` 폴더 자동 검색 - 발견된 폴더마다 탭 1개 자동 추가 - 탭 이름 = JSON 의 `label` 필드 - 탭 위치 = dsmonitor 기본 탭 뒤 (id 알파벳 순) > ⓘ plugin 추가 / 제거 시 다른 plugin 의 탭 위치 + Layer 번호 변동 가능 (id 알파벳 순 정렬). ### 4.2 Summary 탭 Layer 추가 dsmonitor Summary 탭은 측정 부분별 Layer 구조입니다 (Layer 01 / Code, Layer 02 / Lighthouse, Layer 03 / Figma). - plugin 1개당 Layer 04+ 자동 추가 - layer-head 에 plugin label + measuredAt 표시 (dsmonitor 자체와 동일 패턴) - Layer 안의 카드 표시: - `summary.primary` → 가장 큰 카드 - `summary.secondary` → 보조 카드 (배열 순서) > ⓘ Layer 번호는 동적 — dsmonitor 자체 측정 (Code / Lighthouse / Figma) 중 활성 Layer 수에 의존. > - 모두 활성 환경: Layer 04 / 05 / ... > - figma 부분 없는 환경: Layer 03 / 04 / ... > - codebase 외 모두 없는 환경: Layer 02 / 03 / ... ### 4.3 Plugin 탭 표 - `details` 가 있으면 자동으로 표 형식 표시 - 첫 컬럼 = `name` 필드 - 나머지 컬럼 = 모든 행의 키 합집합 순서 ### 4.4 측정 시점 표시 | 위치 | 표시 형식 | 예시 | |---|---|---| | Summary 탭 Layer 의 layer-head stamp | 날짜만 | `2026-04-30` | | Plugin 탭 헤더 | 날짜 + 시간 (분 단위) | `2026-04-30 14:23` | ### 4.5 stale 알림 (회색 배지) - `measuredAt` 가 **7일 이상** 지난 plugin 정보에 회색 배지 표시 - 사용자가 정보가 오래됐음을 인지하도록 함 - 탭 이름 옆 / Summary Layer 옆에 회색 점 시각 ### 4.6 검증 실패 시 처리 dsmonitor 가 plugin JSON 검증: - 필수 필드 (`id` / `label` / `measuredAt` / `summary.primary`) 누락 - 폴더 이름 ≠ JSON 의 `id` 필드 - 잘못된 JSON 형식 위 케이스 발견 시 dashboard 의 plugin 탭에 빨간 알림 배지 + 탭 본문에 오류 메시지. 사용자 디버깅 도움. --- ## 5. plugin 측 작업 분담 ### 5.1 plugin 측이 해야 할 작업 1. **자체 도구 실행** — 측정 도구 (vitest / webpack / axe-core / 다른 도구) 자체 명령 2. **결과 가공** — 도구 결과를 dsmonitor plugin 형식 (Section 3) 으로 변환 3. **JSON 출력** — 약속된 폴더 (Section 2) 에 파일 쓰기 4. **정보 갱신 시점 관리** — plugin 자체가 정보 갱신 시점 결정 ### 5.2 plugin 측이 안 하는 작업 - dsmonitor 코드에 직접 import / 연결 - dsmonitor dashboard 의 시각 코드 작성 - dsmonitor CLI 에 명령 등록 - dsmonitor audit 시점에 자동 실행 ### 5.3 plugin 자체 명령 패턴 plugin 측 자체 npm scripts 또는 명령 형식 자유: ```json { "scripts": { "measure:plugin": "node scripts/export-dsmonitor-plugin.mjs" } } ``` ### 5.4 plugin 정보 갱신 시점 ```bash # plugin 정보 갱신 시점 (plugin 측 자체 명령) $ npm run measure:plugin # dsmonitor dashboard 갱신 시점 (dsmonitor 측 명령) $ npx dsmonitor dashboard # 본 둘 시점이 다를 수 있음 — 사용자가 인지해야 함 # 그래서 measuredAt 필드가 필수 ``` ### 5.5 CI 환경에서 plugin 정보 갱신 CI 파이프라인 (GitHub Actions / GitLab CI / Bitbucket Pipelines 등) 에서 plugin 정보 갱신 권장: ```yaml # 예시 (GitHub Actions) - name: Run plugin run: npm run measure:plugin - name: Commit plugin output run: | git config user.email "ci@example.com" git config user.name "CI" git add dsmonitor/reports/plugins/ git diff --cached --quiet || git commit -m "chore(dsmonitor): plugin 정보 갱신" git push ``` --- ## 6. 검증 방법 ### 6.1 plugin 측 자체 검증 권장 ```typescript function validatePluginOutput(output: unknown): DSMonitorPluginOutput { if (!output || typeof output !== "object") { throw new Error("plugin output must be an object"); } const o = output as Record; if (typeof o.id !== "string") throw new Error("id must be string"); if (typeof o.label !== "string") throw new Error("label must be string"); if (typeof o.measuredAt !== "string") throw new Error("measuredAt must be string"); if (!o.summary || typeof o.summary !== "object") { throw new Error("summary must be an object"); } const summary = o.summary as Record; if (!summary.primary || typeof summary.primary !== "object") { throw new Error("summary.primary must be an object"); } // 필요시 SummaryCard / DetailRow 세부 검증 추가 return o as DSMonitorPluginOutput; } ``` ### 6.2 디버깅 흐름 - dsmonitor dashboard 에서 plugin 탭이 안 보이면: - `dsmonitor/reports/plugins/{id}/` 폴더 존재 확인 - 폴더 안 `{date}.json` 파일 존재 확인 - JSON 형식 유효 확인 (`cat file.json | jq .`) - 필수 필드 누락 확인 - dsmonitor dashboard 에서 plugin 탭이 빨갛게 표시되면: - dashboard 콘솔 (브라우저 개발자 도구) 에서 오류 메시지 확인 - `validatePluginOutput()` 호출 추가해서 plugin 측 자체 검증 --- ## 7. 제한 사항과 추후 계획 ### 7.1 현재 (dsmonitor 0.1.0) 제한 사항 | 제한 | 의미 | |---|---| | 시각 자유도 | dsmonitor 표준 (카드 / 표) 만 — 자유 차트 / 그래프 없음 | | 시계열 표시 | 최신 정보 1개만 표시 — 과거 정보와 비교 없음 | | Plugin 간 상호 작용 | 없음 — 각 plugin 은 독립 | | Plugin CLI 통합 | 없음 — dsmonitor CLI 와 분리 | | Plugin audit 시점 통합 | 없음 — plugin 자체 명령으로 갱신 | | meta dashboard 표시 | 없음 — 정보 형식만 유지 | ### 7.2 추후 계획 (dsmonitor 0.2.0 이후) - **시계열 차트** — 과거 정보 누적 → 추이 차트 자동 표시 - **자유 시각** — plugin 측이 chart 형식 선택 - **자동 알림** — 임계치 초과 시 경고 표시 - **meta dashboard 표시** — 정보 형식 그대로 활용 - **plugin 출력 위치 변경** — dsmonitor.config.ts 에서 위치 설정 가능 ### 7.3 본 plugin 약속의 안정성 - 본 가이드의 정보 형식은 **0.1.x 안 안정** 입니다. - 0.2.0 이후 추가 필드 가능 (기존 필드는 호환 유지). - 기존 plugin 은 0.2.0 이후에도 그대로 작동합니다. --- ## 8. 부록 — plugin 예시 코드 ### 8.1 가장 단순한 plugin (Node.js + ESM) ```javascript // scripts/export-dsmonitor-plugin.mjs import { writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; // === 측정 === function measure() { // TODO: 자기 측정 도구 결과 가져오기 return { passed: 42, failed: 1, total: 43, items: [ { name: "함수1", status: "pass", duration: 5.2 }, { name: "함수2", status: "pass", duration: 12.3 }, { name: "함수3", status: "fail", duration: 0.3 } ] }; } // === dsmonitor plugin 형식 변환 === function toDSMonitorPluginOutput(measurement) { const { passed, failed, total, items } = measurement; return { id: "tests", label: "Tests", measuredAt: new Date().toISOString(), summary: { primary: { label: "통과율", value: `${(passed / total * 100).toFixed(1)}%`, hint: `${passed} / ${total} 통과`, status: failed === 0 ? "good" : "bad" }, secondary: [ { label: "통과", value: passed, status: "good" }, { label: "실패", value: failed, status: failed > 0 ? "bad" : "neutral" } ] }, details: items.map(i => ({ name: i.name, status: i.status, duration: `${i.duration.toFixed(1)}ms` })) }; } // === 자체 검증 === function validatePluginOutput(output) { if (typeof output.id !== "string") throw new Error("id must be string"); if (typeof output.label !== "string") throw new Error("label must be string"); if (typeof output.measuredAt !== "string") throw new Error("measuredAt must be string"); if (!output.summary?.primary) throw new Error("summary.primary required"); return output; } // === 출력 === function outputPlugin(output) { validatePluginOutput(output); const PLUGIN_DIR = `dsmonitor/reports/plugins/${output.id}`; const date = new Date().toISOString().slice(0, 10); mkdirSync(PLUGIN_DIR, { recursive: true }); const filepath = join(PLUGIN_DIR, `${date}.json`); writeFileSync(filepath, JSON.stringify(output, null, 2)); console.log(`✓ dsmonitor plugin output: ${filepath}`); } // === 실행 === const measurement = measure(); const output = toDSMonitorPluginOutput(measurement); outputPlugin(output); ``` 실행: ```bash $ node scripts/export-dsmonitor-plugin.mjs ✓ dsmonitor plugin output: dsmonitor/reports/plugins/tests/2026-04-30.json $ npx dsmonitor dashboard # → dashboard 에 "Tests" 탭 자동 추가됨 ``` ### 8.2 webpack 번들 크기 plugin 예시 > ⓘ **Node 18.17+ 필요** — 본 예시의 `readdirSync({ recursive: true })` 흐름은 Node 18.17+ 부터 지원. 그 이전 케이스는 `fast-glob` 등 별도 라이브러리 사용 권장. ```javascript // scripts/export-bundle-size-plugin.mjs import { writeFileSync, mkdirSync, readFileSync, statSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { gzipSync } from "node:zlib"; // === 번들 측정 === function measureBundle() { const distDir = "dist"; const files = readdirSync(distDir, { recursive: true }) .filter(f => /\.(js|css)$/.test(f)); return files.map(file => { const fullpath = join(distDir, file); const content = readFileSync(fullpath); const size = statSync(fullpath).size; const gzipSize = gzipSync(content).length; return { name: file, size, gzipSize, type: file.endsWith(".js") ? "js" : "css" }; }); } // === dsmonitor plugin 형식 변환 === function toDSMonitorPluginOutput(files) { const totalSize = files.reduce((sum, f) => sum + f.size, 0); const totalGzip = files.reduce((sum, f) => sum + f.gzipSize, 0); const jsTotal = files.filter(f => f.type === "js").reduce((sum, f) => sum + f.gzipSize, 0); const cssTotal = files.filter(f => f.type === "css").reduce((sum, f) => sum + f.gzipSize, 0); const formatKB = (bytes) => `${(bytes / 1024).toFixed(0)} KB`; const SIZE_THRESHOLD_KB = 300; const status = totalGzip / 1024 > SIZE_THRESHOLD_KB ? "warn" : "good"; return { id: "bundle-size", label: "번들 크기", measuredAt: new Date().toISOString(), summary: { primary: { label: "총 번들 크기", value: formatKB(totalGzip), hint: "gzip 기준", status }, secondary: [ { label: "JS", value: formatKB(jsTotal) }, { label: "CSS", value: formatKB(cssTotal) } ] }, details: files.map(f => ({ name: f.name, type: f.type, size: formatKB(f.size), gzip: formatKB(f.gzipSize) })) }; } // === 자체 검증 === function validatePluginOutput(output) { if (typeof output.id !== "string") throw new Error("id must be string"); if (typeof output.label !== "string") throw new Error("label must be string"); if (typeof output.measuredAt !== "string") throw new Error("measuredAt must be string"); if (!output.summary?.primary) throw new Error("summary.primary required"); return output; } // === 출력 === function outputPlugin(output) { validatePluginOutput(output); const PLUGIN_DIR = `dsmonitor/reports/plugins/${output.id}`; const date = new Date().toISOString().slice(0, 10); mkdirSync(PLUGIN_DIR, { recursive: true }); const filepath = join(PLUGIN_DIR, `${date}.json`); writeFileSync(filepath, JSON.stringify(output, null, 2)); console.log(`✓ dsmonitor plugin output: ${filepath}`); } // === 실행 === const files = measureBundle(); const output = toDSMonitorPluginOutput(files); outputPlugin(output); ``` ### 8.3 axe-core 접근성 plugin 예시 ```javascript // scripts/export-a11y-plugin.mjs import { writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import AxeBuilder from "@axe-core/playwright"; import { chromium } from "playwright"; const PAGES = ["/", "/dashboard", "/login"]; const BASE_URL = "http://localhost:3000"; // === 측정 === async function measureA11y() { const browser = await chromium.launch(); const violations = []; try { for (const page of PAGES) { const ctx = await browser.newContext(); const pg = await ctx.newPage(); await pg.goto(`${BASE_URL}${page}`); const result = await new AxeBuilder({ page: pg }).analyze(); for (const v of result.violations) { violations.push({ ...v, page }); } await ctx.close(); } } finally { await browser.close(); } return violations; } // === dsmonitor plugin 형식 변환 === function toDSMonitorPluginOutput(violations) { const critical = violations.filter(v => v.impact === "critical").length; const serious = violations.filter(v => v.impact === "serious").length; const total = violations.length; return { id: "a11y-audit", label: "접근성", measuredAt: new Date().toISOString(), summary: { primary: { label: "위반 항목", value: total, hint: "axe-core 검사", status: total === 0 ? "good" : critical > 0 ? "bad" : "warn" }, secondary: [ { label: "심각", value: critical, status: critical > 0 ? "bad" : "neutral" }, { label: "심각도 중간", value: serious, status: serious > 0 ? "warn" : "neutral" }, { label: "검사 페이지", value: PAGES.length } ] }, details: violations.map(v => ({ name: v.id, severity: v.impact ?? "unknown", count: v.nodes.length, page: v.page })) }; } // === 자체 검증 === function validatePluginOutput(output) { if (typeof output.id !== "string") throw new Error("id must be string"); if (typeof output.label !== "string") throw new Error("label must be string"); if (typeof output.measuredAt !== "string") throw new Error("measuredAt must be string"); if (!output.summary?.primary) throw new Error("summary.primary required"); return output; } // === 출력 === function outputPlugin(output) { validatePluginOutput(output); const PLUGIN_DIR = `dsmonitor/reports/plugins/${output.id}`; const date = new Date().toISOString().slice(0, 10); mkdirSync(PLUGIN_DIR, { recursive: true }); const filepath = join(PLUGIN_DIR, `${date}.json`); writeFileSync(filepath, JSON.stringify(output, null, 2)); console.log(`✓ dsmonitor plugin output: ${filepath}`); } // === 실행 === const violations = await measureA11y(); const output = toDSMonitorPluginOutput(violations); outputPlugin(output); ``` --- ## 9. 체크리스트 — Plugin 발행 직전 - [ ] `dsmonitor/reports/plugins/{id}/` 폴더 생성됨 - [ ] `{date}.json` 파일 출력됨 - [ ] JSON 형식 유효 (`jq .` 통과) - [ ] `id` 필드 = 폴더 이름과 일치 - [ ] 필수 필드 (`id` / `label` / `measuredAt` / `summary.primary`) 모두 존재 - [ ] `measuredAt` 가 ISO 8601 형식 - [ ] `summary.primary.value` 가 사용자에게 의미 있는 값 - [ ] `summary.secondary` 카드 (있으면) 적절한 `status` 설정 - [ ] `details` (있으면) 모든 행이 같은 컬럼 구조 - [ ] `validatePluginOutput()` 자체 검증 호출 - [ ] plugin 자체 명령 실행 시 정보 갱신 정상 작동 - [ ] dsmonitor dashboard 에서 plugin 탭 정상 표시 - [ ] CI 환경에서 자동 갱신 (선택) 작동 --- ## 10. FAQ ### Q1. plugin 폴더에 JSON 파일이 여러 날짜로 있으면 어떻게 표시되나요? 가장 최신 날짜 파일만 표시됩니다. 과거 파일은 영향 없이 보존되며, 추후 시계열 활용 (0.2.0+) 가능합니다. ### Q2. 같은 날짜에 여러 번 측정하면 어떻게 처리하나요? 같은 파일 (`{date}.json`) 을 덮어쓰기 권장합니다. plugin 측이 출력 시 최신 정보로 덮어씌우면 됩니다. ### Q3. plugin id 가 다른 plugin 과 충돌하면? 폴더 이름이 곧 plugin id 이므로 자연스럽게 분리됩니다. 다만 의미상 같은 plugin 이 중복 등록되면 사용자가 인지하기 어려우므로 고유 이름 사용 필수. ### Q4. plugin 측이 정보를 안 갱신하면 어떻게 되나요? dsmonitor 는 가장 최신 파일을 표시합니다. 정보가 7일 이상 안 갱신되면 회색 배지 (stale 알림) 표시. plugin 측이 정보 갱신 시점에 책임이 있습니다. ### Q5. JSON 안에 한국어 사용 가능한가요? 네, UTF-8 형식이면 한국어 / 다른 언어 자유롭게 사용 가능합니다. `label` / `summary.primary.label` 등 모든 표시 텍스트에 사용 가능합니다. ### Q6. plugin 정보를 git 에 commit 해야 하나요? 선택입니다. - **commit 권장** — 팀 전체가 같은 정보 공유, CI 자동 갱신 흐름 자연스러움 - **gitignore** — 정보가 자주 갱신되어 commit 부담이 크면 gitignore 후 CI artifact 로 관리 ### Q7. dsmonitor 측이 plugin 정보를 못 읽으면 어떻게 알 수 있나요? dsmonitor dashboard 에서 plugin 탭이: - 빨간 알림 배지 표시 → JSON 형식 또는 필수 필드 오류 - 회색 알림 배지 표시 → 정보 7일 이상 오래됨 (stale) - 정상 표시 → 정보 정상 읽음 ### Q8. plugin 출력 위치를 다른 곳으로 변경 가능한가요? dsmonitor 0.1.0 시점에서는 `dsmonitor/reports/plugins/{id}/{date}.json` 만 지원합니다. 추후 시점에서 dsmonitor.config.ts 의 plugin 위치 설정 추가 검토 가능합니다. ### Q9. plugin 마다 다른 컬럼을 표시하고 싶어요. `details` 의 객체는 자유 형식이라 plugin 마다 다른 컬럼 가능합니다. 다만 같은 plugin 안의 모든 행은 같은 컬럼 사용 권장 (다르면 빈 셀로 표시). ### Q10. 본 가이드 외 (예: 차트 / 그래프 자유 시각) 가 필요해요. dsmonitor 0.1.0 시점에서는 표준 표 / 카드만 지원합니다. 자유 시각은 dsmonitor 0.2.0 / 0.3.0 시점에서 추가 검토 가능합니다. ### Q11. plugin 정보가 너무 커서 dashboard 가 느려지는데? dsmonitor 0.1.0 은 정보 크기 제한 없음. plugin 측에서 `details` 행 100~200 미만 권장. 추후 0.2.0 에서 페이징 검토. ### Q12. 같은 측정 도구를 다른 위치에 출력 가능? 가능. plugin id 는 폴더 단위 — 같은 vitest 결과를 `tests-unit` / `tests-integration` 두 plugin 으로 분리 가능. ### Q13. dsmonitor 가 plugin 정보를 표시할 때 캐싱하나? dsmonitor 는 dashboard 빌드 시점에 1회 읽음. plugin 정보 갱신 후 `npx dsmonitor dashboard` 재실행 필요. ### Q14. meta 가 dashboard 에 안 보임? dsmonitor 0.1.0 시점에서 dashboard 표시 없음. 정보 형식 약속만 유지 — JSON 파일 안 meta 는 보존됨. 추후 0.2.0 시점에서 dashboard 표시 추가. --- ## 11. 추가 의뢰 본 가이드에 누락된 부분 / 명확하지 않은 부분 / 추가 필요 시점에 dsmonitor 작성자에게 의뢰 부탁합니다. 추가 검토 가능: - 자유 차트 / 그래프 시각 - 시계열 표시 (과거 정보 비교) - meta dashboard 표시 - plugin 간 상호 작용 - 자동 알림 - plugin 측 schema 자체 명세 --- **문서 끝** 본 가이드 대응 dsmonitor 버전: 0.1.0