UNPKG

api-nexus

Version:

Generation of API documentation for the GraphQl and Rest API

329 lines (303 loc) 8.62 kB
import fsPromises from "fs/promises"; import { existsSync, mkdirSync } from "fs"; import path from "path"; import yaml from "js-yaml"; import fetch from "node-fetch"; const docFolder = path.join(process.cwd(), "doc", "data-sources"); const introspectionQuery = ` query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields { name description args { ...InputValue } type { ...TypeRef } } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues { name description } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } `; let [operationResult, isOperationExecuting] = [null, false]; const configLoader = async (configFilePath) => { if (operationResult !== null) { // console.log("Config already loaded. Returning cached result."); return operationResult; } if (isOperationExecuting) { return new Promise((resolve) => { const checkOperation = () => { if (operationResult !== null) { resolve(operationResult); } else { setTimeout(checkOperation, 100); } }; checkOperation(); }); } isOperationExecuting = true; try { operationResult = await processConfiguration(configFilePath); return operationResult; } catch (error) { throw error; } finally { isOperationExecuting = false; } }; const loadMetaDataFromFile = async (params, filePath, includeExample) => { const rootPath = `${docFolder}/${params}`; try { require(`${rootPath}/${filePath}`); const loadedFileData = require(`${rootPath}/${filePath}`); return loadedFileData; } catch (fileLoadError) { let errorMessage; if ( fileLoadError.code === "ENOENT" || fileLoadError.code === "MODULE_NOT_FOUND" ) { errorMessage = `Failed to load ::${filePath} \n- ${fileLoadError.message}`; if (includeExample) { try { mkdirSync(rootPath, { recursive: true }); const cwd = process.cwd(); const referencePath = `${cwd}/node_modules/api-nexus/examples`; const loadExamples = existsSync(referencePath) ? `${referencePath}/${filePath}` : `${cwd}/examples/${filePath}`; const sampleData = require(path.join(loadExamples)); fsPromises.writeFile( `${rootPath}/${filePath}`, JSON.stringify(sampleData, null, 4) ); return sampleData; } catch (exampleLoadError) { console.log("exampleLoadError", exampleLoadError); return {}; } } } else if (fileLoadError instanceof SyntaxError) { errorMessage = `Failed to load ::SyntaxError \n- ${fileLoadError.message}`; } else { console.log(`File Load Error::${fileLoadError}`); } console.log(`File Load Error::${errorMessage}`); return {}; } }; const loadIntrospectionFromUrl = async (graphUrl, folderType, overwrite) => { try { const introspectionResponse = await fetch(graphUrl, { method: "POST", headers: { "Content-Type": "application/json", authorization: "", }, body: JSON.stringify({ operationName: "IntrospectionQuery", query: introspectionQuery, }), }); if (!introspectionResponse.ok) { let errorMessage = "Unknown error"; if ( introspectionResponse.headers .get("content-type") ?.includes("application/json") ) { const { errors = [] } = await introspectionResponse.json(); errorMessage = errors?.length ? errors.map((error) => error?.message).join(", ") : errorMessage; console.error("Introspection request failed:", errors); } else { errorMessage = introspectionResponse?.statusText; } throw new Error( `HTTP Error!\nStatus: ${ introspectionResponse?.status },\nError-Cause in introspection load:: ${JSON.stringify(errorMessage)}` ); } const { data: introspectionData = null } = await introspectionResponse.json(); const introspectionFolderPath = path.join(docFolder, folderType); const introspectionFilePath = path.join( introspectionFolderPath, "introspection.json" ); try { await fsPromises.access(introspectionFolderPath); } catch (accessError) { if (accessError.code === "ENOENT") { // Directory does not exist, create it try { await fsPromises.mkdir(introspectionFolderPath, { recursive: true }); } catch (mkdirError) { console.error("Error creating directory:", mkdirError.message); } } else { // Handle other access errors console.error( "Error checking directory existence:", accessError.message ); } } // try { // await fsPromises.writeFile( // introspectionFilePath, // JSON.stringify(introspectionData ?? {}, null, 4) // ); // } catch (writeError) { // console.error("Error writing introspection file:", writeError.message); // } try { if (overwrite || !existsSync(introspectionFilePath)) { // Overwrite each time or write if the file doesn't exist await fsPromises.writeFile( introspectionFilePath, JSON.stringify(introspectionData ?? {}, null, 4) ); } else { try { const existingData = require(introspectionFilePath); return existingData; } catch (readError) { console.error("Error reading introspection file:", readError.message); return {}; } } } catch (writeError) { console.error("Error writing introspection file:", writeError.message); } return introspectionData; } catch (loadIntrospectionFromUrlError) { throw loadIntrospectionFromUrlError; } }; const replaceEnvironmentVariables = (yamlData) => { return yamlData.replace(/\${(.*?)}/g, (match, variableName) => { return process.env[variableName] || "Not Specified [Env]"; }); }; const readConfigFile = async (configFilePath) => { try { return await fsPromises.readFile(configFilePath, "utf8"); } catch (configLoadError) { if (configLoadError.code === "ENOENT") { throw new Error( 'The "config.yml" file is missing in the project root directory. Please make sure it exists.' ); } else { console.error(`Error reading config file: ${configLoadError.message}`); throw configLoadError; } } }; const processConfiguration = async (configFilePath) => { try { const yamlData = await readConfigFile(configFilePath); const replacedConfig = replaceEnvironmentVariables(yamlData); const jsonConfiguration = yaml.load(replacedConfig) || { apiNexus: { info: { includeInDocument: "both" } }, }; const { apiNexus: { graphQl = {} }, } = jsonConfiguration; const appContext = { introspection: graphQl?.introspection?.url ? await loadIntrospectionFromUrl( graphQl?.introspection?.url, "graph", graphQl?.introspection?.overWriteEachTime ?? false ) : await loadMetaDataFromFile( "graph", `introspection.json`, jsonConfiguration?.apiNexus?.includeExample ?? true ), graphMetaData: await loadMetaDataFromFile( "graph", `graphMetaData.json`, jsonConfiguration?.apiNexus?.includeExample ?? true ), restMetaData: await loadMetaDataFromFile( "rest", `restMetaData.json`, jsonConfiguration?.apiNexus?.includeExample ?? true ), }; return { ...appContext, config: jsonConfiguration }; } catch (error) { console.error("Error in processConfiguration:", error.message); throw error; } }; export { configLoader };