mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 17:42:57 +00:00
feat: add openapi gen to core
This commit is contained in:
@@ -14,10 +14,10 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "webpack-bundle-analyzer www/stats.json",
|
"analyze": "webpack-bundle-analyzer www/stats.json",
|
||||||
"build:prod": "ng build --configuration=production",
|
"build": "ng build",
|
||||||
"build:analyze": "npm run build:stats && npm run analyze",
|
"build:analyze": "npm run build:stats && npm run analyze",
|
||||||
"build:android": "ionic capacitor build android --no-open && cd android && ./gradlew clean assembleDebug && cd ..",
|
"build:android": "ionic capacitor build android --no-open && cd android && ./gradlew clean assembleDebug && cd ..",
|
||||||
"build": "ng build",
|
"build:prod": "ng build --configuration=production",
|
||||||
"build:stats": "ng build --configuration=production --stats-json",
|
"build:stats": "ng build --configuration=production --stats-json",
|
||||||
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
|
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
|
||||||
"check-icons": "ts-node scripts/check-icon-correctness.ts",
|
"check-icons": "ts-node scripts/check-icon-correctness.ts",
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"syncpack": "syncpack list-mismatches && syncpack lint-semver-ranges",
|
"syncpack": "syncpack list-mismatches && syncpack lint-semver-ranges",
|
||||||
"syncpack:fix": "syncpack format && syncpack fix-mismatches",
|
"syncpack:fix": "syncpack format && syncpack fix-mismatches",
|
||||||
"test": "trap 'node coverage.mjs' EXIT && dotenv -c -- turbo run test --filter=!@openstapps/app",
|
"test": "trap 'node coverage.mjs' EXIT && dotenv -c -- turbo run test --filter=!@openstapps/app",
|
||||||
"test:skip": "dotenv -c -- turbo-ignore --task='test' --filter=!@openstapps/app",
|
|
||||||
"test:integration": "dotenv -c -- turbo run test:integration",
|
"test:integration": "dotenv -c -- turbo run test:integration",
|
||||||
|
"test:skip": "dotenv -c -- turbo-ignore --task='test' --filter=!@openstapps/app",
|
||||||
"turboify-pipeline": "node deploy.mjs backend,api-cli,database && pnpm build:full && pnpm test && pnpm run docs"
|
"turboify-pipeline": "node deploy.mjs backend,api-cli,database && pnpm build:full && pnpm test && pnpm run docs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
"@types/cli-progress": "3.11.0",
|
"@types/cli-progress": "3.11.0",
|
||||||
"@types/express": "4.17.17",
|
"@types/express": "4.17.17",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
"@types/junit-report-builder": "3.0.0",
|
|
||||||
"@types/json-schema": "7.0.11",
|
"@types/json-schema": "7.0.11",
|
||||||
|
"@types/junit-report-builder": "3.0.0",
|
||||||
"@types/mocha": "10.0.1",
|
"@types/mocha": "10.0.1",
|
||||||
"@types/node": "18.15.3",
|
"@types/node": "18.15.3",
|
||||||
"@types/wait-on": "5.3.1",
|
"@types/wait-on": "5.3.1",
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ async function retrieveItems(api: ConnectorClient, suite: junit.TestSuite, error
|
|||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
for (const uid of localItemMap.keys()) {
|
for (const {uid, type} of localItemMap.values()) {
|
||||||
await runTest(
|
await runTest(
|
||||||
`Should find ${uid}`,
|
`Should find ${type} (${uid})`,
|
||||||
async () => {
|
async () => {
|
||||||
singleItemSearchRequest.filter!.arguments.value = uid;
|
singleItemSearchRequest.filter!.arguments.value = uid;
|
||||||
const searchResponse = await api.search(singleItemSearchRequest);
|
const searchResponse = await api.search(singleItemSearchRequest);
|
||||||
@@ -139,7 +139,7 @@ async function retrieveItems(api: ConnectorClient, suite: junit.TestSuite, error
|
|||||||
async function compareItems(suite: junit.TestSuite, errors: string[]) {
|
async function compareItems(suite: junit.TestSuite, errors: string[]) {
|
||||||
for (const localThing of localItemMap.values()) {
|
for (const localThing of localItemMap.values()) {
|
||||||
await runTest(
|
await runTest(
|
||||||
`Should be the same for ${localThing.uid}`,
|
`Should be the same for ${localThing.type} (${localThing.uid})`,
|
||||||
async () => {
|
async () => {
|
||||||
/* istanbul ignore next retrieveItems will throw before*/
|
/* istanbul ignore next retrieveItems will throw before*/
|
||||||
if (!remoteItemMap.has(localThing.uid)) {
|
if (!remoteItemMap.has(localThing.uid)) {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["//"],
|
|
||||||
"pipeline": {
|
|
||||||
"deploy": {
|
|
||||||
"dependsOn": ["@openstapps/api-cli#build"],
|
|
||||||
"outputs": [".deploy/api-cli"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -103,7 +103,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"eslintIgnore": [
|
"eslintIgnore": [
|
||||||
"resources",
|
"resources"
|
||||||
"openapi"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
import {Logger} from '@openstapps/logger';
|
import {Logger} from '@openstapps/logger';
|
||||||
import {Command} from 'commander';
|
import {Command} from 'commander';
|
||||||
import {existsSync, readFileSync, writeFileSync} from 'fs';
|
import {existsSync, readFileSync, writeFileSync} from 'fs';
|
||||||
import {copy} from 'fs-extra';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {lightweightDefinitionsFromPath, lightweightProjectFromPath} from '@openstapps/easy-ast';
|
import {lightweightDefinitionsFromPath, lightweightProjectFromPath} from '@openstapps/easy-ast';
|
||||||
import {openapi3Template} from './resources/openapi-303-template.js';
|
import {openapi3Template} from './resources/openapi-303-template.js';
|
||||||
@@ -57,7 +56,7 @@ commander
|
|||||||
// get absolute paths
|
// get absolute paths
|
||||||
const sourcePath = path.resolve(relativeSourceBundlePath);
|
const sourcePath = path.resolve(relativeSourceBundlePath);
|
||||||
const outDirectoryPath = path.resolve(relativeOutDirectoryPath);
|
const outDirectoryPath = path.resolve(relativeOutDirectoryPath);
|
||||||
const outDirectorySchemasPath = path.join(outDirectoryPath, 'schemas');
|
const outDirectorySchemasPath = path.join(outDirectoryPath, 'schema');
|
||||||
|
|
||||||
// get information about routes
|
// get information about routes
|
||||||
const routes = await gatherRouteInformation(sourcePath);
|
const routes = await gatherRouteInformation(sourcePath);
|
||||||
@@ -82,9 +81,6 @@ commander
|
|||||||
// initialize json output
|
// initialize json output
|
||||||
const output = openapi3Template;
|
const output = openapi3Template;
|
||||||
|
|
||||||
// names of the schemas to copy
|
|
||||||
const schemasToCopy: string[] = [];
|
|
||||||
|
|
||||||
// generate documentation for all routes
|
// generate documentation for all routes
|
||||||
for (const routeWithMetaInformation of routes) {
|
for (const routeWithMetaInformation of routes) {
|
||||||
routeWithMetaInformation.tags = [capitalize(routeWithMetaInformation.route.urlPath.split('/')[1])];
|
routeWithMetaInformation.tags = [capitalize(routeWithMetaInformation.route.urlPath.split('/')[1])];
|
||||||
@@ -92,29 +88,10 @@ commander
|
|||||||
output.paths[routeWithMetaInformation.route.urlPath] = generateOpenAPIForRoute(
|
output.paths[routeWithMetaInformation.route.urlPath] = generateOpenAPIForRoute(
|
||||||
routeWithMetaInformation,
|
routeWithMetaInformation,
|
||||||
path.relative(relativeOutDirectoryPath, outDirectorySchemasPath),
|
path.relative(relativeOutDirectoryPath, outDirectorySchemasPath),
|
||||||
schemasToCopy,
|
|
||||||
tagsToKeep,
|
tagsToKeep,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy schema json schema files
|
|
||||||
try {
|
|
||||||
if (!existsSync(outDirectorySchemasPath)) {
|
|
||||||
await mkdir(outDirectorySchemasPath, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const fileName of schemasToCopy) {
|
|
||||||
await copy(
|
|
||||||
path.join(sourcePath, 'schema', `${fileName}.json`),
|
|
||||||
path.join(outDirectorySchemasPath, `${fileName}.json`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await Logger.error(error);
|
|
||||||
process.exit(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write openapi object to file (prettified)
|
// write openapi object to file (prettified)
|
||||||
writeFileSync(path.join(outDirectoryPath, 'openapi.json'), JSON.stringify(output, undefined, 2));
|
writeFileSync(path.join(outDirectoryPath, 'openapi.json'), JSON.stringify(output, undefined, 2));
|
||||||
|
|
||||||
|
|||||||
@@ -79,20 +79,16 @@ export async function gatherRouteInformation(path: string): Promise<RouteWithMet
|
|||||||
* Generate documentation snippet for one route
|
* Generate documentation snippet for one route
|
||||||
* @param routeWithInfo A route instance with its meta information
|
* @param routeWithInfo A route instance with its meta information
|
||||||
* @param outDirectorySchemasPath Path to directory that will contain relevant schemas for the route
|
* @param outDirectorySchemasPath Path to directory that will contain relevant schemas for the route
|
||||||
* @param schemasToCopy Schemas identified as relevant for this route
|
|
||||||
* @param tagsToKeep Tags / keywords that can be used for grouping routes
|
* @param tagsToKeep Tags / keywords that can be used for grouping routes
|
||||||
*/
|
*/
|
||||||
export function generateOpenAPIForRoute(
|
export function generateOpenAPIForRoute(
|
||||||
routeWithInfo: RouteWithMetaInformation,
|
routeWithInfo: RouteWithMetaInformation,
|
||||||
outDirectorySchemasPath: string,
|
outDirectorySchemasPath: string,
|
||||||
schemasToCopy: string[],
|
|
||||||
tagsToKeep: string[],
|
tagsToKeep: string[],
|
||||||
): OpenAPIV3.PathItemObject {
|
): OpenAPIV3.PathItemObject {
|
||||||
const route = routeWithInfo.route;
|
const route = routeWithInfo.route;
|
||||||
const openapiPath: OpenAPIV3.PathItemObject = {};
|
const openapiPath: OpenAPIV3.PathItemObject = {};
|
||||||
|
|
||||||
schemasToCopy.push(route.requestBodyName, route.responseBodyName);
|
|
||||||
|
|
||||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods] = {
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods] = {
|
||||||
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gim, '')),
|
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gim, '')),
|
||||||
description: routeWithInfo.description.text,
|
description: routeWithInfo.description.text,
|
||||||
@@ -133,7 +129,6 @@ export function generateOpenAPIForRoute(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const error of route.errors) {
|
for (const error of route.errors) {
|
||||||
schemasToCopy.push(error.name);
|
|
||||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![error.statusCode] = {
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![error.statusCode] = {
|
||||||
description:
|
description:
|
||||||
error.message ?? capitalize(error.name.replaceAll(/([A-Z][a-z])/g, ' $1').replace('SC ', '')),
|
error.message ?? capitalize(error.name.replaceAll(/([A-Z][a-z])/g, ' $1').replace('SC ', '')),
|
||||||
@@ -155,7 +150,7 @@ export function generateOpenAPIForRoute(
|
|||||||
required: true,
|
required: true,
|
||||||
schema: {
|
schema: {
|
||||||
// TODO make this less of a hack and search copied schemas for the first occurring definition
|
// TODO make this less of a hack and search copied schemas for the first occurring definition
|
||||||
$ref: `schemas/SCSearchResponse.json#/definitions/${schemaDefinition}`,
|
$ref: `schema/SCSearchResponse.json#/definitions/${schemaDefinition}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup-node --dts && pnpm run mappings && pnpm run schema",
|
"build": "tsup-node --dts && pnpm run mappings && pnpm run schema && pnpm run openapi",
|
||||||
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
||||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
"lint:fix": "eslint --fix --ext .ts src/",
|
"lint:fix": "eslint --fix --ext .ts src/",
|
||||||
"mappings": "openstapps-es-mapping-generator mapping ../core/src -i minlength,pattern,see,tjs-format -m lib/mappings/mappings.json -a lib/mappings/aggregations.json",
|
"mappings": "openstapps-es-mapping-generator mapping ../core/src -i minlength,pattern,see,tjs-format -m lib/mappings/mappings.json -a lib/mappings/aggregations.json",
|
||||||
"mappings-integration": "openstapps-es-mapping-generator put-es-templates lib/mappings/mappings.json http://elasticsearch:9200/",
|
"mappings-integration": "openstapps-es-mapping-generator put-es-templates lib/mappings/mappings.json http://elasticsearch:9200/",
|
||||||
|
"openapi": "openstapps-core-tools openapi lib lib && node -e \"assert(JSON.parse(require('fs').readFileSync('lib/openapi.json', 'utf8')).paths['/search'] !== undefined)\"",
|
||||||
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/@openstapps/core-tools/lib/app.js schema src lib/schema",
|
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/@openstapps/core-tools/lib/app.js schema src lib/schema",
|
||||||
"test": "c8 mocha"
|
"test": "c8 mocha"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class LightweightDefinitionBuilder {
|
|||||||
esModuleInterop: true,
|
esModuleInterop: true,
|
||||||
experimentalDecorators: true,
|
experimentalDecorators: true,
|
||||||
inlineSourceMap: true,
|
inlineSourceMap: true,
|
||||||
module: ts.ModuleKind.CommonJS,
|
module: ts.ModuleKind.NodeNext,
|
||||||
strict: true,
|
strict: true,
|
||||||
target: ts.ScriptTarget.ES2015,
|
target: ts.ScriptTarget.ES2015,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {LightweightDefinition} from './lightweight-definition.js';
|
|||||||
*/
|
*/
|
||||||
function buildIndex(project: LightweightProject): Record<string, string> {
|
function buildIndex(project: LightweightProject): Record<string, string> {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.values(project).flatMap((definitions, file) =>
|
Object.entries(project).flatMap(([file, definitions]) =>
|
||||||
Object.keys(definitions).map(definition => [definition, file.toString()]),
|
Object.keys(definitions).map(definition => [definition, file.toString()]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user