feat(api-docs): 확장 API 레퍼런스 발견성 — README 목차 규약 + 동적 스캔 인덱스
확장 API 문서가 인덱스·목차 어디에도 노출되지 않아 처음 온 개발자/AI 가
발견할 수 없던 간극을 해소했다. 코어에 확장명을 하드코딩하지 않고(동적 로딩 원칙),
파일 시스템 패턴 스캔 + 확장 소유 목차 규약으로 발견성을 확보한다.
- ApiDocScaffolder::readmeIndex — 각 대상의 docs/api/README.md 목차(도메인 파일·
엔드포인트 수)를 @generated 블록으로 생성(멱등, 사람 개요 보존). 커맨드가 대상별
README 를 방출(readmeFile 경로 헬퍼). 코어 + 9 확장 = 10개 목차 생성.
- generate-docs-index.cjs scanExtensionApiReadmes — {modules,plugins}/_bundled/*/
docs/api/README.md 를 패턴 스캔(확장명 하드코딩 0)해 ·AGENTS.md 에
"확장 API 레퍼런스" 표로 자동 편입. 확장 추가/삭제 시 재생성만으로 반영.
- api-documentation.md 에 README 목차 규약 + 발견 경로 명문화. coverage.json 에
api-doc-readme-index(manual-only) 등록. 루트 README API 레퍼런스 링크 갱신.
테스트: readmeIndex 목차 생성·멱등 2건 + 스캐너 편입·하드코딩부재 2건(node --test).
This commit is contained in:
1 parent
56a78e1d55
commit
168f439be3
15 files changed
+401
No files matched your search
@@ -144,6 +144,23 @@
|
||||
| [e2e-testing.md](docs/testing/e2e-testing.md) | 그누보드7 Playwright E2E 테스트 가이드 | - |
|
||||
|
||||
|
||||
### 확장 API 레퍼런스 (9개 확장, 자동 스캔)
|
||||
|
||||
> 각 확장이 소유하는 API 문서 목차. `php artisan api:docgen` 이 생성하며, 이 표는 `{modules,plugins}/_bundled/*/docs/api/README.md` 를 패턴 스캔해 자동 편입된다(확장명 하드코딩 없음).
|
||||
|
||||
| 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 |
|
||||
|------|------|--------------|----------------|
|
||||
| `gnuboard7-hello_module` | 모듈 | [docs/api/](modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 2 |
|
||||
| `sirsoft-board` | 모듈 | [docs/api/](modules/_bundled/sirsoft-board/docs/api/README.md) | 10 / 80 |
|
||||
| `sirsoft-ecommerce` | 모듈 | [docs/api/](modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 231 |
|
||||
| `sirsoft-page` | 모듈 | [docs/api/](modules/_bundled/sirsoft-page/docs/api/README.md) | 2 / 17 |
|
||||
| `sirsoft-ckeditor5` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-ckeditor5/docs/api/README.md) | 2 / 2 |
|
||||
| `sirsoft-gdpr` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-gdpr/docs/api/README.md) | 4 / 15 |
|
||||
| `sirsoft-marketing` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-marketing/docs/api/README.md) | 2 / 2 |
|
||||
| `sirsoft-pay_kginicis` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-pay_kginicis/docs/api/README.md) | 5 / 22 |
|
||||
| `sirsoft-verification_kginicis` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-verification_kginicis/docs/api/README.md) | 1 / 1 |
|
||||
|
||||
|
||||
<!-- AUTO-GENERATED-END: docs-quick-reference -->
|
||||
|
||||
---
|
||||
|
||||
@@ -128,11 +128,26 @@ class ApiDocgenCommand extends Command
|
||||
$checkFindings = [];
|
||||
$examplesOnly = (bool) $this->option('examples-only');
|
||||
|
||||
// 대상(코어/확장)별 README 목차 항목 누적: readmeKey => ['label' => ..., 'entries' => [...]]
|
||||
$readmeIndex = [];
|
||||
|
||||
foreach ($grouped as $file => $items) {
|
||||
$sections = [];
|
||||
$sectionKeys = [];
|
||||
$exampleBlocks = [];
|
||||
|
||||
// 목차용 도메인 파일 항목 (소유별). 예시-only/check 모드에서도 목차는 최신화한다.
|
||||
$owner = $items[0]['owner'];
|
||||
$readmeKey = $this->readmeFile($owner);
|
||||
$readmeIndex[$readmeKey]['label'] = $owner['type'] === 'core'
|
||||
? '코어'
|
||||
: ($owner['type'] === 'module' ? '모듈' : '플러그인')." `{$owner['id']}`";
|
||||
$readmeIndex[$readmeKey]['entries'][] = [
|
||||
'domain' => $items[0]['domain_group'],
|
||||
'file' => basename($file),
|
||||
'count' => count($items),
|
||||
];
|
||||
|
||||
foreach ($items as $route) {
|
||||
$request = $introspector->introspect($route['controller'], $route['controller_method']);
|
||||
[$schema, $probeMeta] = $this->probeEndpoint($probe, $inferrer, $route, $probed, $sampleMap, $request);
|
||||
@@ -210,6 +225,17 @@ class ApiDocgenCommand extends Command
|
||||
|
||||
$probe->cleanup();
|
||||
|
||||
// 대상별 API 문서 목차(README.md) 생성/갱신 — 확장 API 레퍼런스 발견성의 규약 진입점.
|
||||
// check/dry-run 은 파일을 쓰지 않으므로 제외. examples-only 는 목차도 최신화한다.
|
||||
if (! $this->option('check')) {
|
||||
foreach ($readmeIndex as $readmePath => $meta) {
|
||||
$existing = File::exists($readmePath) ? File::get($readmePath) : null;
|
||||
$content = $scaffolder->readmeIndex($meta['label'], $meta['entries'], $existing);
|
||||
File::ensureDirectoryExists(dirname($readmePath));
|
||||
File::put($readmePath, $content);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('check')) {
|
||||
if ($checkFindings !== []) {
|
||||
$this->warn('실측 제외/문서 누락 '.count($checkFindings).'건:');
|
||||
@@ -790,6 +816,23 @@ class ApiDocgenCommand extends Command
|
||||
return base_path("{$base}/_bundled/{$owner['id']}/docs/api/{$domain}.md");
|
||||
}
|
||||
|
||||
/**
|
||||
* 소유 대상의 API 문서 목차(README.md) 경로를 반환합니다.
|
||||
*
|
||||
* @param array{type: string, id: string|null} $owner 소유 메타
|
||||
* @return string README.md 절대 경로
|
||||
*/
|
||||
private function readmeFile(array $owner): string
|
||||
{
|
||||
if ($owner['type'] === 'core') {
|
||||
return base_path('docs/backend/api/README.md');
|
||||
}
|
||||
|
||||
$base = $owner['type'] === 'module' ? 'modules' : 'plugins';
|
||||
|
||||
return base_path("{$base}/_bundled/{$owner['id']}/docs/api/README.md");
|
||||
}
|
||||
|
||||
/**
|
||||
* 문서 헤더(제목 + TL;DR)를 생성합니다.
|
||||
*
|
||||
|
||||
@@ -116,6 +116,55 @@ class ApiDocScaffolder
|
||||
return implode("\n", $lines)."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* 대상(코어/확장)의 API 문서 디렉토리 목차(README.md)를 생성합니다.
|
||||
*
|
||||
* 도메인 파일별 링크 + 엔드포인트 수를 표로 나열해, 처음 진입한 개발자/AI 가 이 대상의
|
||||
* API 레퍼런스 전모를 한눈에 파악하도록 한다. 표는 `@generated` 블록 안에 두어 재생성 시
|
||||
* 갱신되며, 블록 밖 사람 서술(개요·주의사항)은 보존한다. 확장 API 문서의 발견성은
|
||||
* 코어 인덱스 생성기가 이 README 를 패턴 스캔(확장명 하드코딩 없이)해 확보한다.
|
||||
*
|
||||
* @param string $ownerLabel 소유 라벨 (예: '코어', '모듈 `sirsoft-ecommerce`')
|
||||
* @param array<int, array{domain: string, file: string, count: int}> $entries 도메인 파일 목록
|
||||
* @param string|null $existing 기존 README (사람 서술 보존용, 없으면 null)
|
||||
* @return string README 마크다운
|
||||
*/
|
||||
public function readmeIndex(string $ownerLabel, array $entries, ?string $existing = null): string
|
||||
{
|
||||
usort($entries, fn ($a, $b) => strcmp($a['domain'], $b['domain']));
|
||||
$total = array_sum(array_column($entries, 'count'));
|
||||
|
||||
$rows = ['| 문서 | 도메인 | 엔드포인트 |', '| --- | --- | --- |'];
|
||||
foreach ($entries as $e) {
|
||||
$rows[] = "| [{$e['file']}]({$e['file']}) | `{$e['domain']}` | {$e['count']} |";
|
||||
}
|
||||
|
||||
$genKey = 'api-readme-index';
|
||||
$lines = [];
|
||||
$lines[] = '# API 레퍼런스 문서 목차';
|
||||
$lines[] = '';
|
||||
$lines[] = "> **소유**: {$ownerLabel} · **생성**: `php artisan api:docgen` (실측 기반).";
|
||||
$lines[] = '> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.';
|
||||
$lines[] = '';
|
||||
$lines[] = self::GEN_START.$genKey.' -->';
|
||||
$lines[] = '- **문서 수**: '.count($entries)." · **엔드포인트 수**: {$total}";
|
||||
$lines[] = '';
|
||||
$lines[] = implode("\n", $rows);
|
||||
$lines[] = '';
|
||||
$lines[] = self::GEN_END;
|
||||
$lines[] = '';
|
||||
|
||||
$generated = implode("\n", $lines)."\n";
|
||||
|
||||
// 블록 밖 사람 서술(개요 등)이 있으면 보존한다.
|
||||
$prose = $existing !== null ? $this->extractHumanProse($existing, $genKey) : null;
|
||||
if ($prose !== null && $prose !== '') {
|
||||
$generated .= "\n".$prose."\n";
|
||||
}
|
||||
|
||||
return $generated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증/권한 라인을 구성합니다.
|
||||
*
|
||||
|
||||
@@ -51,6 +51,25 @@ G7 의 REST API 는 라우트 `->name()` 규약은 있으나 엔드포인트별
|
||||
|
||||
도메인 그룹핑은 URI/라우트명 prefix 기준(`api.admin.users.*` → `users.md`)으로 커맨드가 자동 분류한다.
|
||||
|
||||
### 문서 목차와 발견성 (README.md 규약)
|
||||
|
||||
각 대상(코어·확장)의 API 문서 디렉토리에는 `README.md` 목차가 있어야 한다. `api:docgen` 이 도메인 파일
|
||||
목록·엔드포인트 수를 담아 자동 생성한다(`@generated` 블록, 재생성 멱등, 블록 밖 사람 개요는 보존).
|
||||
|
||||
- 코어: `docs/backend/api/README.md`
|
||||
- 확장: `{modules|plugins}/_bundled/{id}/docs/api/README.md`
|
||||
|
||||
확장 API 문서의 발견성은 이 목차를 통해 확보한다. 코어 인덱스 생성기(`generate-docs-index.cjs`)는
|
||||
확장명을 하드코딩하지 않고 `{modules,plugins}/_bundled/*/docs/api/README.md` 를 **패턴 스캔**해,
|
||||
CLAUDE.md·AGENTS.md·docs-index 의 "확장 API 레퍼런스" 표에 자동 편입한다(동적 로딩 원칙 — 코어는 규약과
|
||||
스캔 패턴만, 확장 이름은 파일 시스템에서 발견). 처음 진입하는 개발자/AI 의 도달 경로:
|
||||
|
||||
```text
|
||||
CLAUDE.md "확장 API 레퍼런스" 표 (자동 스캔)
|
||||
→ {확장}/docs/api/README.md (확장 소유 목차)
|
||||
→ {도메인}.md (엔드포인트별 파라미터·응답·예시)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 표준 문서 포맷
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 코어 · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 35 · **엔드포인트 수**: 291
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [activity-logs.md](activity-logs.md) | `activity-logs` | 3 |
|
||||
| [attachment.md](attachment.md) | `attachment` | 1 |
|
||||
| [attachments.md](attachments.md) | `attachments` | 4 |
|
||||
| [auth.md](auth.md) | `auth` | 15 |
|
||||
| [avatar.md](avatar.md) | `avatar` | 2 |
|
||||
| [broadcasting.md](broadcasting.md) | `broadcasting` | 1 |
|
||||
| [changelog.md](changelog.md) | `changelog` | 1 |
|
||||
| [core-update.md](core-update.md) | `core-update` | 2 |
|
||||
| [dashboard.md](dashboard.md) | `dashboard` | 5 |
|
||||
| [extensions.md](extensions.md) | `extensions` | 3 |
|
||||
| [identity.md](identity.md) | `identity` | 27 |
|
||||
| [language-packs.md](language-packs.md) | `language-packs` | 15 |
|
||||
| [layouts.md](layouts.md) | `layouts` | 2 |
|
||||
| [license.md](license.md) | `license` | 1 |
|
||||
| [locales.md](locales.md) | `locales` | 1 |
|
||||
| [me.md](me.md) | `me` | 3 |
|
||||
| [menus.md](menus.md) | `menus` | 10 |
|
||||
| [modules.md](modules.md) | `modules` | 25 |
|
||||
| [notification-channels.md](notification-channels.md) | `notification-channels` | 1 |
|
||||
| [notification-definitions.md](notification-definitions.md) | `notification-definitions` | 5 |
|
||||
| [notification-logs.md](notification-logs.md) | `notification-logs` | 3 |
|
||||
| [notification-templates.md](notification-templates.md) | `notification-templates` | 4 |
|
||||
| [notifications.md](notifications.md) | `notifications` | 14 |
|
||||
| [password.md](password.md) | `password` | 1 |
|
||||
| [permissions.md](permissions.md) | `permissions` | 1 |
|
||||
| [plugins.md](plugins.md) | `plugins` | 27 |
|
||||
| [profile.md](profile.md) | `profile` | 4 |
|
||||
| [roles.md](roles.md) | `roles` | 7 |
|
||||
| [schedules.md](schedules.md) | `schedules` | 12 |
|
||||
| [search.md](search.md) | `search` | 1 |
|
||||
| [seo.md](seo.md) | `seo` | 5 |
|
||||
| [settings.md](settings.md) | `settings` | 15 |
|
||||
| [templates.md](templates.md) | `templates` | 57 |
|
||||
| [users.md](users.md) | `users` | 12 |
|
||||
| [verify-password.md](verify-password.md) | `verify-password` | 1 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 모듈 `gnuboard7-hello_module` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 1 · **엔드포인트 수**: 2
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [memos.md](memos.md) | `memos` | 2 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 모듈 `sirsoft-board` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 10 · **엔드포인트 수**: 80
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [activity-stats.md](activity-stats.md) | `activity-stats` | 1 |
|
||||
| [board.md](board.md) | `board` | 18 |
|
||||
| [board-activities.md](board-activities.md) | `board-activities` | 1 |
|
||||
| [board-types.md](board-types.md) | `board-types` | 4 |
|
||||
| [boards.md](boards.md) | `boards` | 37 |
|
||||
| [dashboard.md](dashboard.md) | `dashboard` | 4 |
|
||||
| [my-comments.md](my-comments.md) | `my-comments` | 1 |
|
||||
| [reports.md](reports.md) | `reports` | 7 |
|
||||
| [settings.md](settings.md) | `settings` | 5 |
|
||||
| [users.md](users.md) | `users` | 2 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 모듈 `sirsoft-ecommerce` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 33 · **엔드포인트 수**: 231
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [addresses.md](addresses.md) | `addresses` | 6 |
|
||||
| [brands.md](brands.md) | `brands` | 6 |
|
||||
| [cart.md](cart.md) | `cart` | 11 |
|
||||
| [categories.md](categories.md) | `categories` | 14 |
|
||||
| [category-image.md](category-image.md) | `category-image` | 1 |
|
||||
| [checkout.md](checkout.md) | `checkout` | 5 |
|
||||
| [claim-reasons.md](claim-reasons.md) | `claim-reasons` | 8 |
|
||||
| [coupons.md](coupons.md) | `coupons` | 4 |
|
||||
| [currency.md](currency.md) | `currency` | 2 |
|
||||
| [dashboard.md](dashboard.md) | `dashboard` | 4 |
|
||||
| [extra-fee-templates.md](extra-fee-templates.md) | `extra-fee-templates` | 10 |
|
||||
| [guest.md](guest.md) | `guest` | 5 |
|
||||
| [inquiries.md](inquiries.md) | `inquiries` | 10 |
|
||||
| [mileage.md](mileage.md) | `mileage` | 3 |
|
||||
| [mileage-transactions.md](mileage-transactions.md) | `mileage-transactions` | 5 |
|
||||
| [options.md](options.md) | `options` | 3 |
|
||||
| [orders.md](orders.md) | `orders` | 22 |
|
||||
| [payments.md](payments.md) | `payments` | 1 |
|
||||
| [presets.md](presets.md) | `presets` | 4 |
|
||||
| [product-common-infos.md](product-common-infos.md) | `product-common-infos` | 6 |
|
||||
| [product-image.md](product-image.md) | `product-image` | 1 |
|
||||
| [product-labels.md](product-labels.md) | `product-labels` | 6 |
|
||||
| [product-notice-templates.md](product-notice-templates.md) | `product-notice-templates` | 7 |
|
||||
| [products.md](products.md) | `products` | 30 |
|
||||
| [promotion-coupons.md](promotion-coupons.md) | `promotion-coupons` | 9 |
|
||||
| [review-image.md](review-image.md) | `review-image` | 1 |
|
||||
| [reviews.md](reviews.md) | `reviews` | 12 |
|
||||
| [settings.md](settings.md) | `settings` | 10 |
|
||||
| [shipping-carriers.md](shipping-carriers.md) | `shipping-carriers` | 7 |
|
||||
| [shipping-country.md](shipping-country.md) | `shipping-country` | 2 |
|
||||
| [shipping-policies.md](shipping-policies.md) | `shipping-policies` | 11 |
|
||||
| [users.md](users.md) | `users` | 2 |
|
||||
| [wishlist.md](wishlist.md) | `wishlist` | 3 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 모듈 `sirsoft-page` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 2 · **엔드포인트 수**: 17
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [attachments.md](attachments.md) | `attachments` | 3 |
|
||||
| [pages.md](pages.md) | `pages` | 14 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 플러그인 `sirsoft-ckeditor5` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 2 · **엔드포인트 수**: 2
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [images.md](images.md) | `images` | 1 |
|
||||
| [upload.md](upload.md) | `upload` | 1 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 플러그인 `sirsoft-gdpr` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 4 · **엔드포인트 수**: 15
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [consent.md](consent.md) | `consent` | 7 |
|
||||
| [consent-log.md](consent-log.md) | `consent-log` | 1 |
|
||||
| [policy-versions.md](policy-versions.md) | `policy-versions` | 4 |
|
||||
| [settings.md](settings.md) | `settings` | 3 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 플러그인 `sirsoft-marketing` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 2 · **엔드포인트 수**: 2
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [channels.md](channels.md) | `channels` | 1 |
|
||||
| [settings.md](settings.md) | `settings` | 1 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 플러그인 `sirsoft-pay_kginicis` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 5 · **엔드포인트 수**: 22
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [cbt.md](cbt.md) | `cbt` | 2 |
|
||||
| [orders.md](orders.md) | `orders` | 13 |
|
||||
| [payment.md](payment.md) | `payment` | 5 |
|
||||
| [transaction.md](transaction.md) | `transaction` | 1 |
|
||||
| [vbank.md](vbank.md) | `vbank` | 1 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# API 레퍼런스 문서 목차
|
||||
|
||||
> **소유**: 플러그인 `sirsoft-verification_kginicis` · **생성**: `php artisan api:docgen` (실측 기반).
|
||||
> 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
- **문서 수**: 1 · **엔드포인트 수**: 1
|
||||
|
||||
| 문서 | 도메인 | 엔드포인트 |
|
||||
| --- | --- | --- |
|
||||
| [identity.md](identity.md) | `identity` | 1 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -840,4 +840,52 @@ MD;
|
||||
$this->assertStringNotContainsString('| identifier | query |', $section);
|
||||
$this->assertSame(1, substr_count($section, '| path | path |'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function readme_목차는_도메인_파일과_엔드포인트_수를_표로_나열한다(): void
|
||||
{
|
||||
$scaffolder = new ApiDocScaffolder;
|
||||
|
||||
$entries = [
|
||||
['domain' => 'products', 'file' => 'products.md', 'count' => 30],
|
||||
['domain' => 'orders', 'file' => 'orders.md', 'count' => 22],
|
||||
];
|
||||
|
||||
$readme = $scaffolder->readmeIndex('모듈 `sirsoft-ecommerce`', $entries);
|
||||
|
||||
// 소유 라벨·집계·도메인 링크가 표에 나타난다.
|
||||
$this->assertStringContainsString('# API 레퍼런스 문서 목차', $readme);
|
||||
$this->assertStringContainsString('모듈 `sirsoft-ecommerce`', $readme);
|
||||
$this->assertStringContainsString('**문서 수**: 2 · **엔드포인트 수**: 52', $readme);
|
||||
$this->assertStringContainsString('| [products.md](products.md) | `products` | 30 |', $readme);
|
||||
$this->assertStringContainsString('| [orders.md](orders.md) | `orders` | 22 |', $readme);
|
||||
// 도메인 알파벳 정렬 — orders 가 products 보다 먼저.
|
||||
$this->assertLessThan(strpos($readme, 'products.md'), strpos($readme, 'orders.md'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function readme_재생성은_generated_블록만_갱신하고_사람_서술을_보존한다(): void
|
||||
{
|
||||
$scaffolder = new ApiDocScaffolder;
|
||||
|
||||
$entries = [['domain' => 'pages', 'file' => 'pages.md', 'count' => 5]];
|
||||
$first = $scaffolder->readmeIndex('모듈 `sirsoft-page`', $entries);
|
||||
|
||||
// @generated 블록 밖에 사람 개요 서술을 덧붙인다.
|
||||
$human = $first."\n## 개요\n\n이 모듈은 정적 페이지를 관리합니다. (사람 서술)\n";
|
||||
|
||||
// 엔드포인트 수가 바뀐 재생성.
|
||||
$entriesUpdated = [['domain' => 'pages', 'file' => 'pages.md', 'count' => 6]];
|
||||
$regen = $scaffolder->readmeIndex('모듈 `sirsoft-page`', $entriesUpdated, $human);
|
||||
|
||||
// 표는 갱신되고(6), 사람 서술은 보존된다.
|
||||
$this->assertStringContainsString('엔드포인트 수**: 6', $regen);
|
||||
$this->assertStringContainsString('이 모듈은 정적 페이지를 관리합니다. (사람 서술)', $regen);
|
||||
// 재재생성해도 사람 서술 중복 없이 멱등.
|
||||
$regen2 = $scaffolder->readmeIndex('모듈 `sirsoft-page`', $entriesUpdated, $regen);
|
||||
$this->assertSame(
|
||||
substr_count($regen, '(사람 서술)'),
|
||||
substr_count($regen2, '(사람 서술)')
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user