Files
a-share-analysis/server/tools/export-openapi.ts
T

97 lines
3.5 KiB
TypeScript

import 'reflect-metadata';
import { Module } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule, type OpenAPIObject } from '@nestjs/swagger';
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { format } from 'prettier';
import { HealthController } from '../apps/api/src/health.controller';
import { MarketOverviewController } from '../apps/api/src/market-overview/market-overview.controller';
import { MarketDetailController } from '../apps/api/src/market-overview/detail/market-detail.controller';
import { ShortTermMarketController } from '../apps/api/src/market-overview/short-term.controller';
import {
HealthService,
MYSQL_HEALTH_PORT,
REDIS_HEALTH_PORT,
} from '../apps/api/src/health.service';
import {
MarketOverviewService,
TDX_CLIENT_PORT,
} from '../apps/api/src/market-overview/market-overview.service';
import { MarketDetailService } from '../apps/api/src/market-overview/detail/market-detail.service';
import { createAktoolsClient } from '../apps/api/src/market-overview/aktools/aktools.client';
import {
AKTOOLS_CLIENT_PORT,
ShortTermMarketService,
} from '../apps/api/src/market-overview/short-term.service';
import { DisabledHealthClient } from '../libs/infrastructure/src/disabled-health.client';
@Module({
controllers: [
HealthController,
MarketOverviewController,
MarketDetailController,
ShortTermMarketController,
],
providers: [
HealthService,
MarketOverviewService,
MarketDetailService,
{ provide: TDX_CLIENT_PORT, useValue: { request: async () => ({}) } },
ShortTermMarketService,
{
provide: AKTOOLS_CLIENT_PORT,
useValue: createAktoolsClient({
enabled: false,
baseUrl: 'http://127.0.0.1:8080',
timeoutMs: 10_000,
}),
},
{ provide: REDIS_HEALTH_PORT, useValue: new DisabledHealthClient('文档生成不连接 Redis') },
{ provide: MYSQL_HEALTH_PORT, useValue: new DisabledHealthClient('文档生成不连接 MySQL') },
],
})
class OpenApiModule {}
function sortObject(value: unknown): unknown {
if (Array.isArray(value)) return value.map(sortObject);
if (!value || typeof value !== 'object') return value;
return Object.fromEntries(
Object.entries(value as Record<string, unknown>)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, child]) => [key, sortObject(child)]),
);
}
/** 从真实 Controller/DTO 生成确定性 OpenAPI 文档,生成期间不连接外部基础设施。 */
export async function createOpenApiDocument(): Promise<OpenAPIObject> {
const app = await NestFactory.create(OpenApiModule, { logger: false });
app.setGlobalPrefix('api/v1');
const options = new DocumentBuilder()
.setTitle('A 股市场分析工具 API')
.setVersion('0.0.8-draft')
.build();
const document = SwaggerModule.createDocument(app, options);
await app.close();
return sortObject(document) as OpenAPIObject;
}
async function writeOpenApiDocument(): Promise<void> {
const serverDirectory = path.resolve(__dirname, '..');
const output = path.join(serverDirectory, 'openapi', 'openapi.json');
const serialized = await format(JSON.stringify(await createOpenApiDocument()), {
parser: 'json',
});
await mkdir(path.dirname(output), { recursive: true });
await writeFile(output, serialized, 'utf8');
}
if (require.main === module) {
void writeOpenApiDocument().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
}