Files
Gnuboard7/AGENTS.md
T
HeuJung bd215bc586 feat(identity): NHN KCP 휴대폰 본인확인 플러그인 추가
메일 인증 대신 휴대폰 본인확인을 쓸 수 있게 하는 플러그인을 추가한다.
가입·비밀번호 재설정·성인인증 등 코어 IDV 가 강제하는 모든 지점에서 동작하며,
테스트 모드로 계약 없이 전 흐름을 확인할 수 있다.

구현·검수 과정에서 드러난 저장소 전역 결함을 함께 처리했다.

- 외래키 컬럼의 한국어 comment 가 방출되지 않던 문제 (소스 38건 교정 +
 기설치본 백필 업그레이드 스텝 7종). ->comment 를 ->constrained 뒤에
 체인하면 예외 없이 통과하면서 comment 가 조용히 사라진다
- 로케일 전환 후 확장 액션 핸들러가 소실되던 문제 (플러그인 3종에
 재등록 진입점 노출)
- 본인인증 상태 폴링 응답이 누적 시도 횟수를 함께 내보내던 문제
- 인증 안내 문구와 실제 진행 방식의 폭 경계 불일치 (768~1023px).
 엔진과 같은 값을 같은 방법으로 읽도록 교정
- transition_overlay_target 이 replace 없이 무효이던 관리자 목록 40곳.
 로딩 표시가 나오지 않아도 경고가 남지 않아 화면을 봐야만 알 수 있었다

두 인증 플러그인의 코어 최소 요구 버전을 7.0.6 으로 올린다. 운영 모드
자격증명 필수 검사가 7.0.6 의 설정 저장 검증 표면에 의존하며, 그보다 낮은
코어에서는 그 검사가 조용히 통과해 빈 자격증명으로 저장할 수 있었다.

재발 차단으로 정적 검사 룰 4종을 신설하고, 검사 도구가 미커밋 신규 확장을
"검사 파일 0" 으로 통과시키던 사각을 함께 막았다.
2026-07-31 00:32:46 +09:00

67 KiB
Raw Blame History

그누보드7 Development Guide

이 문서는 그누보드7 오픈소스 CMS 프로젝트의 개발 가이드입니다. AI 에이전트 및 외부 기여자를 위한 참고 자료입니다.

빠른 참조 - 상세 가이드 문서

백엔드 backend/ (32개)

문서 설명 TL;DR 핵심
activity-log-hooks.md 활동 로그 훅 레퍼런스 (Activity Log Hooks Reference) 코어 66훅 + 이커머스 92훅 + 게시판 32훅 + 페이지 8훅 = 총 198훅
activity-log.md 활동 로그 시스템 (Activity Log System) Monolog 기반: Service 훅 → Listener → Log::channel('activity...
admin-settings-access.md Admin 환경설정 값 접근 (g7_core_settings vs config()) 동기화 SSoT: storage/app/settings/*.json → SettingsServicePr...
api-documentation.md API 레퍼런스 문서 규정 (API Documentation) 모든 API 엔드포인트는 레퍼런스 문서 필수 — 메서드/URI/파라미터/응답 필드 + 요청·응답 예시 ...
api-resources.md API 리소스 Resource: BaseApiResource 상속 필수 / Collection: BaseApiColl...
authentication.md 인증 및 세션 처리 Laravel Sanctum 토큰 전용 인증 (Bearer 토큰만 사용)
broadcasting.md Broadcasting (실시간 이벤트) Laravel Reverb 사용 (WebSocket)
console-confirm.md 콘솔 yes/no 프롬프트 (ConsoleConfirm) 콘솔 커맨드의 yes/no 프롬프트는 $this->unifiedConfirm() 사용 — Laravel...
controllers.md 컨트롤러 계층 구조 AdminBaseController / AuthBaseController / PublicBaseCont...
core-config.md 코어 설정 (config/core.php) config/core.php = 코어 권한/역할/메뉴/메일템플릿의 SSoT (Single Source ...
core-update-system.md 코어 업데이트 시스템 (Core Update System) 코어 업그레이드 스텝: upgrades/ 디렉토리 (프로젝트 루트), 네임스페이스 App\Upgrades
data-sync-helpers.md 데이터 동기화 Helper (Data Sync Helpers) 모든 데이터 동기화는 Service/Seeder 가 Helper 를 호출해 수행 (직접 Model 조작...
dto.md DTO (Data Transfer Object) 사용 규칙 DTO 두 패턴 — Value Object(불변 1회 전달) vs Data Carrier(다단계 변형/...
enum.md Enum 사용 규칙 상태/타입/분류 = Enum 필수 (PHP 8.1+ Backed Enum)
exceptions.md Custom Exception 다국어 처리 예외 메시지 하드코딩 금지 → __() 함수 필수
geoip.md GeoIP 시스템 (MaxMind GeoLite2) MaxMind GeoLite2-City DB 기반 IP → 타임존 감지 (SetTimezone 미들웨어...
identity-messages.md 본인인증 메시지 템플릿 시스템 (Identity Messages) 알림 시스템(notification_*)과 완전 분리된 IDV 전용 템플릿 인프라
identity-policies.md 본인인증 정책 시스템 (Identity Policies) -
identity-providers.md IDV Provider 작성 가이드 (Identity Verification Providers) VerificationProviderInterface 구현 + IdentityProviderManage...
language-pack-service.md LanguagePackService (백엔드 Service 레이어) LanguagePackService 가 install/activate/deactivate/uninsta...
middleware.md 미들웨어 등록 규칙 인증 필요 미들웨어 → 전역 등록 금지!
notification-system.md 알림 시스템 (Notification System) GenericNotification 범용 클래스 1개로 모든 알림 처리 (개별 클래스 불필요)
response-helper.md API 응답 규칙 (ResponseHelper) 모든 API 응답은 ResponseHelper 사용
routing.md 라우트 네이밍 및 경로 모든 라우트는 name() 필수: ->name('api.users.index')
search-system.md Scout 검색 엔진 시스템 (Search System) Laravel Scout + DatabaseFulltextEngine: MySQL FULLTEXT + ...
seo-system.md SEO 페이지 생성기 시스템 (SEO Page Generator) SeoMiddleware: 봇 요청 감지 → ?locale= 파라미터 해석 → SeoRenderer가 ...
service-provider.md 서비스 프로바이더 안전성 DB 접근 전 .env 파일 존재 확인 필수
service-repository.md Service-Repository 패턴 RepositoryInterface 주입 필수 (구체 클래스 직접 주입 금지)
settings-multilingual-enrichment.md Settings 카탈로그 다국어 자동 보강 settings JSON 의 다국어 카탈로그 라벨(_cached_name 등)은 카탈로그 빌드 시점에 보강
translatable-seeders.md 다국어 시더 인터페이스 (Translatable Seeders) 다국어 JSON 컬럼(name 등)을 시드하는 확장 entity 시더는 TranslatableSeede...
user-overrides.md 사용자 수정 보존 (HasUserOverrides Trait) 모델에 use HasUserOverrides; + `protected array $trackable...
validation.md 검증 (Validation) 필수: FormRequest에서 검증 (Service에 검증 로직 배치 금지)

프론트엔드 frontend/ (50개)

문서 설명 TL;DR 핵심
actions-g7core-api.md 액션 시스템 - G7Core API (React 컴포넌트용) -
actions-handlers-navigation.md 액션 핸들러 - 네비게이션 -
actions-handlers-state.md 액션 핸들러 - 상태 관리 -
actions-handlers-ui.md 액션 핸들러 - UI 인터랙션 -
actions-handlers.md 액션 핸들러 - 핸들러별 상세 사용법 navigate: 페이지 이동 (path, query, mergeQuery 옵션)
actions.md 액션 핸들러 가이드 구조: type 또는 event(이벤트), handler(핸들러명), params(옵션)
auth-system.md 인증 시스템 (AuthManager) AuthManager: 싱글톤 인증 상태 관리 클래스
component-props-composite.md 컴포넌트 Props 레퍼런스 - Composite FileUploader: autoUpload, uploadTriggerEvent, imageCompre...
component-props.md 컴포넌트 Props 레퍼런스 -
components-advanced.md 컴포넌트 고급 기능 -
components-patterns.md 컴포넌트 패턴 및 다국어 -
components-types.md 컴포넌트 타입별 개발 규칙 -
components.md 컴포넌트 개발 규칙 HTML 태그 직접 사용 금지 (
→ Div, → Button)
dark-mode.md 다크 모드 지원 (engine-v1.1.0+) Tailwind dark: variant 사용 (예: bg-white dark:bg-gray-800)
data-binding-i18n.md 데이터 바인딩 - 다국어 처리 -
data-binding.md 데이터 바인딩 및 표현식 API 데이터: {{user.name}}, URL 파라미터: {{route.id}}
data-sources-advanced.md 데이터 소스 - 고급 기능 -
data-sources.md 데이터 소스 (Data Sources) data_sources 배열에 API 정의: id, endpoint, method
editors.md 에디터 컴포넌트 가이드 HtmlEditor: HTML/텍스트 편집, 게시판/상품 설명 등 사용
g7core-api-advanced.md G7Core 전역 API 레퍼런스 - 고급 -
g7core-api.md G7Core 전역 API 레퍼런스 G7Core.state: get/set/subscribe 전역 상태 관리
g7core-helpers.md G7Core 헬퍼 API -
identity-guard-interceptor.md IdentityGuardInterceptor — 코어 본인인증 인터셉터 레퍼런스 ActionDispatcher.handleApiCall 응답 후처리에서 isIdentityRequire...
identity-verification-ui.md 본인인증(IDV) 공통 UI 가이드 모든 IDV 강제 지점은 동일한 428 응답 형식을 공유 (코어 9 + 게시판 4 + 이커머스 4 + N)
layout-json-components-loading.md 레이아웃 JSON - 데이터 로딩 및 생명주기 -
layout-json-components-rendering.md 레이아웃 JSON - 조건부/반복 렌더링 -
layout-json-components-slots.md 레이아웃 JSON - 슬롯 시스템 -
layout-json-components.md 레이아웃 JSON - 컴포넌트 (반복 렌더링, Blur, 생명주기, 슬롯) if: 조건부 렌더링 (type: "conditional" 사용 금지!)
layout-json-features-actions.md 레이아웃 JSON - 초기화, 모달, 액션, 스크립트 -
layout-json-features-error.md 레이아웃 JSON - 에러 핸들링 -
layout-json-features-styling.md 레이아웃 JSON - 스타일 및 계산된 값 -
layout-json-features.md 레이아웃 JSON - 기능 (에러 핸들링, 초기화, 모달, 액션) classMap: 조건부 CSS 클래스 (key → variants 매핑)
layout-json-inheritance.md 레이아웃 JSON - 상속 (Extends, Partial, 병합) extends: 베이스 레이아웃 상속 (type: "slot" 위치에 삽입)
layout-json.md 레이아웃 JSON 스키마 HTML 태그 직접 사용 금지 → 기본 컴포넌트 사용 (Div, Button, Span)
layout-testing.md 그누보드7 레이아웃 파일 렌더링 테스트 가이드 createLayoutTest()로 테스트 헬퍼 생성, mockApi()로 API 응답 모킹
modal-usage.md Modal 컴포넌트 사용 가이드 modals 섹션 모달은 openModal 핸들러로 열고, closeModal 핸들러로 닫음
responsive-layout.md 반응형 레이아웃 개발 (engine-v1.1.0+) responsive 속성: 컴포넌트 레벨 breakpoint 오버라이드 (권장)
security.md 보안 및 검증 레이아웃 JSON: FormRequest + Custom Rule 10종 검증 (서버 사전 차단)
state-management-advanced.md 상태 관리 - 고급 기능 -
state-management-forms.md 상태 관리 - 폼 자동 바인딩 및 setState -
state-management.md 전역 상태 관리 전역 상태: _global.속성명 (앱 전체 공유, 페이지 이동 시 유지)
tailwind-safelist.md Tailwind Safelist 가이드 Tailwind는 빌드 시 사용된 클래스만 CSS에 포함
template-development.md 템플릿 개발 가이드라인 디렉토리: templates/[vendor-template]/ (예: sirsoft-admin_basic)
template-handlers.md 템플릿 전용 핸들러 setLocale: 앱 언어 변경 — 엔진 빌트인 (ActionDispatcher)
components.md sirsoft-admin_basic 컴포넌트 Basic 37개: HTML 래핑 (Div, Button, Input, Select, Form, A, ...
handlers.md sirsoft-admin_basic 핸들러 setLocale: 앱 언어 변경 (locale 파라미터)
layouts.md sirsoft-admin_basic 레이아웃 베이스: _admin_base.json (사이드바 + 헤더 + 콘텐츠 슬롯)
components.md sirsoft-basic 컴포넌트 Basic 26개: HTML 래핑 (Div, Button, Input, Select, Form, A, ...
handlers.md sirsoft-basic 핸들러 setTheme/initTheme: 다크/라이트 모드 전환 (admin과 동일 키 공유)
layouts.md sirsoft-basic 레이아웃 베이스: _user_base.json (헤더 + 푸터 + 모바일 네비 + 콘텐츠 슬롯)

확장 시스템 extension/ (30개)

문서 설명 TL;DR 핵심
cache-driver.md 캐시 드라이버 시스템 (CacheInterface) 모든 캐시 저장은 CacheInterface 사용 (Cache:: 직접 호출 금지)
changelog-rules.md Changelog 규칙 (Changelog Rules) 확장/코어 버전 업 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가)
editor-spec.md 편집기 스펙 (editor-spec.json) editor-spec.json = 편집기 팔레트/스타일 컨트롤/중첩 규칙/샘플 데이터/레시피의 선언 (...
extension-manager.md ExtensionManager (확장 관리자) composer.json 수정 없음 - 런타임 오토로드 방식 사용
extension-update-system.md 확장 업데이트 시스템 (Extension Update System) 업데이트 감지 우선순위: GitHub > _bundled (2단계, _pending 미참여)
hooks.md 훅 시스템 (Hook System) Action 훅: doAction() - 부가 작업 (로그, 알림, 캐시)
language-packs.md 언어팩 시스템 (Language Packs) 코어/번들 확장의 lang/{ko,en}/ 는 가상 보호 행으로 자동 노출 (DB 없이 항상 activ...
layout-extensions.md 레이아웃 확장 시스템 (Layout Extensions) -
menus.md 메뉴 시스템 구조: User → Role → role_menus 피벗 → Menu
module-assets.md 모듈 프론트엔드 에셋 시스템 module.json에 에셋 매니페스트 정의 (js, css, loading strategy)
module-basics.md 모듈 개발 기초 디렉토리: vendor-module (예: sirsoft-ecommerce)
module-commands.md 모듈 Artisan 커맨드 목록: php artisan module:list
module-i18n.md 모듈 다국어 시스템 백엔드: /lang/{locale}/*.php → __('vendor-module::key')
module-identity-settings.md 모듈/플러그인 본인인증(IDV) 설정 통합 가이드 정책/목적/메시지: module.php::getIdentity{Policies,Purposes,Mess...
module-layouts.md 모듈 레이아웃 시스템 위치: modules/_bundled/vendor-module/resources/layouts/admi...
module-routing.md 모듈 라우트 규칙 URL prefix 자동: /api/modules/[vendor-module]/...
module-settings.md 모듈 환경설정 시스템 개발 가이드 -
permissions.md 권한 시스템 구조: User → Role → Permission (기능 레벨)
plugin-development.md 플러그인 개발 가이드 디렉토리: plugins/vendor-plugin (예: sirsoft-payment)
sample-extensions.md 학습용 샘플 확장 (Sample Extensions) 샘플 확장 4종: gnuboard7-hello_module / _plugin / _admin_templ...
storage-driver.md 스토리지 드라이버 시스템 (StorageInterface) 모든 파일 저장은 StorageInterface 사용 (Storage::disk() 직접 호출 금지)
template-basics.md 템플릿 시스템 기초 타입: Admin (관리자용), User (일반사용자용)
template-caching.md 템플릿 캐싱 전략 -
template-commands.md 템플릿 Artisan 커맨드 목록: php artisan template:list
template-idv-bootstrap.md 템플릿 IDV launcher 등록 가이드 템플릿 부트스트랩(initTemplate)에서 window.G7Core.identity.setLaunc...
template-routing.md 템플릿 라우트/언어 파일 규칙 -
template-security.md 템플릿 보안 정책 -
template-workflow.md 템플릿 개발 워크플로우 필수 파일: template.json, routes.json, _base.json, errors/{40...
upgrade-step-guide.md 업그레이드 스텝 작성 가이드 (Upgrade Step Guide) upgrade step 이 실행되는 환경은 경로에 따라 다르다 — 섹션 9 "업그레이드 경로" 먼저 읽기
vendor-bundle.md Vendor 번들 시스템 (Vendor Bundle System) -

공통 (5개)

문서 설명 TL;DR 핵심
cheatsheet.md 그누보드7 자주 쓰는 명령어 치트시트 _bundled에서 레이아웃 JSON만 수정 → 확장 업데이트(--force)만 실행 (빌드 불필요)
database-guide.md 그누보드7 데이터베이스 개발 가이드 마이그레이션: 한국어 comment 필수, down() 구현 필수
requirements.md 그누보드7 시스템 요구사항 (System Requirements) PHP 8.2+ 필수
testing-guide.md 그누보드7 테스트 가이드 테스트 통과 = 작업 완료 (작성만으로 불충분!)
e2e-testing.md 그누보드7 Playwright E2E 테스트 가이드 -

API 레퍼런스 진입점

엔드포인트별 요청 파라미터·응답 필드·요청/응답 예시. 공통 규약(Bearer 토큰 인증, 응답 봉투, 페이지네이션, 401/403/422/428)은 진입점 문서 상단에 정리되어 있다.

대상 진입점 문서/엔드포인트
코어 docs/backend/api/README.md 35 / 291

확장 API 레퍼런스 (12개 확장, 자동 스캔)

각 확장이 소유하는 API 문서 목차. php artisan api:docgen 이 생성하며, 이 표는 {modules,plugins}/_bundled/*/docs/api/README.md 를 패턴 스캔해 자동 편입된다(확장명 하드코딩 없음).

확장 유형 API 문서 목차 문서/엔드포인트
gnuboard7-hello_module 모듈 docs/api/ 1 / 2
sirsoft-board 모듈 docs/api/ 10 / 80
sirsoft-ecommerce 모듈 docs/api/ 33 / 231
sirsoft-page 모듈 docs/api/ 2 / 17
sirsoft-ckeditor5 플러그인 docs/api/ 2 / 2
sirsoft-gdpr 플러그인 docs/api/ 4 / 15
sirsoft-marketing 플러그인 docs/api/ 2 / 2
sirsoft-pay_kginicis 플러그인 docs/api/ 5 / 22
sirsoft-pay_nhnkcp 플러그인 docs/api/ 0 / 0
sirsoft-pay_nicepayments 플러그인 docs/api/ 0 / 0
sirsoft-verification_kginicis 플러그인 docs/api/ 1 / 1
sirsoft-verification_nhnkcp 플러그인 docs/api/ 1 / 1

프로젝트 개요

프로젝트명: 그누보드7 목적: 오픈소스 CMS 플랫폼 설계 원칙: 코어 수정 최소화, 모듈화, 플러그인 시스템, 템플릿 시스템, 동적 로딩


버전 동기화 의무

코어 또는 번들 확장의 공개 표면을 수정할 때, 그 변경의 영향 범위에 있는 다른 확장의 버전 제약(g7_version, dependencies.{modules|plugins})을 함께 갱신한다.

① 코어 → 확장 동기화 (requires.g7_version)

  • 트리거: 코어 공개 확장 표면(app/Extension/Abstract*, HookManager, ExtensionManager, ModuleManager, PluginManager, TemplateManager, app/Contracts/Extension/**, app/Extension/Helpers/**, app/Seo/Contracts/**, app/ActivityLog/** 공개 API, 루트 CHANGELOG.md Added/Changed/Removed) 수정
  • 조치: 영향 받는 번들 확장의 g7_version 상향 + 각 확장 CHANGELOG 에 변경 기재

② 확장 → 확장 동기화 (dependencies.{modules|plugins})

  • 트리거: 번들 모듈/플러그인의 공개 Service/Contract/Repository/Model/Route, 발행 훅·이벤트, CHANGELOG 수정
  • 조치: 그 확장에 의존하는 다른 번들 확장 전수 스캔 → 최소 버전 제약 상향 여부 판정

판정 순서

  1. 기존 소비자 API 시그니처/동작을 건드렸는가 → 소비 확장 최소 버전 상향
  2. 새 공개 API 가 도입되었는가 → 후보 확장 전수 스캔 후 검토
  3. 의존 관계 B 의 공개 API 가 변경되었는가 → A 의 dependencies.B 상향
  4. 동기화 대상이 없다면 그 근거("순수 내부 리팩토링" 등)를 변경 이력에 기록

상세: changelog-rules.md "코어 버전 제약 정책"


CRITICAL RULES - 절대 금지 패턴 (DO NOT)

API/핸들러 호출

금지 올바른 사용
G7Core.actions.execute G7Core.dispatch
G7Core.api.call G7Core.dispatch({ handler: 'apiCall', ... })
handler: "api" handler: "apiCall"
handler: "nav" handler: "navigate"
handler: "setLocalState" handler: "setState" + target: "local"
navigate + replace: true (URL만 변경 시) handler: "replaceUrl"
navigate params.path: "back" (동작 키워드로 착각) handler: "navigateBack" — path 는 주소로 해석되어 조용히 /back 으로 이동한다
navigate params.url / href / to 로 목적지 전달 params.path (또는 액션 target) — 엔진은 이 둘만 읽는다. 다른 이름은 무시되어 목적지가 undefined 가 되고, 예외도 404 도 없이 버튼만 동작하지 않는다
apiCall params.target (params 내부) target 은 액션 top-level. params 내부 위치 시 URL 미해석
apiCall params.onSuccess / params.onError (params 내부) 액션 top-level. params 내부면 무시됨
refetchDataSource params.id params.dataSourceId 사용
handler: "showToast" handler: "toast"
모달 안에서 부모 _local.* 참조 데이터소스 응답 필드 또는 _global 사용 (모달은 별도 컨텍스트)

데이터 바인딩

금지 올바른 사용
{{products.data}} {{products?.data?.data}} (배열 경로 확인)
{{value}} {{value ?? ''}} (fallback 필수)
{{error.data}} {{error.errors}} (API 응답 구조)
{{error.data?.errors ?? {}}} {{error.errors}} ({}}} 파서 모호성 회피)
$value (이벤트 값) $event.target.value
{{props.xxx}} (Partial) data_sources ID 직접 참조
{{$response.xxx}} (onSuccess) {{response.xxx}} ($ 접두사 없음)

iteration/반복 렌더링

금지 올바른 사용
"item", "index" "item_var", "index_var"
iteration 내 if 순서 무시 if가 iteration보다 먼저 평가됨

컴포넌트 Props

금지 올바른 사용
Icon className="w-4 h-4" className="text-base" (아래 등가표) 또는 size prop
Select valueKey/labelKey computed로 { value, label } 변환
Form 내 Button type 없음 type="button" 명시 (submit 방지)
options={{options}} options={{options ?? []}} (fallback)

Icon 은 <i> 글리프라 박스 크기가 곧 font-size 다. w-N h-N 은 박스만 정하고 글리프는 부모 font-size 를 상속하므로 어긋난다. 기존 w-N h-N 을 옮길 때는 아래 등가표를 쓴다 (Chrome 실측).

w-N h-N px 등가 className
w-3 h-3 12 text-xs
w-3.5 h-3.5 14 text-sm
w-4 h-4 16 text-base
w-5 h-5 20 text-xl
w-6 h-6 24 text-2xl
w-12 h-12 48 text-5xl

size prop 은 Font Awesome fa-* 클래스로 매핑되며 등가가 아니다 — size="sm" → fa-sm → font-size: 0.875em(상대값) + line-height 붕괴로 16px 이 12.25×0.88px 이 된다. 새 아이콘에는 써도 되지만, 기존 w-N h-N 의 치환용으로는 쓰지 않는다.

상태 관리

금지 올바른 사용
스냅샷 기반 setState 함수형 업데이트 또는 stateRef.current
closeModal 후 setState setState 후 closeModal (순서 중요)
sortable 내 폼 자동바인딩 parentFormContextProp={undefined}
await 후 캡처된 상태 사용 await 후 G7Core.state.getLocal() 재조회
setState params 키에 {{}} 사용 키는 정적 경로만, 배열 조작은 .map()/.filter()

핸들러 정의

금지 올바른 사용
{{handler()}} (표현식에서 호출) actions: [{ handler: "xxx" }]

globalHeaders 사용 (engine-v1.16.0+)

금지 올바른 사용
"globalHeaders": { "X-Key": "value" } "globalHeaders": [{ "pattern": "*", "headers": {...} }]
모든 API에 개별 headers 설정 globalHeaders로 공통 헤더 정의
pattern 없이 헤더 정의 pattern 필수 (*, /api/shop/* 등)

인증/리다이렉트 규칙 (engine-v1.47.0+)

금지 올바른 사용
모듈/플러그인에서 AuthManager.updateConfig() 호출 템플릿 부트스트랩(initTemplate)에서만 호출
AuthManager.updateConfig({ loginPath: 'https://...' }) (외부 origin) loginPath 는 / 로 시작하는 동일 origin path-only
AuthManager.updateConfig({ loginPath: '//evil.com/...' }) (protocol-relative) // 시작 금지 (open redirect 방지)
401 에러 페이지(errors/401.json)에서 직접 로그인 리다이렉트 구현 코어 TemplateApp.showRouteError 가드에 위임 (자동 처리)

정적 확장자 라우트 / 자산 URL 생성

금지 올바른 사용
Route::get('{id}/routes.json', ...) (.js/.css/.json/.map 단일 등록) Route::dualSuffix('{id}/routes', 'json', ...) — 확장자 형태 + 확장자 없는 형태 동시 등록
Route::get('bundle.js', ...) (접미사가 종류를 구분해 제거 불가) Route::dualSuffixSegment('bundle', 'js', ...) (bundle.js + bundle/js)
Route::get('assets/{id}/{path}', ...) (와일드카드 자산) Route::dualAsset('assets/{id}', ...) (.../{path} + ?file= 쿼리)
서버에서 '/api/templates/assets/'.$id.'/'.$path 문자열 조립 App\Support\AssetUrl::templateAsset($id, $path)
프론트에서 `/api/templates/${id}/routes.json` 템플릿 리터럴 조립 resources/js/core/support/assetUrl.ts 의 suffixed() / templateAsset() 등

정규식 location 은 프리픽스 location 보다 먼저 매칭되므로, 정적 최적화 블록(location ~* \.(js|css|json)$)이 있는 서버에서는 확장자 붙은 동적 응답이 try_files ... /index.php 폴백 기회 없이 404 가 된다. 서버측 AssetUrl 과 프론트측 assetUrl.ts 는 동일 규칙을 공유하므로 한쪽만 바꾸면 그 자산만 404 가 된다. 상세: routing.md "정적 확장자로 끝나는 동적 엔드포인트", api/README.md "자산 URL 이중 모드".

목록 컨텍스트 왕복 (list context round-trip)

페이지네이션 목록 화면과 그에 딸린 상세·형제 상세·작성/수정 폼·확인 모달은 하나의 목록 클러스터다. 이 클러스터 안에서의 이동은 URL 목록 상태(page/search/category/filters[*]/정렬/per_page)를 손실 없이 보존해야 한다.

금지 올바른 사용
클러스터 내 navigate 에 mergeQuery 누락 "params": { "path": "…", "mergeQuery": true, "query": {} }
이전글/다음글 등 형제 상세 이동만 규약에서 누락 목록 진입 / 목록 복귀 / 형제 이동 / 폼 취소 / 삭제 후 복귀 전 leg 동일 적용
현재 값을 그대로 다시 넘기는 키 열거 ({"del": "{{query.del ?? ''}}"}) mergeQuery 가 이미 전부 나른다 — 열거는 중복이자 누락 위험
덮어쓸 키만 남기지 않고 필터 키 전부 재열거 값을 바꿔야 하는 키만 남긴다 (페이지 되돌림은 {"page": ""})
새로고침 버튼에 mergeQuery: false 새로고침은 보던 목록을 다시 부르는 것 — 병합 유지
mergeQuery 를 표현식으로 분기 ("{{cond}}") boolean 리터럴 고정 — 분기마다 보존 여부가 갈리면 한쪽이 조용히 상태를 떨군다
"path": "/board/{slug}/write?parent_id={{id}}" (인라인 쿼리스트링) 인라인 쿼리는 병합 시 버려진다 → query 객체로 옮긴다
mergeQuery: true + query 키 생략 의도를 드러내도록 "query": {} 를 함께 둔다
"query": [] (배열 리터럴) "query": {} — 동작은 같아 조용히 통과하지만, 나중에 덮어쓸 키를 넣으면 그 값이 버려진다
목적지가 표현식이라 판정 불가한 이동을 무표시로 둠 ("{{_global.shopBase}}/products") 클러스터 내 이동이면 mergeQuery: true, 밖으로 나가는 이동이면 예외 주석으로 의도를 명시
의도적 리셋(검색·필터 초기화 / 탭 전환 / 프리셋 적용)에 mergeQuery: true 리셋은 병합하지 않는다 — 병합하면 초기화 버튼이 아무 일도 하지 않는다
탭 전환(onTabChange)이나 겹치지 않는 다른 목록으로의 이동에 mergeQuery: true 목록 정체성이 다르면 승계하지 않는다 — 남의 검색어·페이지가 얹혀 빈 화면이 열린다
면제 주석은 "병합하지 않는다" 인데 코드는 mergeQuery: true 주석과 코드를 일치시킨다 (주석은 사실이 아니라 선언일 뿐)
검색 실행·페이지 이동 액션에서 query 키를 비움 값을 바꾸는 액션은 그 값을 직접 넘긴다 ({"page": "{{$args[0]}}"}) — 병합만으로는 새 값이 전달되지 않는다
path 없이 query 만 바꾸는 액션에 mergeQuery 누락 (탭 전환 {"tab": …}, 항목 선택 {"id": …, "mode": "view"}) path 생략은 "현재 주소에 작용" 이라 목록 화면 자신이 대상 — mergeQuery: true 없으면 지금 걸린 목록 상태가 통째로 날아간다

의도적 리셋(검색 초기화 / 필터 초기화 / 탭 전환 / 프리셋 적용 / 다른 목록으로의 이동)은 예외다. 그 경우 액션 노드 comment 에 audit:allow layout-list-context-navigate-merge-query <사유> 를 남겨 의도를 코드에 기록한다. 상세: actions-handlers-navigation.md "목록 컨텍스트 왕복 규약"

중첩 리소스 스코프 / 계층 무결성

금지 올바른 사용
중첩 라우트의 상위 리소스 ID 를 받아만 두고 조회에 미반영 Repository where 절에 상위 스코프 반영(SSoT) + Service 가 상위 ID 전달 → 교차 접근 시 404
$request->except(...) / ->all() 결과를 Service 쓰기 메서드로 전달 $request->validated() 기준 (FormRequest 미정의 필드가 $fillable 로 새는 것 차단)
요청 배열 항목의 Rule::exists 에 상위 스코프 미부착 Rule::exists(Model::class,'id')->where('order_id', $order->id) → 422
수정/순서변경 FormRequest 의 parent_id 에 Rule::exists 만 부착 자손 전체를 검사하는 순환 방지 Rule 부착 (자기참조만 막는 Rule 은 A→B→A 통과)
같은 리소스의 두 엔드포인트가 서로 다른 검증 강도 부모 변경 경로 전부 동일 강도 — 약한 쪽이 우회로가 된다
설정값이 정하는 한계를 Service 에서 리터럴로 재클램프 Service 는 계산만, 상한 검증은 Rule 단일 책임 (이중 클램프 시 깊이 제한이 통째로 무력화)
계층 재귀(path/depth 재계산)에 방문 ID 가드 없음 방문 집합으로 유한 종료 — 검증 우회 경로/오염 데이터에서도 무한 루프 금지

상세: validation.md "계층 리소스 순환 참조" / "배열 항목의 상위 스코프", service-repository.md "중첩 리소스 스코프" / "설정 기반 한계값"

Listener 데이터 접근

금지 올바른 사용
Listener 에서 Model::query/find/where/create 직접 호출 Repository 인터페이스 주입 후 위임
Listener 에서 DB::table()->update(...) Repository 의 도메인 의도 메서드 (recalculate*/anonymize* 등)
Listener 에서 $row->save() / saveQuietly() / delete() Repository 의 update/save/delete 호출
Listener 생성자에 구체 Repository 직접 주입 Repository Interface 주입
Listener 에서 request() / $_POST 직접 접근 Service 가 검증 후 도메인 객체로 전달 받기
Filter 훅에 'type' => 'filter' 누락 type 명시 필수 (반환값 무시 회귀 차단)
Listener 가 HookListenerInterface 미구현 (auto-discovery 대상) implements + getSubscribedHooks() 정적 메서드

상세: hooks.md "Listener 데이터 접근 규정", service-repository.md


템플릿 엔진 내부 버전 (engine-v1.x.x)

코드와 문서에서 engine-v1.x.x 형태의 버전은 템플릿 엔진의 내부 개발 이력입니다. 그누보드7 공식 버전(config/app.php)과는 무관합니다.

  • CHANGELOG: resources/js/core/template-engine/CHANGELOG.md
  • 표기법: engine-v1.X.Y (engine- 접두사 필수, v1.X.Y 단독 사용 금지)
  • 사용처: @since JSDoc, 인라인 주석, 규정 문서

엔진 CHANGELOG 반영 규칙

트리거 필수 작업
resources/js/core/template-engine/** 기능 추가 시 마이너 버전 업 (engine-v1.X+1.0) + CHANGELOG 기록
resources/js/core/template-engine/** 버그 수정 시 패치 버전 업 (engine-v1.X.Y+1) + CHANGELOG 기록
resources/js/core/*.ts (TemplateApp, G7CoreGlobals 등) 버그 수정 시 CHANGELOG [Unreleased] 또는 해당 버전에 기록
코드에 @since 추가 시 CHANGELOG 해당 버전에 항목 추가
규정 문서에 엔진 버전 표기 시 engine-v1.X.Y 형식 사용

엔진 CHANGELOG 대상 범위

엔진 CHANGELOG에 기록하는 대상은 엔진 코어 코드의 변경사항입니다:

포함 (엔진 코드) 제외 (비엔진 코드)
resources/js/core/template-engine/** templates/**/src/components/** (템플릿 컴포넌트)
resources/js/core/TemplateApp.ts modules/**/resources/layouts/** (모듈 레이아웃)
resources/js/core/G7CoreGlobals.ts resources/layouts/** (코어 레이아웃 JSON)
resources/js/core/template-engine.ts docs/** (규정 문서)
resources/js/core/types/ (엔진 타입) 백엔드 PHP 코드

엔진 CHANGELOG 작성 형식

Keep a Changelog 표준:

  • ### Added — 새 기능
  • ### Fixed — 버그 수정
  • ### Changed — 기존 기능 변경
  • ### Deprecated — 곧 제거될 기능
  • ### Removed — 제거된 기능

CHANGELOG 항목 작성 규칙

엔진 코드 수정 후 CHANGELOG 미기록 시 작업 미완료로 간주합니다.

항목 형식: - 수정 내용 요약 (수정 파일명)

# 좋은 예
- setState dot notation 멀티 키 병합 시 이전 키 변경 유실 방지 (ActionDispatcher)
- blocking 데이터소스 + errorHandling 데드락 — fallback 동기 적용, 에러핸들러 비동기 실행

# 나쁜 예
- 버그 수정                      ← 무엇을 수정했는지 불명확
- ActionDispatcher.ts 수정       ← 파일명만으로는 변경 내용 파악 불가

패치 버전 항목 형식: - (engine-v1.X.Y) 수정 내용 (파일명)

- (engine-v1.17.5) dataKey 자동 바인딩 컴포넌트에서 setState 호출 시 stale 값 방지

버전 결정 기준:

상황 버전 처리
새 기능 추가 (핸들러, 속성, API) 마이너 버전 업: engine-v1.X+1.0
기존 기능 버그 수정 패치 버전 업: engine-v1.X.Y+1
특정 버전에 귀속 불가한 수정 [Unreleased] 섹션에 기록
릴리스 시 [Unreleased] → [engine-v1.X.0]으로 이동

대규모 Fixed 섹션 카테고리 분류 (항목 10개 초과 시):

### Fixed

#### 상태 동기화
- 항목 1
- 항목 2

#### 캐시
- 항목 3

공개 CHANGELOG 작성 규칙

코어(CHANGELOG.md) 및 확장(modules/*/CHANGELOG.md, plugins/*/CHANGELOG.md, templates/*/CHANGELOG.md)의 릴리즈 CHANGELOG 작성 규칙입니다.

톤과 표현

  • 사용자/개발자가 읽는 문서이므로 사용자 관점으로 작성
  • "~할 수 있도록 개선", "~하도록 변경", "~문제 수정" 톤 사용
  • 각 불릿은 1~2줄로 간결하게
  • 내부 구현 상세(클래스명, 파일 경로, 테스트 건수, 훅 체인, DI 패턴)는 포함하지 않음
  • 이슈 번호(#123)는 포함하지 않음

포함/제외 대상

포함 제외
사용자에게 보이는 기능 추가/변경 내부 파일 경로, 클래스/메서드명
API 변경 (엔드포인트, 파라미터) 테스트 건수/파일명
기존 기능의 버그 수정 리팩토링 세부사항
성능 개선 (체감 가능한 것) 내부 규정/문서 변경
Breaking Change 내부 작업 이슈 번호 단독 (예: refs #347)
엔진 버전 참조 (engine-v1.X.Y) 코드 패턴 설명
공개 제보자 attribution ((#N @login 님께서 제보해주셨습니다.)) —
KISA 등 공식 보안 채널 ((KISA 측에서 제보해주셨습니다 — KVE-XXXX-XXXXX)) —

공개 제보자 attribution

공개 저장소(GitHub) 이슈로 제보·건의된 항목이 출시 CHANGELOG 에 반영되면, 항목 끝에 공개 이슈 번호와 제보자 GitHub 핸들 멘션을 부착합니다.

  • 형식: - (본문) (#N @login 님께서 제보해주셨습니다.) 또는 ... 건의해주셨습니다.
  • 톤: 버그 리포트는 "제보", 제안형 개선 요청은 "건의"
  • 다중 매칭: (#A @x, #B @y 님께서 제보해주셨습니다.) / 혼재: (#A @x 님께서 제보해주시고, #B @y 님께서 건의해주셨습니다.)
  • KISA 등 공식 보안 채널: GitHub 멘션 없이 텍스트 "KISA 측에서" + 공개 가능한 식별자만

신규 기능의 버그 수정 제외 규칙

해당 릴리즈에서 새로 도입한 기능의 개발 중 버그 수정은 Fixed에 기록하지 않습니다. 사용자 관점에서 그 기능은 해당 릴리즈에서 처음 제공되므로, "추가했다가 고쳤다"는 내부 개발 이력일 뿐입니다.

판단 기준: 해당 버그가 이전 릴리즈에도 존재했던 기능에서 발생한 것인지 확인

기능 그룹핑

Added/Changed/Fixed 내 항목이 10개를 초과하면 #### 서브 헤딩으로 기능 단위 분류합니다.

Keep a Changelog 형식

  • ## [버전] - YYYY-MM-DD 헤더 필수
  • ### Added / ### Changed / ### Fixed / ### Removed 카테고리 사용
  • 최신 버전이 파일 상단

레이아웃 JSON 구현 규칙

1. 새로운 기능 사용 전 → 반드시 해당 규정 문서에서 지원 여부 확인
2. 지원되지 않는 문법 사용 금지 → 추측/가정으로 구현하지 않음
3. 불확실한 경우 → 기존 레이아웃 패턴 참조
4. 규정 문서에 없는 기능 → 절대 사용 금지

레이아웃 작성 체크리스트

□ 레이아웃 구조가 layout-json.md 스키마와 일치하는가?
□ 사용할 컴포넌트가 components.md에 정의되어 있는가?
□ 컴포넌트 props가 component-props.md에 정의된 것만 사용하는가?
□ 사용할 핸들러가 actions.md에 정의되어 있는가?
□ 핸들러의 params 구조가 actions-handlers.md와 일치하는가?
□ 데이터 바인딩 문법이 data-binding.md에 정의된 형식인가?
□ 다크 모드 클래스가 dark-mode.md 규칙을 따르는가?
□ 기존 유사 레이아웃에서 동일 패턴이 사용되고 있는가?

주의 사항

필수: 규정 문서에 정의된 핸들러/props/바인딩 문법만 사용 (API 응답 구조도 확인 후 바인딩)
필수: Partial은 컴포넌트 치환만 수행 (computed, data_sources, modals, state 미지원)
필수: data_sources ID 고유성 유지, 조건부 렌더링은 if 속성만 사용 (type: "conditional" 미지원)

테스트 프로토콜

기능 구현 = 테스트 코드 작성 필수
신규 기능 / 도메인 표면 변경 = 시나리오 매니페스트(tests/scenarios/<feature>.yaml) 작성 의무 — 입력 axis cross product + 후속 효과 체인 전수 커버
테스트 통과 = 작업 완료 (작성만으로 불충분!)
기존 테스트 있음 → 변경사항 반영하여 수정 후 실행
기능 구현 시 관련된 모든 계층(백엔드+프론트엔드+레이아웃 렌더링) 테스트 필수
주의: 모듈/플러그인 프론트엔드 테스트는 독립 vitest.config.ts 사용 (루트 config 포함 금지)
필수: 도메인 매트릭스 = 테스트 위치/형식 가이드. 입력 조합 망라 의무는 시나리오 매니페스트가 SSoT
필수: 버그 수정은 먼저 실패하는 회귀 테스트 → fail 확인 → 수정 → green 4단계
필수: 테스트 중 발견한 무관 에러도 같은 세션에서 처리 (stale test 또는 로직 수정)
필수: 릴리스 전 composer test-smoke 통과

상세: docs/testing-guide.md — 기능 단위 시나리오 매트릭스, 도메인 매트릭스, Pre-release Smoke Suite, 회귀 테스트 4단계, 무관 에러 처리

그누보드7 레이아웃 렌더링 테스트

그누보드7 레이아웃 테스트는 브라우저 기반 E2E가 아님!
Vitest + createLayoutTest() 유틸리티 사용 → 추가 인프라 불필요
"인프라 부족" 이유로 레이아웃 테스트 건너뛰기 절대 금지
레이아웃 테스트는 해당 레이아웃이 속한 확장 디렉토리에 작성
모듈 테스트: modules/_bundled/{id}/resources/js/__tests__/layouts/
템플릿 테스트: templates/_bundled/{id}/__tests__/layouts/
코어 테스트: resources/js/core/template-engine/__tests__/layouts/
특성 설명
테스트 환경 Vitest (jsdom) - 브라우저 불필요
렌더링 DynamicRenderer를 통한 실제 React 렌더링
유틸리티 createLayoutTest() - 이미 구축됨
API 모킹 mockApi() - fetch 자동 모킹
상태 관리 getState(), setState() - 즉시 사용 가능
액션 트리거 triggerAction() - 핸들러 실행
import { createLayoutTest, screen } from '../utils/layoutTestUtils';

const testUtils = createLayoutTest(layoutJson);
testUtils.mockApi('products', { response: { data: [] } });
await testUtils.render();
expect(screen.getByTestId('element')).toBeInTheDocument();
testUtils.cleanup();

테스트 작성 트리거

수정 대상 테스트 파일 위치 테스트 유형
app/Models/*.php tests/Unit/Models/*Test.php 모델 메서드, 관계, 스코프
app/Services/*.php tests/Unit/Services/*Test.php 비즈니스 로직
app/Enums/*.php tests/Unit/Enums/*Test.php Enum 메서드
app/Http/Controllers/**/*.php tests/Feature/**/*Test.php API 엔드포인트
database/migrations/*.php 해당 모델/서비스 테스트에서 검증 스키마 변경
templates/**/src/components/**/*.tsx templates/**/__tests__/*.test.tsx 컴포넌트
resources/js/core/**/*.ts resources/js/core/__tests__/*.test.ts 템플릿 엔진
resources/layouts/**/*.json resources/js/core/template-engine/__tests__/layouts/*.test.tsx 코어 레이아웃 렌더링
modules/**/resources/layouts/**/*.json modules/_bundled/{id}/resources/js/__tests__/layouts/*.test.tsx 모듈 레이아웃 렌더링
templates/**/layouts/**/*.json templates/_bundled/{id}/__tests__/layouts/*.test.tsx 템플릿 레이아웃 렌더링

기능 구현 시 전 계층 테스트

작업 유형 백엔드 (PHPUnit) 프론트엔드 (Vitest) 레이아웃 렌더링 (Vitest)
새 화면 구현 API 엔드포인트 테스트 컴포넌트 테스트 레이아웃 JSON 렌더링 테스트
기존 화면 수정 변경된 API 테스트 변경된 컴포넌트 테스트 레이아웃 렌더링 회귀 테스트
데이터 흐름 변경 Service/Repository 테스트 상태 관리 테스트 데이터 바인딩 렌더링 테스트

Windows 환경 테스트 규칙

프론트엔드 (npm/Vitest) → PowerShell 래퍼 필수
백엔드 (PHPUnit/Laravel) → Bash 직접 실행

프론트엔드 (템플릿 디렉토리에서 실행 권장):

# 템플릿 디렉토리에서 실행 (해당 템플릿만 테스트)
cd templates/sirsoft-admin_basic
powershell -Command "npm run test:run"              # 전체
powershell -Command "npm run test:run -- DataGrid"  # 특정 테스트

# 루트에서 실행 (모든 테스트)
powershell -Command "npm run test:run"
powershell -Command "npm run test:run -- template-engine"  # 코어 테스트

백엔드:

php artisan test
php artisan test --filter=TestName

_bundled 확장 테스트 (활성 디렉토리 복사 불필요):

# _bundled 모듈 테스트 직접 실행
php vendor/bin/phpunit modules/_bundled/sirsoft-ecommerce/tests
php vendor/bin/phpunit --filter=ShippingPolicyControllerTest modules/_bundled/sirsoft-ecommerce/tests

# _bundled 모듈 프론트엔드 테스트
cd modules/_bundled/sirsoft-ecommerce
powershell -Command "npm run test:run"

필수 준수 사항

필수: 기능 구현 시 모든 계층(백엔드+프론트엔드+레이아웃) 테스트 포함
필수: 테스트 통과 확인 후 완료 선언 (기존 테스트 유지 — 삭제/skip 금지)
필수: createLayoutTest() 유틸리티 활용 (추가 인프라 불필요)

상세: testing-guide.md | layout-testing.md


npm install 규칙

기본 npm install은 package-lock.json을 자동 수정할 수 있으므로, lock 파일 변경 의도가 없는 의존성 복구나 작업 환경 재구성에는 npm install --package-lock=false를 사용합니다.

상황 권장 명령어 비고
누락 의존성 복구 / 작업 환경 재구성 npm install --package-lock=false lock 파일 변경 없이 설치
clean install npm ci package.json과 package-lock.json이 동기화된 경우
의존성 신규 추가/업데이트 npm install <pkg> lock 변경이 작업 범위에 포함된 경우만

lock 파일 변경 의도가 없는 상황에서 npm install 단독 실행을 피합니다. module.json, plugin.json, template.json의 version을 바꾸면 해당 확장의 package.json, package-lock.json, composer.json 버전도 함께 동기화합니다. 의존성 재설치 없이 lock 파일의 version 필드만 갱신할 때는 npm install --package-lock-only를 사용합니다.


핵심 원칙

1. 동적 로딩

절대 금지: composer.json에 모듈/플러그인 하드코딩
필수: /modules와 /plugins 디렉토리 스캔으로 자동 발견

2. 코어 수정 최소화

  • 모든 확장은 모듈/플러그인으로 구현
  • 훅 시스템을 통한 기능 추가
  • 서비스 계층에서 훅 실행

3. 계층 분리

Controller → Request → Service → RepositoryInterface → Repository → Model

4. Repository 인터페이스

절대 금지: Repository 구체 클래스 직접 타입힌트
필수: Repository 인터페이스를 통한 DI
필수: CoreServiceProvider에서 인터페이스-구현체 바인딩

기술 스택

백엔드

  • PHP: 8.2+
  • Laravel: 12.x
  • 데이터베이스: MySQL 8.0
  • 인증: Laravel Sanctum 4.x
  • 테스트: PHPUnit 11.x
  • 코드 스타일: Laravel Pint (PSR-12)

아키텍처 패턴

디렉토리 구조 개요

/
├── /app                    # 코어 애플리케이션
├── /modules                # 모듈 디렉토리
│   ├── _bundled/           # 선탑재 확장 소스 (Git 추적)
│   ├── _pending/           # 외부 다운로드 대기소 (Git 제외)
│   └── vendor-module/      # 활성 설치 디렉토리 (Git 제외)
├── /plugins                # 플러그인 디렉토리 (동일 구조)
├── /templates              # 템플릿 디렉토리 (동일 구조)
├── /resources/js/core/     # 코어 렌더링 엔진
└── /public/build/          # Vite 빌드 결과

네이밍 규칙

항목 디렉토리명 네임스페이스
모듈 sirsoft-ecommerce Modules\Sirsoft\Ecommerce\
플러그인 sirsoft-payment Plugins\Sirsoft\Payment\
템플릿 sirsoft-admin_basic -

백엔드 개발 - 핵심 요약

상세: docs/backend/ | database-guide.md

절대 금지: Service 클래스에 검증 로직 구현 → FormRequest + Custom Rule 사용
절대 금지: FormRequest authorize()에서 인증/권한 로직 → permission 미들웨어 사용
필수: __() 함수를 사용한 다국어 처리
필수: 상태/타입/분류는 Enum으로 정의
절대 금지: 인증 필요 미들웨어를 append()로 전역 등록 → appendToGroup('api') 사용
절대 금지: DB CASCADE에 의존한 삭제 → Service에서 명시적 삭제 (훅/파일/로깅 보장)
절대 금지: 로케일 하드코딩 → config('app.supported_locales') 사용
필수: 마이그레이션 한국어 comment 필수, down() 구현 필수
필수: FK 컬럼의 ->comment() 는 ->constrained()/->references()/->on() 앞에 둔다 (뒤에 두면 comment 가 컬럼이 아닌 FK 정의에 부착되어 조용히 사라진다)
필수: 소스 교정만으로는 기설치본이 낫지 않는다 — 마이그레이션은 재실행되지 않으므로 업그레이드 스텝 백필을 함께 작성
주의: ResponseHelper::success($messageKey, $data) — 메시지가 첫 번째 인수

상세 규칙 (API 리소스, ServiceProvider, validation, 인증, 활동 로그 등): docs/backend/ 각 문서 참조

컨트롤러 계층

BaseApiController (최상위)
├── AdminBaseController (관리자 전용)
├── AuthBaseController (인증된 사용자)
└── PublicBaseController (공개 API)

파사드 사용

✅ use Illuminate\Support\Facades\Log; → Log::info()
❌ \Log::info(), auth()->user() 금지

프론트엔드/템플릿 시스템

상세: docs/frontend/

필수: 기본 컴포넌트만 사용 (Div, Button, H2 등 — HTML 태그 직접 사용 금지)
필수: 집합 컴포넌트 재사용 우선
필수: 다크 모드 light/dark variant 함께 지정
필수: HtmlEditor 사용 (RichTextEditor 미구현)

확장 시스템 빠른 참조

상세: docs/extension/

필수: 모든 확장 작업은 _bundled 디렉토리에서만 수행 (활성 디렉토리 직접 수정 금지)
필수: 프로덕션 반영은 update 커맨드로만 수행 (_bundled → 활성 디렉토리)
필수: 확장 코드 변경 시 manifest 버전 업 (미변경 시 업데이트 감지 불가)
필수: 버전 업 시 CHANGELOG.md 기록 — Keep a Changelog 표준 (미기록 시 버전 업 불가)
필수: StorageInterface 사용 (Storage::disk() 직접 호출 금지)
필수: ActionDispatcher 에 핸들러를 등록하는 확장은 재등록 진입점을 window 전역에 고정 이름으로 노출 — 모듈 window.__[Name].initModule, 플러그인 window.__[Name].initPlugin (미노출 시 로케일 전환 후 해당 확장 액션이 전부 무반응, 에러·토스트 없음). 진입점은 핸들러 재등록만 수행
필수: 확장 미들웨어는 getMiddleware() 로 부착 대상(targets) 명시 선언 (self-gate) — SP Kernel 미들웨어 그룹 직접 조작·라우트 파일 자기 미들웨어 FQCN 부착 금지, 무규율 전역 개입 금지
필수: 코어 레이아웃에 모듈 UI 주입은 layout_extensions만 사용
필수: 모든 확장 작업은 Artisan 커맨드로 수행

상세 규칙 (플러그인 의존성, 훅 시스템, 버전 동기화, 업그레이드 스텝 등): docs/extension/ 각 문서 참조

확장 타입 요약

타입 네이밍 네임스페이스 예시
모듈 vendor-module Modules\Vendor\Module\ sirsoft-ecommerce
플러그인 vendor-plugin Plugins\Vendor\Plugin\ sirsoft-payment
템플릿 vendor-template - sirsoft-admin_basic

한국어 사용 규칙

한국어: 사용자 대상 텍스트, 주석, 문서, 커밋 메시지, DB comment
영어: 변수명, 함수명, 클래스명
Laravel 기본 메서드 주석은 영어 유지 (up(), down() 등)

코드 품질

Laravel Pint

vendor/bin/pint --dirty

PHPDoc

/**
 * 상품을 생성합니다.
 *
 * @param array $data 상품 생성 데이터
 * @return Product 생성된 상품 모델
 * @throws \Exception 생성 실패 시
 */
public function createProduct(array $data): Product

빌드 vs 확장 업데이트

수정 파일 유형 필요한 작업
*.json (레이아웃만) {type}:update {id} --force 실행
*.tsx, *.ts + *.json {type}:build + {type}:update {id} --force
*.tsx, *.ts만 {type}:build + {type}:update {id} --force
lang-packs/_bundled/** (번들 언어팩 콘텐츠/버전) language-pack:update {id} --force (빌드 불필요)
# 확장 업데이트 (_bundled → 활성 반영)
php artisan template:update sirsoft-admin_basic --force
php artisan module:update sirsoft-ecommerce --force
php artisan plugin:update sirsoft-payment --force
php artisan language-pack:update g7-core-ja --force

번들 언어팩도 _bundled 는 배포 원본일 뿐이다. 설치본(lang-packs/{id}/)을 갱신하지 않으면 새로 추가한 번역 키가 런타임에 존재하지 않아 해당 로케일이 조용히 기준 로케일로 폴백한다.

코어 3-번들 구조 + 공유 런타임 (engine-v1.51.0+)

core:build 는 코어 프론트엔드를 3개 IIFE 번들로 빌드한다:

번들 로드 시점 vite config
template-engine.min.js 모든 페이지 (동기 <script>) vite.config.core.js
layout-editor.min.js /admin/layout-editor/* 진입 시 런타임 주입 vite.config.editor.js
devtools.min.js 디버그 모드에서만 런타임 주입 vite.config.devtools.js

lazy 번들(편집기/devtools)이 코어 런타임(DynamicRenderer·엔진 싱글톤·React Context·DevTools 코어)을 재사용할 때는 재번들하지 않고 window.G7Core.__runtime 을 빌려 쓴다 — React/컨텍스트/싱글톤 인스턴스 동일성이 강제되기 때문(사본이 둘이면 "Invalid hook call"·컨텍스트 미매칭). 메인 번들이 G7CoreGlobals 에서 공유 대상을 G7Core.__runtime 에 노출하고, lazy 번들 vite config 는 React 4종(react/react-dom/react-dom/client/react/jsx-runtime)을 external→window 로, 코어 런타임 모듈을 resolveId 플러그인으로 __runtime-shims/ 로 치환한다.

확장 번들 병합 (서버측 concat)

활성 모듈/플러그인의 프론트엔드 IIFE JS·CSS 는 타입별로 서버에서 하나의 번들로 병합해 서빙한다(/api/{modules,plugins}/bundle.{js,css}?v={version}). ExtensionBundleService 가 정렬·필터·concat·캐시를 전담하고, 프론트는 window.G7Config.bundleUrls 를 읽어 모듈 번들 → 플러그인 번들 순으로 로드한다. 병합 규율:

  • priority 순서는 선언형 — 실행 순서는 오직 manifest loading.priority 오름차순(uasort). 특정 확장 이름을 지목하는 분기를 두지 않는다.
  • IIFE 사이는 \n;\n(JS)/\n(CSS) 로 잇는다. 미사용 시 ASI 경계가 깨져 번들 전체 파싱 에러가 난다.
  • 소스맵은 prod strip, dev 는 개별 에셋 서빙 절대 URL 로 rewrite. 개별 에셋 서빙 라우트(*.map 포함)는 존치한다.
  • 번들 URL 은 반드시 same-origin(/api/...). 외부 origin/CDN·protocol-relative 는 gdpr preblocker 에 자기 차단된다.
  • 확장 에셋 절대경로는 getBuiltAssetAbsolutePaths()(=getModulePath()/getPluginPath()) 만 쓴다. base_path("modules"|"plugins") 직접 조립은 _bundled 경로 오해석 → 빈 번들.
  • concat 루프는 확장별 try/catch — 실패 확장만 skip 하고 나머지 병합을 지속한다.
  • 번들 파일명에 확장 캐시 버전을 포함({type}.{version}.{js,css}). 조합 변경 시 version bump → 새 파일명 → 자동 재생성. 구파일 GC 는 ext-bundles:cleanup + {module,plugin,template}:cache-clear 가 담당한다. prod 은 version-in-path 디스크 캐시, 비프로덕션은 매 요청 concat.

빌드 명령어 (Artisan)

# 코어 템플릿 엔진 (resources/js/core/template-engine/**)
php artisan core:build                    # 기본: 템플릿 엔진만 빌드
php artisan core:build --full             # 전체 빌드 (npm run build)
php artisan core:build --watch            # 파일 감시 모드

# 모듈 빌드 (기본: _bundled 디렉토리)
php artisan module:build sirsoft-ecommerce          # _bundled에서 빌드
php artisan module:build --all                      # 모든 _bundled 모듈 빌드
php artisan module:build sirsoft-ecommerce --watch   # 활성 디렉토리에서 watch
php artisan module:build sirsoft-ecommerce --active   # 활성 디렉토리에서 빌드

# 템플릿 빌드 (기본: _bundled 디렉토리)
php artisan template:build sirsoft-admin_basic        # _bundled에서 빌드
php artisan template:build --all                      # 모든 _bundled 템플릿 빌드
php artisan template:build sirsoft-admin_basic --watch # 활성 디렉토리에서 watch
php artisan template:build sirsoft-admin_basic --active # 활성 디렉토리에서 빌드

# 플러그인 빌드 (기본: _bundled 디렉토리)
php artisan plugin:build sirsoft-payment              # _bundled에서 빌드
php artisan plugin:build --all                        # 모든 _bundled 플러그인 빌드
php artisan plugin:build sirsoft-payment --watch       # 활성 디렉토리에서 watch
php artisan plugin:build sirsoft-payment --active       # 활성 디렉토리에서 빌드

빌드 원칙: 기본값은 _bundled 디렉토리. 빌드 결과물은 빌드 경로 내에만 남음. 활성 디렉토리 반영은 update 커맨드로만 수행. --watch 모드는 실시간 개발용으로 활성 디렉토리를 자동 사용.


확장 시스템 Artisan 명령어

# 코어 업데이트
php artisan core:check-updates                                    # 코어 업데이트 확인
php artisan core:update [--force] [--no-backup] [--no-maintenance] # 코어 업데이트 실행
php artisan core:execute-upgrade-steps --from=X.Y.Z --to=A.B.C [--force]  # 업그레이드 스텝 단독 실행 (HANDOFF 안내/수동 복구용 — 사전·사후 단계 자동 수행)

# 모듈
php artisan module:list
php artisan module:install [identifier]
php artisan module:activate [identifier]
php artisan module:deactivate [identifier]
php artisan module:uninstall [identifier]
php artisan module:composer-install [identifier?] [--all]
php artisan module:cache-clear [identifier?]
php artisan module:seed [identifier] [--sample] [--count=key=value]
php artisan module:check-updates [identifier?]
php artisan module:update [identifier] [--force] [--source=auto|bundled|github]

# 플러그인
php artisan plugin:list
php artisan plugin:install [identifier]
php artisan plugin:activate [identifier]
php artisan plugin:deactivate [identifier]
php artisan plugin:uninstall [identifier]
php artisan plugin:composer-install [identifier?] [--all]
php artisan plugin:cache-clear [identifier?]
php artisan plugin:seed [identifier] [--sample] [--count=key=value]
php artisan plugin:check-updates [identifier?]
php artisan plugin:update [identifier] [--force] [--source=auto|bundled|github]

# 템플릿
php artisan template:list
php artisan template:install [identifier]
php artisan template:activate [identifier]
php artisan template:deactivate [identifier]
php artisan template:uninstall [identifier]
php artisan template:cache-clear
php artisan template:check-updates [identifier?]
php artisan template:update [identifier] [--layout-strategy=overwrite] [--force] [--source=auto|bundled|github]

# Composer 의존성 (모듈/플러그인별 독립 vendor/)
php artisan extension:composer-install

# 오토로드
php artisan extension:update-autoload

SEO Artisan 커맨드

php artisan seo:warmup [--layout=]
php artisan seo:clear [--layout=]
php artisan seo:stats
php artisan seo:generate-sitemap [--sync]

코드 스타일/마이그레이션 명령어

# 코드 스타일 (Laravel Pint)
vendor/bin/pint --dirty

# 마이그레이션
php artisan make:migration create_[table]_table
php artisan migrate
php artisan migrate:rollback

파일 유형별 규정 확인

파일 수정 전 해당 규정 파일을 먼저 확인합니다:

수정 대상 파일 패턴 작업 전 필수 참조
app/Http/Controllers/** controllers.md, api-documentation.md
app/Services/** service-repository.md
app/Http/Requests/** validation.md
app/Repositories/** service-repository.md
app/Http/Resources/** api-resources.md
app/**/DTO/**, modules/**/src/DTO/**, plugins/**/src/DTO/** dto.md
database/migrations/** database-guide.md
database/seeders/** database-guide.md
resources/layouts/**/*.json layout-json.md
templates/**/layouts/**/*.json layout-json.md
templates/**/src/components/**/*.tsx components.md
modules/**/Listeners/** hooks.md
plugins/**/Listeners/** hooks.md
lang/{ko,en}/**/*.php database-guide.md (다국어 섹션) — 코어 백엔드 다국어
lang/{ko,en}.json, lang/partial/{ko,en}/** data-binding-i18n.md — 코어 프론트엔드 다국어 ($t:core.*)
lang/** database-guide.md (다국어 섹션)
routes/** routing.md
app/Seo/** seo-system.md

참고 파일 위치

  • AbstractModule: app/Extension/AbstractModule.php
  • HookManager: app/Extension/HookManager.php
  • ModuleManager: app/Extension/ModuleManager.php
  • PluginManager: app/Extension/PluginManager.php
  • TemplateManager: app/Extension/TemplateManager.php
  • CoreStorageDriver: app/Extension/Storage/CoreStorageDriver.php
  • ResponseHelper: app/Helpers/ResponseHelper.php
  • ExtensionStatusGuard: app/Extension/Helpers/ExtensionStatusGuard.php
  • ExtensionBackupHelper: app/Extension/Helpers/ExtensionBackupHelper.php
  • ExtensionPendingHelper: app/Extension/Helpers/ExtensionPendingHelper.php
  • ExtensionRoleSyncHelper: app/Extension/Helpers/ExtensionRoleSyncHelper.php
  • ExtensionMenuSyncHelper: app/Extension/Helpers/ExtensionMenuSyncHelper.php
  • SettingsMigrator: app/Extension/Helpers/SettingsMigrator.php
  • UpgradeStepInterface: app/Contracts/Extension/UpgradeStepInterface.php
  • UpgradeContext: app/Extension/UpgradeContext.php
  • SeoRenderer: app/Seo/SeoRenderer.php
  • SeoMiddleware: app/Seo/SeoMiddleware.php
  • SeoCacheManager: app/Seo/SeoCacheManager.php
  • SeoServiceProvider: app/Seo/SeoServiceProvider.php
  • SitemapContributorInterface: app/Seo/Contracts/SitemapContributorInterface.php
  • SitemapGenerator: app/Seo/SitemapGenerator.php
  • ActivityLogChannel: app/ActivityLog/ActivityLogChannel.php
  • ActivityLogHandler: app/ActivityLog/ActivityLogHandler.php
  • ActivityLogProcessor: app/ActivityLog/ActivityLogProcessor.php
  • ResolvesActivityLogType: app/ActivityLog/Traits/ResolvesActivityLogType.php
  • ChangeDetector: app/ActivityLog/ChangeDetector.php
  • CoreActivityLogListener: app/Listeners/CoreActivityLogListener.php