chore: add step-by-step OpenAPI conversion demo
Add tools/api-format-to-openapi/demo-steps.mjs and npm scripts demo/demo:pause; ignore demo-output and document usage.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
# Generated OpenAPI (local runs; commit only if you want to publish the spec)
|
# Generated OpenAPI (local runs; commit only if you want to publish the spec)
|
||||||
openapi.generated.json
|
openapi.generated.json
|
||||||
openapi.llm.json
|
openapi.llm.json
|
||||||
|
tools/api-format-to-openapi/demo-output/
|
||||||
|
|||||||
@@ -92,6 +92,17 @@ X-AID-Export-Key: <если задан AID_EXPORT_API_KEY>
|
|||||||
|
|
||||||
## CLI (без Nest)
|
## CLI (без Nest)
|
||||||
|
|
||||||
|
### Пошаговая демонстрация в терминале
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tools/api-format-to-openapi
|
||||||
|
npm run demo
|
||||||
|
# или с паузой после каждого шага (Enter):
|
||||||
|
npm run demo:pause
|
||||||
|
```
|
||||||
|
|
||||||
|
Показывает входной **api-format**, логику маппинга, запуск конвертера и структуру **OpenAPI**; результат — `demo-output/openapi.json`.
|
||||||
|
|
||||||
### api-format → OpenAPI
|
### api-format → OpenAPI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -10,6 +10,23 @@
|
|||||||
| `prompts/llm-system.md` | Системный промпт для LLM: «верни только JSON OpenAPI 3.0.3» |
|
| `prompts/llm-system.md` | Системный промпт для LLM: «верни только JSON OpenAPI 3.0.3» |
|
||||||
| `convert.mjs` | CLI: режим `deterministic` (маппинг в коде) и `llm` (OpenAI API) |
|
| `convert.mjs` | CLI: режим `deterministic` (маппинг в коде) и `llm` (OpenAI API) |
|
||||||
|
|
||||||
|
## Пошаговая демонстрация в терминале
|
||||||
|
|
||||||
|
Чтобы **постепенно** увидеть: входной api-format → что делает конвертер → структура OpenAPI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tools/api-format-to-openapi
|
||||||
|
npm run demo
|
||||||
|
```
|
||||||
|
|
||||||
|
С паузой после каждого шага (нажимай Enter):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run demo:pause
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат кладётся в `demo-output/openapi.json`.
|
||||||
|
|
||||||
## Детерминированный режим (без LLM)
|
## Детерминированный режим (без LLM)
|
||||||
|
|
||||||
Подходит для **фиксированной** схемы `apiFormatVersion: "1"` как в примере.
|
Подходит для **фиксированной** схемы `apiFormatVersion: "1"` как в примере.
|
||||||
|
|||||||
117
tools/api-format-to-openapi/demo-steps.mjs
Normal file
117
tools/api-format-to-openapi/demo-steps.mjs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Пошаговая демонстрация: api-format → OpenAPI 3.0 (детерминированный режим).
|
||||||
|
*
|
||||||
|
* node demo-steps.mjs — все шаги подряд в консоли
|
||||||
|
* node demo-steps.mjs --pause — пауза после каждого шага (Enter)
|
||||||
|
*
|
||||||
|
* Результат также пишется в demo-output/openapi.json рядом со скриптом.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { createInterface } from "node:readline/promises";
|
||||||
|
import { stdin as input, stdout as output } from "node:process";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const EXAMPLE = join(__dirname, "examples", "api-format.example.json");
|
||||||
|
const OUT_DIR = join(__dirname, "demo-output");
|
||||||
|
const OUT_OPENAPI = join(OUT_DIR, "openapi.json");
|
||||||
|
const usePause = process.argv.includes("--pause");
|
||||||
|
|
||||||
|
function banner(title) {
|
||||||
|
const line = "═".repeat(Math.min(60, title.length + 8));
|
||||||
|
console.log(`\n${line}\n ${title}\n${line}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pause(msg = "Нажми Enter, чтобы перейти к следующему шагу…") {
|
||||||
|
if (!usePause) return;
|
||||||
|
const rl = createInterface({ input, output });
|
||||||
|
await rl.question(msg);
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.clear?.();
|
||||||
|
|
||||||
|
banner("Шаг 0. Задача");
|
||||||
|
console.log(
|
||||||
|
"У нас есть описание API в СВОЁМ формате (api-format), не OpenAPI.\n" +
|
||||||
|
"Нужно получить стандартную спецификацию OpenAPI 3.0 — для Swagger, клиентов, AID.\n" +
|
||||||
|
"Сейчас покажем путь на учебном примере (детерминированный маппинг в convert.mjs).",
|
||||||
|
);
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 1. Входной файл (фрагмент api-format)");
|
||||||
|
console.log(`Файл: ${EXAMPLE}\n`);
|
||||||
|
const rawIn = readFileSync(EXAMPLE, "utf8");
|
||||||
|
const apiFormat = JSON.parse(rawIn);
|
||||||
|
console.log(JSON.stringify(apiFormat, null, 2));
|
||||||
|
console.log(
|
||||||
|
"\n↑ Это НЕ OpenAPI. Здесь: версия формата, info, basePath, ресурс Equipment с полями и операциями CRUD.",
|
||||||
|
);
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 2. Что делает конвертер (логика)");
|
||||||
|
console.log(`
|
||||||
|
• apiFormatVersion "1" → включается ветка toOpenApiDeterministic в convert.mjs
|
||||||
|
• Ресурс Equipment → components.schemas.Equipment + пути /api/equipment и /api/equipment/{id}
|
||||||
|
• Поля (string, uuid, enum…) → JSON Schema в components.schemas
|
||||||
|
• listQuery → query-параметры (_start, _end, _sort, _order, q, status…)
|
||||||
|
• security bearer → components.securitySchemes + security на операциях
|
||||||
|
`);
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 3. Запуск convert.mjs");
|
||||||
|
mkdirSync(OUT_DIR, { recursive: true });
|
||||||
|
const convertScript = join(__dirname, "convert.mjs");
|
||||||
|
console.log(`Команда:\n node convert.mjs --in examples/api-format.example.json --out demo-output/openapi.json\n`);
|
||||||
|
execFileSync(process.execPath, [convertScript, "--in", EXAMPLE, "--out", OUT_OPENAPI], {
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
console.log(`\nГотово. Файл: ${OUT_OPENAPI}`);
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 4. Результат — структура OpenAPI");
|
||||||
|
const spec = JSON.parse(readFileSync(OUT_OPENAPI, "utf8"));
|
||||||
|
console.log(`openapi: ${spec.openapi}`);
|
||||||
|
console.log(`title: ${spec.info?.title}`);
|
||||||
|
console.log(`version: ${spec.info?.version}`);
|
||||||
|
console.log("\nПути (paths):");
|
||||||
|
for (const p of Object.keys(spec.paths || {}).sort()) {
|
||||||
|
const methods = Object.keys(spec.paths[p]).join(", ");
|
||||||
|
console.log(` ${p} [${methods}]`);
|
||||||
|
}
|
||||||
|
console.log("\nСхемы (components.schemas):", Object.keys(spec.components?.schemas || {}).join(", "));
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 5. Фрагмент: GET список (одна операция)");
|
||||||
|
const listPath = Object.keys(spec.paths || {}).find((k) => k.endsWith("/equipment") && !k.includes("{"));
|
||||||
|
if (listPath && spec.paths[listPath]?.get) {
|
||||||
|
console.log(JSON.stringify({ [listPath]: { get: spec.paths[listPath].get } }, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log("(путь списка не найден — открой demo-output/openapi.json)");
|
||||||
|
}
|
||||||
|
await pause();
|
||||||
|
|
||||||
|
banner("Шаг 6. Как проверить дальше");
|
||||||
|
console.log(`
|
||||||
|
1) Открой целиком: ${OUT_OPENAPI}
|
||||||
|
2) Валидация (из корня репозитория):
|
||||||
|
npx -y @apidevtools/swagger-cli validate tools/api-format-to-openapi/demo-output/openapi.json
|
||||||
|
3) Через Nest (сервер на 3001):
|
||||||
|
POST http://127.0.0.1:3001/aid/export/openapi
|
||||||
|
тело: { "apiFormat": <содержимое api-format.example.json>, "mode": "deterministic" }
|
||||||
|
4) Режим LLM (другой входной JSON):
|
||||||
|
node convert.mjs --mode llm --in your.json --out openapi.llm.json
|
||||||
|
(нужен OPENAI_API_KEY)
|
||||||
|
`);
|
||||||
|
console.log("Демо завершено.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
"description": "Конвертация доменного api-format в OpenAPI 3.0 (детерминированно или через LLM)",
|
"description": "Конвертация доменного api-format в OpenAPI 3.0 (детерминированно или через LLM)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"convert": "node convert.mjs --in examples/api-format.example.json --out ../../openapi.generated.json",
|
"convert": "node convert.mjs --in examples/api-format.example.json --out ../../openapi.generated.json",
|
||||||
"convert:llm": "node convert.mjs --mode llm --in examples/api-format.example.json --out ../../openapi.llm.json"
|
"convert:llm": "node convert.mjs --mode llm --in examples/api-format.example.json --out ../../openapi.llm.json",
|
||||||
|
"demo": "node demo-steps.mjs",
|
||||||
|
"demo:pause": "node demo-steps.mjs --pause"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user