mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
chore(core): add supplementary openapi json (#4472)
* chore(core): support supplementary openapi json * refactor(core): copy json files to build
This commit is contained in:
parent
00e72714de
commit
e35d099c35
5 changed files with 70 additions and 7 deletions
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
|
@ -5,7 +5,10 @@
|
||||||
"source.fixAll.stylelint": true
|
"source.fixAll.stylelint": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stylelint.validate": ["css", "scss"],
|
"stylelint.validate": [
|
||||||
|
"css",
|
||||||
|
"scss"
|
||||||
|
],
|
||||||
"eslint.workingDirectories": [
|
"eslint.workingDirectories": [
|
||||||
{
|
{
|
||||||
"pattern": "./packages/*",
|
"pattern": "./packages/*",
|
||||||
|
@ -23,6 +26,14 @@
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": true,
|
||||||
},
|
},
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"packages/core/src/routes/*.openapi.json"
|
||||||
|
],
|
||||||
|
"url": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Alipay",
|
"Alipay",
|
||||||
"CIAM",
|
"CIAM",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
"copyfiles": "copyfiles -u 1 src/**/*.md build",
|
"copyfiles": "copyfiles -u 1 src/routes/**/*.openapi.json build/",
|
||||||
"build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles",
|
"build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles",
|
||||||
"build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap",
|
"build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap",
|
||||||
"lint": "eslint --ext .ts src",
|
"lint": "eslint --ext .ts src",
|
||||||
|
|
15
packages/core/src/routes/status.openapi.json
Normal file
15
packages/core/src/routes/status.openapi.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"paths": {
|
||||||
|
"/api/status": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Health check",
|
||||||
|
"description": "The traditional health check API. No authentication needed.\n\n> **Note**\n> Even if 204 is returned, it does not guarantee all the APIs are working properly since they may depend on additional resources or external services.",
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "The Logto core service is healthy."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,8 +51,6 @@ describe('GET /swagger.json', () => {
|
||||||
|
|
||||||
it('should contain the specific paths', async () => {
|
it('should contain the specific paths', async () => {
|
||||||
const response = await mockSwaggerRequest.get('/swagger.json');
|
const response = await mockSwaggerRequest.get('/swagger.json');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
expect(Object.entries(response.body.paths)).toHaveLength(2);
|
|
||||||
expect(response.body.paths).toMatchObject({
|
expect(response.body.paths).toMatchObject({
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
'/api/mock': {
|
'/api/mock': {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { httpCodeToMessage } from '@logto/core-kit';
|
import { httpCodeToMessage } from '@logto/core-kit';
|
||||||
import { conditionalArray, deduplicate, toTitle } from '@silverhand/essentials';
|
import { conditionalArray, deduplicate, toTitle } from '@silverhand/essentials';
|
||||||
|
import deepmerge from 'deepmerge';
|
||||||
import type { IMiddleware } from 'koa-router';
|
import type { IMiddleware } from 'koa-router';
|
||||||
import type Router from 'koa-router';
|
import type Router from 'koa-router';
|
||||||
import type { OpenAPIV3 } from 'openapi-types';
|
import type { OpenAPIV3 } from 'openapi-types';
|
||||||
|
@ -146,6 +151,27 @@ const isManagementApiRouter = ({ stack }: Router) =>
|
||||||
.filter(({ path }) => !path.includes('.*'))
|
.filter(({ path }) => !path.includes('.*'))
|
||||||
.some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_)));
|
.some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively find all supplement files (files end with `.openapi.json`) for the given
|
||||||
|
* directory.
|
||||||
|
*/
|
||||||
|
/* eslint-disable @silverhand/fp/no-mutating-methods, no-await-in-loop */
|
||||||
|
const findSupplementFiles = async (directory: string) => {
|
||||||
|
const result: string[] = [];
|
||||||
|
|
||||||
|
for (const file of await fs.readdir(directory)) {
|
||||||
|
const stats = await fs.stat(path.join(directory, file));
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
result.push(...(await findSupplementFiles(path.join(directory, file))));
|
||||||
|
} else if (file.endsWith('.openapi.json')) {
|
||||||
|
result.push(path.join(directory, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
/* eslint-enable @silverhand/fp/no-mutating-methods, no-await-in-loop */
|
||||||
|
|
||||||
// Keep using `any` to accept various custom context types.
|
// Keep using `any` to accept various custom context types.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export default function swaggerRoutes<T extends AnonymousRouter, R extends Router<unknown, any>>(
|
export default function swaggerRoutes<T extends AnonymousRouter, R extends Router<unknown, any>>(
|
||||||
|
@ -185,17 +211,30 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
|
||||||
pathMap.set(path, { ...pathMap.get(path), [method]: operation });
|
pathMap.set(path, { ...pathMap.get(path), [method]: operation });
|
||||||
}
|
}
|
||||||
|
|
||||||
const document: OpenAPIV3.Document = {
|
// Current path should be the root directory of routes files.
|
||||||
|
const supplementPaths = await findSupplementFiles(path.dirname(fileURLToPath(import.meta.url)));
|
||||||
|
const supplementDocuments = await Promise.all(
|
||||||
|
supplementPaths.map(
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
async (path) => JSON.parse(await fs.readFile(path, 'utf8')) as Record<string, unknown>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseDocument: OpenAPIV3.Document = {
|
||||||
openapi: '3.0.1',
|
openapi: '3.0.1',
|
||||||
info: {
|
info: {
|
||||||
title: 'Logto Core',
|
title: 'Logto Core',
|
||||||
version: '0.1.0',
|
description: 'Management APIs for Logto core service.',
|
||||||
|
version: 'Cloud',
|
||||||
},
|
},
|
||||||
paths: Object.fromEntries(pathMap),
|
paths: Object.fromEntries(pathMap),
|
||||||
components: { schemas: translationSchemas },
|
components: { schemas: translationSchemas },
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.body = document;
|
ctx.body = supplementDocuments.reduce(
|
||||||
|
(document, supplement) => deepmerge(document, supplement),
|
||||||
|
baseDocument
|
||||||
|
);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue