diff --git a/tools/bundler/index.js b/tools/bundler/index.js index c405b2e4..2975a20b 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -1,27 +1,32 @@ const path = require('path'); const fs = require('fs'); const traverse = require('json-schema-traverse'); +const { url } = require('inspector'); const definitionsDirectory = path.resolve(__dirname, '../../definitions'); -const commonSchemasDirectory = path.resolve(__dirname, '../../common'); const bindingsDirectory = path.resolve(__dirname, '../../bindings'); +const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); +console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; +const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; +const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i + +// definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. +const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i /** * Function to load all the core AsyncAPI spec definition (except the root asyncapi schema, as that will be loaded later) into the bundler. */ async function loadDefinitions(bundler, versionDir) { const definitions = await fs.promises.readdir(versionDir); - const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi');}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); + const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi')}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); const definitionJson = definitionFiles.map((file) => JSON.parse(file)); for (const jsonFile of definitionJson) { @@ -40,35 +45,39 @@ async function loadDefinitions(bundler, versionDir) { } } + /** - * Function to load all the binding version schemas into the bundler + * Function to load all schemas into bundler, by "type" you specify if these are "bindings" or "extensions" */ -async function loadBindings(bundler) { - const bindingDirectories = await fs.promises.readdir(bindingsDirectory); - for (const bindingDirectory of bindingDirectories) { - const bindingVersionDirectories = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory)); - const bindingVersionDirectoriesFiltered = bindingVersionDirectories.filter((file) => fs.lstatSync(path.resolve(bindingsDirectory, bindingDirectory, file)).isDirectory()); - for (const bindingVersionDirectory of bindingVersionDirectoriesFiltered) { - const bindingFiles = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory)); - const bindingFilesFiltered = bindingFiles.filter((bindingFile) => path.extname(bindingFile) === '.json').map((bindingFile) => path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory, bindingFile)); - for (const bindingFile of bindingFilesFiltered) { - const bindingFileContent = require(bindingFile); - bundler.add(bindingFileContent); - } - } +async function loadSchemas(bundler, type) { + + let directory; + + switch (type) { + case "bindings": + directory = bindingsDirectory; + break; + case "extensions": + directory = extensionsDirectory; + break; + default: + console.error("Invalid input. I'm not going to assume if you want bindings or extensions - these are different beasts."); } -} -async function loadCommonSchemas(bundler) { - // Add common schemas to all versions - const commonSchemas = await fs.promises.readdir(commonSchemasDirectory); - const commonSchemaFiles = commonSchemas.map((file) => path.resolve(commonSchemasDirectory, file)); - for (const commonSchemaFile of commonSchemaFiles) { - const commonSchemaFileContent = require(commonSchemaFile); - bundler.add(commonSchemaFileContent); + const directories = await fs.promises.readdir(directory); + for (const nestedDir of directories) { + const versionDirectories = await fs.promises.readdir(path.resolve(directory, nestedDir)); + const versionDirectoriesFiltered = versionDirectories.filter((file) => fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory()); + for (const versionDir of versionDirectoriesFiltered) { + const files = await fs.promises.readdir(path.resolve(directory, nestedDir, versionDir)); + const filesFiltered = files.filter((file) => path.extname(file) === '.json').map((file) => path.resolve(directory, nestedDir, versionDir, file)); + for (const filteredFile of filesFiltered) { + const fileContent = require(filteredFile); + bundler.add(fileContent); + } + } } } - /** * When run, go through all versions that have split definitions and bundles them together. */ @@ -80,15 +89,15 @@ async function loadCommonSchemas(bundler) { } console.log(`The following versions have separate definitions: ${versions.join(',')}`); for (const version of versions) { - const Bundler = require('@hyperjump/json-schema-bundle'); - try { + const Bundler = require("@hyperjump/json-schema-bundle"); + try{ console.log(`Bundling the following version together: ${version}`); const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); const versionDir = path.resolve(definitionsDirectory, version); await loadDefinitions(Bundler, versionDir); - await loadCommonSchemas(Bundler); - await loadBindings(Bundler); + await loadSchemas(Bundler, 'bindings'); + await loadSchemas(Bundler, 'extensions'); const filePathToBundle = `file://${versionDir}/asyncapi.json`; const fileToBundle = await Bundler.get(filePathToBundle); @@ -97,7 +106,7 @@ async function loadCommonSchemas(bundler) { * bundling schemas into one file with $id */ const bundledSchemaWithId = await Bundler.bundle(fileToBundle); - bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description !== undefined && bundledSchemaWithId.description !== null ? bundledSchemaWithId.description : ''}`; + bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description ?? ''}`; console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); @@ -108,7 +117,7 @@ async function loadCommonSchemas(bundler) { const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); - } catch (e) { + }catch(e) { throw new Error(e); } } @@ -118,6 +127,7 @@ async function loadCommonSchemas(bundler) { /** * Extract file data from reference file path */ + async function loadRefProperties(filePath) { const schemaPath = filePath.$ref; // first we need to turn the path to an absolute file path instead of a generic url @@ -126,23 +136,24 @@ async function loadRefProperties(filePath) { try { const data = await fs.promises.readFile(`../../examples${versionPath}`); return JSON.parse(data); - } catch (e) { - throw new Error(e); + }catch(e) { + throw new Error(e); + } } -} /** * we first update definitions from URL to normal names * than update refs to point to new definitions, always inline never remote */ function modifyRefsAndDefinitions(bundledSchema) { + //first we need to improve names of the definitions from URL to their names for (const def of Object.keys(bundledSchema.definitions)) { const newDefName = getDefinitionName(def); //creating copy of definition under new name so later definition stored under URL name can be removed bundledSchema.definitions[newDefName] = bundledSchema.definitions[def]; - delete bundledSchema.definitions[def]; + delete bundledSchema.definitions[def] } traverse(bundledSchema, replaceRef); @@ -150,7 +161,7 @@ function modifyRefsAndDefinitions(bundledSchema) { traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); - return bundledSchema; + return bundledSchema } /** @@ -167,8 +178,12 @@ function getDefinitionName(def) { const result = bindingsRegex.exec(def); if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } + if (def.startsWith('http://asyncapi.com/extensions')) { + const result = extensionsRegex.exec(def); + if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; + } - return path.basename(def, '.json'); + return path.basename(def, '.json') } /** @@ -177,11 +192,10 @@ function getDefinitionName(def) { */ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com - delete schema.$id; - - //traversing should take place only in case of schemas with refs - if (schema.$ref === undefined) return; - + delete schema.$id + + //traversing shoudl take place only in case of schemas with refs + if (schema.$ref === undefined ) return; // updating refs that are related to remote URL refs that need to be update and point to inlined versions if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } @@ -190,12 +204,11 @@ function replaceRef(schema) { * this is a callback used when traversing through json schema * to fix avro schema definitions to point to right direction */ -function updateAvro(schema) { - //traversing should take place only in case of schemas with refs +function updateAvro(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( - /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', '#/definitions/avroSchema_v1/definitions/' ); @@ -205,8 +218,8 @@ function updateAvro(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateOpenApi(schema) { - //traversing should take place only in case of schemas with refs +function updateOpenApi(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; @@ -224,8 +237,8 @@ function updateOpenApi(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateJsonSchema(schema) { - //traversing should take place only in case of schemas with refs +function updateJsonSchema(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace(