diff --git a/.changeset/stupid-badgers-check.md b/.changeset/stupid-badgers-check.md new file mode 100644 index 0000000..b3c6110 --- /dev/null +++ b/.changeset/stupid-badgers-check.md @@ -0,0 +1,5 @@ +--- +"@eventcatalog/generator-asyncapi": major +--- + +feat(plugin): asyncapi plugin now groups by service diff --git a/packages/generator-asyncapi/src/index.ts b/packages/generator-asyncapi/src/index.ts index d668606..0fddc45 100644 --- a/packages/generator-asyncapi/src/index.ts +++ b/packages/generator-asyncapi/src/index.ts @@ -24,6 +24,7 @@ import { defaultMarkdown as generateMarkdownForChannel, getChannelProtocols } fr import checkLicense from './checkLicense'; import { EventType, MessageOperations } from './types'; +import { join } from 'node:path'; const parser = new Parser(); @@ -33,6 +34,7 @@ const cliArgs = argv(process.argv.slice(2)); const optionsSchema = z.object({ licenseKey: z.string().optional(), + writeFilesToRoot: z.boolean().optional(), services: z.array( z.object({ id: z.string({ required_error: 'The service id is required. please provide the service id' }), @@ -111,18 +113,21 @@ export default async (config: any, options: Props) => { version: versionEvent, get: getEvent, addSchema: addSchemaToEvent, + collection: 'events', }, command: { write: writeCommand, version: versionCommand, get: getCommand, addSchema: addSchemaToCommand, + collection: 'commands', }, query: { write: writeQuery, version: versionQuery, get: getQuery, addSchema: addSchemaToQuery, + collection: 'queries', }, }; @@ -173,6 +178,14 @@ export default async (config: any, options: Props) => { let serviceSpecificationsFiles = []; let serviceMarkdown = generateMarkdownForService(document); + // Have to ../ as the SDK will put the files into hard coded folders + let servicePath = options.domain + ? path.join('../', 'domains', options.domain.id, 'services', service.id) + : path.join('../', 'services', service.id); + if (options.writeFilesToRoot) { + servicePath = service.id; + } + // Manage domain if (options.domain) { // Try and get the domain @@ -283,6 +296,7 @@ export default async (config: any, options: Props) => { version: versionMessage, get: getMessage, addSchema: addSchemaToMessage, + collection: folder, } = MESSAGE_OPERATIONS[eventType]; let messageMarkdown = generateMarkdownForMessage(document, message); @@ -290,6 +304,11 @@ export default async (config: any, options: Props) => { console.log(chalk.blue(`Processing message: ${getMessageName(message)} (v${messageVersion})`)); + let messagePath = join(servicePath, folder, message.id()); + if (options.writeFilesToRoot) { + messagePath = message.id(); + } + if (serviceOwnsMessageContract) { // Check if the message already exists in the catalog const catalogedMessage = await getMessage(message.id().toLowerCase(), 'latest'); @@ -321,7 +340,7 @@ export default async (config: any, options: Props) => { }, { override: true, - path: message.id(), + path: messagePath, } ); @@ -399,6 +418,7 @@ export default async (config: any, options: Props) => { ...(repository && { repository }), }, { + path: servicePath, override: true, } ); diff --git a/packages/generator-asyncapi/src/test/plugin.test.ts b/packages/generator-asyncapi/src/test/plugin.test.ts index 87a4504..271d3c8 100644 --- a/packages/generator-asyncapi/src/test/plugin.test.ts +++ b/packages/generator-asyncapi/src/test/plugin.test.ts @@ -904,7 +904,9 @@ describe('AsyncAPI EventCatalog Plugin', () => { it('when a message has a schema defined in the AsyncAPI file, the schema is documented in EventCatalog', async () => { await plugin(config, { services: [{ path: join(asyncAPIExamplesDir, 'simple.asyncapi.yml'), id: 'account-service' }] }); - const schema = await fs.readFile(join(catalogDir, 'events', 'UserSignedUp', 'schema.json')); + const schema = await fs.readFile( + join(catalogDir, 'services', 'account-service', 'events', 'UserSignedUp', 'schema.json') + ); expect(schema).toBeDefined(); }); @@ -1089,7 +1091,10 @@ describe('AsyncAPI EventCatalog Plugin', () => { expect(event).toBeDefined(); expect(event.schemaPath).toEqual('schema.avsc'); - const schema = await fs.readFile(join(catalogDir, 'events', 'lightMeasuredMessageAvro', 'schema.avsc'), 'utf-8'); + const schema = await fs.readFile( + join(catalogDir, 'services', 'test-service', 'events', 'lightMeasuredMessageAvro', 'schema.avsc'), + 'utf-8' + ); const parsedAsyncAPIFile = await fs.readFile( join(catalogDir, 'services', 'test-service', 'asyncapi-with-avro-expect-not-to-parse-schemas.yml'), 'utf-8' @@ -1188,7 +1193,10 @@ describe('AsyncAPI EventCatalog Plugin', () => { expect(event.schemaPath).toEqual('schema.avsc'); // Check file schema.avsc - const schema = await fs.readFile(join(catalogDir, 'events', 'userSignedUp', 'schema.avsc'), 'utf-8'); + const schema = await fs.readFile( + join(catalogDir, 'services', 'user-signup-api', 'events', 'userSignedUp', 'schema.avsc'), + 'utf-8' + ); const schemaParsed = JSON.parse(schema); expect(schemaParsed).toEqual({ type: 'record', @@ -1319,5 +1327,33 @@ describe('AsyncAPI EventCatalog Plugin', () => { expect(schema).toContain('x-parser-schema-id'); }); }); + + describe('writeFilesToRoot', () => { + it('if `writeFilesToRoot` is true, the files are written to the root of the service', async () => { + const { getService } = utils(catalogDir); + + await plugin(config, { + services: [{ path: join(asyncAPIExamplesDir, 'simple.asyncapi.yml'), id: 'account-service' }], + writeFilesToRoot: true, + }); + + const service = await getService('account-service', '1.0.0'); + expect(service.schemaPath).toEqual('simple.asyncapi.yml'); + + const events = await fs.readdir(join(catalogDir, 'events')); + expect(events).toEqual(['UserSignedOut', 'UserSignedUp']); + + const queries = await fs.readdir(join(catalogDir, 'queries')); + expect(queries).toEqual(['CheckEmailAvailability', 'GetUserByEmail']); + + const commands = await fs.readdir(join(catalogDir, 'commands')); + expect(commands).toEqual(['SignUpUser']); + + const services = await fs.readdir(join(catalogDir, 'services')); + expect(services).toEqual(['account-service']); + const file = await fs.readFile(join(catalogDir, 'services', 'account-service', 'simple.asyncapi.yml'), 'utf-8'); + expect(file).toBeDefined(); + }); + }); }); }); diff --git a/packages/generator-asyncapi/src/types.ts b/packages/generator-asyncapi/src/types.ts index 573cc5c..3bb53b1 100644 --- a/packages/generator-asyncapi/src/types.ts +++ b/packages/generator-asyncapi/src/types.ts @@ -4,6 +4,7 @@ export interface MessageOperations { version: (id: string) => Promise; get: (id: string, version: string) => Promise; addSchema: (id: string, schema: any, version: any) => Promise; + collection: string; } // Define valid event types