Files
Gnuboard7/docs/extension/changelog-rules.md
T
HeuJung b46396b079 fix(harness): CHANGELOG 한 버전 섹션 안 카테고리 헤딩 중복 검출
이슈 브랜치마다 working 버전 섹션 머리에 자기 ### Fixed 블록을 얹고
병합이 양쪽을 이어 붙여 sirsoft-ecommerce 1.2.1 에 Fixed·Changed 가
각 2회 남았다. 기존 판정기는 [Unreleased] 오용만 봐서 어느 훅에서도
검출되지 않았다.

- check-changelog-section: 카테고리 헤딩 중복·버전 헤더 중복·허용 밖
 카테고리 축 추가. working 섹션(+[Unreleased])만 차단, 공개 배포된
 과거 섹션은 notice (--notices). 모집단에 lang-packs 포함
- validate-runner: 공개 CHANGELOG 편집 직후 그 파일만 검사해 즉시 알림
- stop-guard 7.6: 판정기·픽스처 편집 시 픽스처 자동 실행 (저장소
 위반 0건이라 전수 실행이 공허 통과)
- 픽스처 13건, 규정 4문서, coverage 항목
- ecommerce 1.2.1 두 번째 Fixed·Changed 블록을 첫 블록에 병합 (항목 삭제 0)
2026-09-07 08:05:55 +09:00

326 lines
13 KiB
Markdown

# Changelog 규칙 (Changelog Rules)
> 코어 및 확장(모듈, 플러그인, 템플릿)의 변경사항을 CHANGELOG.md에 기록하는 규정
## TL;DR (5초 요약)
```text
1. 확장/코어 버전 업 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가)
2. 형식: Keep a Changelog 표준 (## [버전] - 날짜 / ### 카테고리 / - 항목) — 한 버전 안에 같은 카테고리 헤딩은 한 번만
3. 허용 카테고리: Added, Changed, Deprecated, Removed, Fixed, Security
4. 확장 위치: 각 확장의 루트 디렉토리 (modules/_bundled/vendor-module/CHANGELOG.md)
5. 코어 위치: 프로젝트 루트 /CHANGELOG.md (코어 버전 변경 시 필수)
```
---
## 목차
1. [CHANGELOG.md 작성 규칙](#1-changelogmd-작성-규칙)
2. [카테고리 정의](#2-카테고리-정의)
3. [파일 위치](#3-파일-위치)
4. [ChangelogParser 헬퍼](#4-changelogparser-헬퍼)
5. [API 엔드포인트](#5-api-엔드포인트)
6. [관리 화면 표시](#6-관리-화면-표시)
7. [템플릿 예시](#7-템플릿-예시)
8. [코어 버전 제약 정책](#8-코어-버전-제약-정책)
9. [다국어 파일 변경과 CHANGELOG](#9-다국어-파일-변경과-changelog)
---
## 1. CHANGELOG.md 작성 규칙
### 필수 사항
```text
확장 버전 업 시 CHANGELOG.md에 변경사항 기록 필수
미기록 시 버전 업 작업 불완전으로 간주
릴리스 태깅 전 composer test-smoke 통과 필수 (Installation 스위트)
```
- **형식**: [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 표준 준수
- **버전 관리**: [Semantic Versioning](https://semver.org/lang/ko/) 준수
- **작성 언어**: 한국어 (확장 대상이 한국어 사용자)
- **날짜 형식**: YYYY-MM-DD (ISO 8601)
- **최신 버전**: 파일 상단에 위치 (역순)
### 릴리스 전 Smoke Suite 통과
코어 또는 번들 확장의 버전 bump 시 다음을 충족해야 합니다:
```bash
# 릴리스 전 필수 검증
composer test-smoke
# 첫 실패에서 중단 (CI 모드)
composer test-smoke-ci
```
- `composer test-smoke`는 `tests/Feature/Installation/` 및 각 번들 확장의 `tests/Feature/Installation/` 디렉토리를 실행합니다.
- 스모크 통과 없이 릴리스 태깅을 진행하면 beta.2 #12 유형(마이그레이션–Repository 결합 누락) 회귀를 놓칠 수 있습니다.
- 상세: [testing-guide.md#pre-release-smoke-suite](../testing-guide.md#pre-release-smoke-suite)
### 버전 섹션 형식
```markdown
## [버전] - YYYY-MM-DD
```
예: `## [0.1.2] - 2026-02-25`
### 항목 형식
```markdown
### 카테고리
- 변경사항 설명
```
### 한 버전 섹션 안의 카테고리는 한 번만
브랜치마다 버전 섹션 머리에 자기 `### Fixed` 블록을 새로 얹으면, 병합이 양쪽을 그대로 이어 붙여 한 버전 안에 같은 카테고리 헤딩이 두 번 남는다. 오류도 경고도 없고, 렌더된 문서에서 같은 제목이 두 번 보이는 것이 유일한 증상이다.
- 새 항목은 **이미 있는 카테고리 블록 끝에** 추가한다 (같은 카테고리 헤딩을 새로 만들지 않는다)
- 병합 뒤 두 번째 블록이 보이면 그 항목을 첫 블록에 합치고 **헤딩만** 지운다 (항목 삭제 금지)
- 같은 버전 헤더(`## [1.2.1]`)도 한 번만 — 뒤 섹션의 내용을 앞 섹션에 합친다
- 카테고리 헤딩은 §2 의 6종만 쓴다. 기능 단위 분류는 `####` 서브 헤딩으로 둔다
- 이미 공개 배포된 과거 버전 섹션은 소급 수정하지 않는다
번들 확장은 정적 검사가 working 버전 섹션(+`[Unreleased]`)에서 이 조건을 차단한다. 제3자 확장에는 강제하지 않는다.
---
## 2. 카테고리 정의
| 카테고리 | 설명 | 사용 시점 |
|----------|------|----------|
| `Added` | 새 기능 추가 | 새로운 기능, 화면, API 엔드포인트 추가 |
| `Changed` | 기존 기능 변경 | 기존 동작 수정, 성능 개선, UI 변경 |
| `Deprecated` | 향후 제거 예정 | 향후 삭제될 기능 경고 |
| `Removed` | 기능 제거 | 기존 기능/API 삭제 |
| `Fixed` | 버그 수정 | 기존 버그, 오류 수정 |
| `Security` | 보안 취약점 수정 | 보안 관련 수정 |
---
## 3. 파일 위치
### 확장 (모듈/플러그인/템플릿)
```text
modules/_bundled/vendor-module/CHANGELOG.md # 모듈 (예: sirsoft-board)
plugins/_bundled/vendor-plugin/CHANGELOG.md # 플러그인 (예: sirsoft-tosspayments)
templates/_bundled/vendor-template/CHANGELOG.md # 템플릿 (예: sirsoft-admin_basic)
```
- _bundled 디렉토리에서 작업 (활성 디렉토리 직접 수정 금지)
- 업데이트 프로세스를 통해 활성 디렉토리로 복사
### 코어
```text
/CHANGELOG.md # 프로젝트 루트
```
- **코어 버전 변경 시 CHANGELOG.md에 변경사항 기록 필수** (미기록 시 버전 업 불가)
- 확장과 동일한 Keep a Changelog 형식 사용
- 코어 버전 변경 대상 파일: `config/app.php`, `.env.example`, `.env.testing`
- 버전 변경 시 반드시 CHANGELOG.md에 해당 버전 섹션 추가 후 버전 파일 수정
---
## 4. ChangelogParser 헬퍼
**파일**: `app/Extension/Helpers/ChangelogParser.php`
### 주요 메서드
| 메서드 | 설명 | 반환 |
|--------|------|------|
| `parse(string $filePath)` | CHANGELOG.md 전체 파싱 | `array` (버전별 구조화 배열) |
| `getVersionRange(string $filePath, string $from, string $to)` | 특정 범위 엔트리 추출 | `array` (from 초과 ~ to 이하) |
| `resolveChangelogPath(string $basePath, string $identifier, ?string $source)` | 소스별 경로 결정 | `?string` |
### 반환 형식
```php
[
[
'version' => '0.1.2',
'date' => '2026-02-25',
'categories' => [
['name' => 'Added', 'items' => ['새 기능 A']],
['name' => 'Fixed', 'items' => ['버그 B 수정']],
],
],
]
```
---
## 5. API 엔드포인트
```text
GET /api/admin/modules/{identifier}/changelog
GET /api/admin/plugins/{identifier}/changelog
GET /api/admin/templates/{identifier}/changelog
```
### 쿼리 파라미터
| 파라미터 | 설명 | 기본값 |
|----------|------|--------|
| `source` | CHANGELOG.md 읽을 위치 | `active` |
| `from_version` | 이 버전 초과 항목만 반환 | - |
| `to_version` | 이 버전 이하만 반환 | - |
### 사용 시나리오
- **상세 모달**: `GET .../changelog` (전체 조회)
- **업데이트 모달**: `GET .../changelog?source=bundled&from_version=0.1.1&to_version=0.1.2` (변경분)
---
## 6. 관리 화면 표시
### 업데이트 모달
- **전역 상태**: `_global.updateChangelog` (3개 확장 타입 공통)
- **표시 조건**: `updateChangelog && updateChangelog.length > 0`
- **표시 위치**: 버전 정보 아래, 외부 링크(github_changelog_url) 위
- **스타일**: blue 배경 (`bg-blue-50 dark:bg-blue-900/20`)
- **우선순위**: 인라인 changelog > 외부 링크 (둘 다 있으면 둘 다 표시)
### 상세 모달
- **전역 상태**: `_global.moduleChangelog`, `_global.pluginChangelog`, `_global.templateChangelog` (타입별)
- **섹션 ID**: `changelog_section`
- **아이콘**: `clock-rotate-left`
- **empty state**: `_global.xxxChangelog`가 비어있으면 "변경 내역 정보가 없습니다." 표시
### 메인 리스트 액션 흐름
```text
# 업데이트 버튼 클릭 시:
setState(selectedX) → apiCall(changelog?source=bundled&from_version=...&to_version=...)
→ onSuccess: setState(updateChangelog) + openModal(update_modal)
→ onError: setState(updateChangelog=[]) + openModal(update_modal)
# 상세 보기 버튼 클릭 시:
setState(selectedX) → apiCall(changelog)
→ onSuccess: setState(xChangelog) + openModal(detail_modal)
→ onError: setState(xChangelog=[]) + openModal(detail_modal)
```
---
## 7. 템플릿 예시
새 확장을 만들 때 아래 템플릿을 사용합니다:
```markdown
# Changelog
이 프로젝트의 모든 주요 변경사항을 기록합니다.
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [0.1.0] - YYYY-MM-DD
### Added
- 초기 기능 구현
```
---
## 8. 코어 버전 제약 정책
확장 manifest(`module.json`, `plugin.json`, `template.json`)의 `g7_version` 과 `dependencies.{modules|plugins}` 버전 제약 작성 규칙.
### 표기 규칙
- 형식: `>=X.Y.Z[-prerelease]` 통일 (공백 없음). 예: `>=7.0.0-beta.2`, `>=1.0.0-beta.2`
- 캐럿(`^`), 틸드(`~`), 엄격 일치(`=`) 사용 금지
- placeholder 금지: 실존하지 않는 버전(`0.1.0`, `0.0.1` 등) 을 적어 사실상 "아무 버전이나 허용" 상태로 두지 않음
### `g7_version` — 코어 최소 요구 버전
- 확장이 실제로 의존하는 코어 API/기능의 **최초 도입 버전**을 최소값으로 기재
- 확장 `version` 이 bump 될 때 `g7_version` 재검토 필수
- 번들 확장은 일반적으로 코어의 현재 릴리스와 같은 단계(beta.X, rc.X, X.Y.Z)를 하한으로 둠
- 예: 알림 시스템 3계층(NotificationDefinition/Template) 을 사용하는 모듈은 최소 `>=7.0.0-beta.2`
### `dependencies.{modules|plugins}` — 확장 간 버전 제약
- A 확장이 B 확장의 공개 Service/Contract/Model/Route/훅을 사용한다면 `dependencies.{type}.B: ">=X.Y.Z"` 기재
- B 에서 공개 표면이 변경되거나 새 API 가 도입되어 A 가 그것을 소비하게 되면 A 의 최소 버전 제약을 **그 API 최초 도입 버전**으로 상향
- 실존하지 않는 placeholder 버전(`>=0.1.0` 등)은 금지 — 버전 게이팅 무효화 유발
### 버전 제약 재검토 트리거
다음 이벤트 발생 시 관련 확장 manifest 를 전수 재검토한다.
| 이벤트 | 재검토 대상 |
|--------|------------|
| 코어 공개 확장 표면 변경 (AbstractModule/AbstractPlugin/HookManager/Contracts 등) | 모든 번들 확장의 `g7_version` |
| 코어 minor/major/beta 번호 변경 | 번들 확장 전체 `g7_version` |
| 번들 모듈/플러그인의 공개 Service/Contract/Model/Route/훅/CHANGELOG 변경 | 해당 확장을 `dependencies` 에 선언한 모든 확장 |
### CHANGELOG 기재
- `g7_version` 상향: `### Changed` 에 `- 코어 최소 요구 버전을 X.Y.Z 로 상향`
- `dependencies.{id}` 상향: `### Changed` 에 `- {id} 의존성 버전 제약을 실제 릴리스 버전에 맞춰 정비` 또는 `- {id} 최소 버전을 X.Y.Z 로 상향` (변경 이유에 따라)
---
## 9. 다국어 파일 변경과 CHANGELOG
호스트(코어/모듈/플러그인/템플릿)의 다국어 파일을 변경하면 그 호스트의 CHANGELOG 에도 사용자 관점 항목을 기재한다.
다국어 변경은 대부분 사용자가 화면에서 읽는 문장의 변경이다.
| 변경 유형 | 사용자에게 보이는 결과 |
| --- | --- |
| 누락 키 추가 | 원문 키(`sirsoft-ecommerce::messages.settings.fetch_success`)가 그대로 보이던 화면이 정상 문구로 바뀐다 |
| 문구 정정 | 잘못된 안내(예: 실제 입력 한도와 다른 글자 수)가 고쳐진다 |
| 오탈자 정정 | 화면 용어가 바뀐다 |
이들 변경은 diff 상 `lang/` 아래에만 남아 CHANGELOG 를 쓰는 시점에 눈에 띄지 않는다. 실제로 이슈 #78 에서 코어·이커머스·게시판·kginicis 4개 호스트의 문구 수정이 기록 없이 누락됐고, 그중에는 로그인/역할 권한 거절 응답에 원문 키가 노출되던 사용자 관점 결함도 있었다.
### 기재 예시
```markdown
### Fixed
- 일부 안내가 실제 문장 대신 내부 식별자로 표시되던 문제를 수정했습니다. 로그인하지 않았거나 역할 권한이 없어 요청이 거절될 때가 해당됩니다.
```
### 면제
사용자에게 노출되지 않는 문구(내부 커맨드 출력, 테스트 픽스처 등)는 변경 파일 상단에 사유와 함께 면제 주석을 둔다.
```php
// changelog:allow 개발용 Artisan 커맨드 출력 문구 — 사용자 화면 비노출
```
### 자동 검증
정적 검사가 다음 두 가지를 본다.
- 변경셋에 호스트 lang 파일이 있는데 그 호스트 CHANGELOG 가 없으면 경고한다.
- 같은 레벨의 중복 배열 키는 차단한다. 뒤 선언이 앞을 덮어 앞 블록 전용 키가 런타임에 소실되고 원문 키가 노출되기 때문이다. 정정은 항상 **두 블록 병합** — 한쪽만 삭제하면 그쪽 키가 사라진다.
언어팩 패키지(`lang-packs/_bundled/**`)는 별도 검사가 담당하므로 본 절의 대상이 아니다.
---
## 관련 파일
- `app/Extension/Helpers/ChangelogParser.php` — Changelog 파서
- `app/Http/Requests/Extension/ChangelogRequest.php` — FormRequest 검증
- `app/Http/Controllers/Api/Admin/ModuleController.php` — `changelog()` 메서드
- `app/Http/Controllers/Api/Admin/PluginController.php` — `changelog()` 메서드
- `app/Http/Controllers/Api/Admin/TemplateController.php` — `changelog()` 메서드
- `app/Services/ModuleService.php` — `getModuleChangelog()` 메서드
- `app/Services/PluginService.php` — `getPluginChangelog()` 메서드
- `app/Services/TemplateService.php` — `getTemplateChangelog()` 메서드