docfy patch-spec
Правит уже собранный документ OpenAPI исключительно статическим анализом (ts-morph), без единого require() docs-файлов.
Зачем это нужно
Это ручной способ из CI обойти то единственное, чего конвейер DocfyModule в рантайме сделать не может: сработать под режимом сборки webpack: true из NestJS CLI (автоматическая и рекомендуемая альтернатива описана в разделе Плагин CLI, она выполняет тот же анализ на каждой сборке, без отдельного шага). patch-spec обходит стену webpack тем, что сопоставляет по пути и HTTP-методу, вычисляя их так же, как это уже делают check, coverage и lint, вместо того чтобы требовать живую ссылку на класс контроллера.
Использование
npx nestjs-docfy patch-spec --spec <path-or-url> [options]# Patch a running app's served document
npx nestjs-docfy patch-spec --spec http://localhost:3000/api-json --out openapi.json
# Patch a file already written to disk
npx nestjs-docfy patch-spec --spec dist/openapi.json --out dist/openapi.jsonПараметры
| Option | Default | Description |
|---|---|---|
--spec <path|url> | (required) | A local openapi.json, or a URL (e.g. a running app's /api-json) |
--out <path> | stdout | Where to write the patched document |
--root <path> | . | Project root directory |
--tsconfig <path> | auto-detected | Path to tsconfig.json |
--pattern <glob> | **/*.controller.ts | Glob pattern to find controllers |
--format <format> | ts | Формат docs-файлов, которые нужно искать: ts или js |
--quiet | false | Suppress all output except errors |
Что попадает в документ
Сопоставление с базовым документом идёт по path и HTTP-методу:
ApiTags→ объединяются сtags(теги, которые уже были в базовом документе, не теряются)ApiOperation({ summary, description, deprecated })→ перезаписывает эти поляApiResponse({ status, description, schema, example, examples })→ сливается по коду ответа (остальные коды не трогаются). Когдаschemaилиtypeне заданы, берётся разрешённый тип возврата самого метода, то есть тот же вывод из DTO, class-validator и интерфейсов, который уже делаетgenerate, включаяoneOfиз$ref, если тип возврата — это объединение двух и более именованных DTO или сущностей (например,Promise<UserDto | AdminDto>)ApiBody({ schema, examples })→ задаётrequestBody, с таким же откатом к разрешённому типу параметра@Body()и с той же обработкой объединения черезoneOf(exampleздесь намеренно не читается: у настоящего типаApiBodyOptionsиз@nestjs/swaggerесть толькоexamples, в отличие отApiResponse, где доступны оба)ApiBearerAuth()→ дописывается вsecurityApiParam/ApiQuery/ApiHeader→ сливаются вparametersпо имени и месту: по-настоящему новый параметр дописывается, а у уже существующего поля накладываются поверх, а не выбрасываются (например,@nestjs/swaggerпо одной лишь рефлексии сам заводит голую запись сrequired: trueдля любого аргумента с@Query()или@Param()).enumчитается из литерала массива (enum: ['a', 'b']) либо из ссылки наenumв TS, в том числе импортированный из другого файла (enum: Role), аtypeв схеме выводится какnumber, когда все разрешённые значения числовые, и какstringв остальных случаях
What this does not do
Команда намеренно покрывает не всё и не повторяет семантику декораторов @nestjs/swagger целиком. anyOf не трогается, потому что ни одна конструкция TS не ложится на «любой из» так же естественно, как тип-объединение ложится на oneOf. links и callbacks тоже, ведь у самого @nestjs/swagger нет опции декоратора ни для того, ни для другого. Любой аргумент декоратора, который не является литералом (переменная, вызов функции, спред), остаётся как есть, вместо того чтобы гадать. Лучше оставить поле таким, каким оно было в базовом документе, чем вписать туда неверное значение. Тип возврата или содержимое @Body() превращаются в oneOf только когда каждая ветка разрешается в именованный DTO или сущность. Маршруты, которые docs-файл описывает, но которых нет в --spec, попадают в предупреждения, а не отбрасываются молча и не роняют команду.