diff --git a/.changeset/lovely-tigers-look.md b/.changeset/lovely-tigers-look.md new file mode 100644 index 0000000000..5e7d230e6d --- /dev/null +++ b/.changeset/lovely-tigers-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-adr-backend': patch +--- + +Support MADR v3 format diff --git a/ADOPTERS.md b/ADOPTERS.md index 068928260d..8fa8d37c79 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -230,3 +230,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [OVO Energy](https://www.ovoenergy.com/) | [Michael Wizner](https://github.com/mwz), [Dan Laird](https://github.com/dlaird-ovo), [Samantha Betts](https://github.com/sammbetts) | Developer Experience Tool with an aim to to improve processes, boost productivity, finding of information/docs and building tech engagement throughout the business. | [MusicTribe](https://careers.musictribe.com/) | [Alex Ford](mailto:alex.j.ford@gmail.com), [Tiago Barbosa](https://github.com/t1agob) | We are starting to use Backstage as a developer portal to share API specifications and documentation, to quickly onboard new projects with the software templates, and to help developers discover software through the catalog. | [Cazoo](https://www.cazoo.co.uk/) | [Abz Mungul](https://www.linkedin.com/in/abzmungul/), [Scott Edwards](https://www.linkedin.com/in/scott-edwards-tech/) | We're assessing Backstage as our developer platform at Cazoo with a focus on reducing cognitive load for our engineers. We're currently aiming for 3 outcomes: creating visibility into service ownership across teams, improving the discoverability of event schemas and relationships, and improving the discoverability of technical documentation and best practices. | +| [Gumtree](https://www.gumtree.com.au) | [Kumar Gaurav](https://www.linkedin.com/in/kumargaurav517) | We are starting to use it as a single place to find all component information in a distributed architecture. | diff --git a/plugins/adr-backend/README.md b/plugins/adr-backend/README.md index 29fead7c84..4ffa9c63eb 100644 --- a/plugins/adr-backend/README.md +++ b/plugins/adr-backend/README.md @@ -89,7 +89,7 @@ indexBuilder.addCollator({ ### Parsing custom ADR document formats -By default, the `DefaultAdrCollatorFactory` will parse and index documents that follow the [MADR v2.x standard file name and template format](https://github.com/adr/madr/tree/2.1.2). If you use a different ADR format and file name convention, you can configure `DefaultAdrCollatorFactory` with custom `adrFilePathFilterFn` and `parser` options (see type definitions for details): +By default, the `DefaultAdrCollatorFactory` will parse and index documents that follow [MADR v3.0.0](https://github.com/adr/madr/tree/3.0.0) and [MADR v2.x](https://github.com/adr/madr/tree/2.1.2) standard file name and template format. If you use a different ADR format and file name convention, you can configure `DefaultAdrCollatorFactory` with custom `adrFilePathFilterFn` and `parser` options (see type definitions for details): ```ts DefaultAdrCollatorFactory.fromConfig({ diff --git a/plugins/adr-backend/src/search/madrParser.ts b/plugins/adr-backend/src/search/madrParser.ts index 30672028fe..c992ef125e 100644 --- a/plugins/adr-backend/src/search/madrParser.ts +++ b/plugins/adr-backend/src/search/madrParser.ts @@ -18,28 +18,71 @@ import { DateTime } from 'luxon'; import { marked } from 'marked'; import { MADR_DATE_FORMAT } from '@backstage/plugin-adr-common'; -export const madrParser = (content: string, dateFormat = MADR_DATE_FORMAT) => { - const tokens = marked.lexer(content); - if (!tokens.length) { - throw new Error('ADR has no content'); - } - - // First h1 header should contain ADR title - const adrTitle = ( +const getTitle = (tokens: marked.TokensList): string | undefined => { + return ( tokens.find( t => t.type === 'heading' && t.depth === 1, ) as marked.Tokens.Heading )?.text; +}; - // First list should contain status & date metadata (if defined) - const listTokens = (tokens.find(t => t.type === 'list') as marked.Tokens.List) - ?.items; - const adrStatus = listTokens +const getStatusForV3Format = (tokens: marked.TokensList): string | undefined => + ( + tokens.find( + t => + t.type === 'heading' && + t.depth === 2 && + t.text.toLowerCase().includes('status:'), + ) as marked.Tokens.Text + )?.text + .toLowerCase() + .split('\n') + .find(l => /^status:/.test(l)) + ?.replace(/^status:/i, '') + .trim() + .toLocaleLowerCase('en-US'); + +const getStatusForV2Format = (tokens: marked.TokensList): string | undefined => + (tokens.find(t => t.type === 'list') as marked.Tokens.List)?.items ?.find(t => /^status:/i.test(t.text)) ?.text.replace(/^status:/i, '') .trim() .toLocaleLowerCase('en-US'); +const getDateForV3Format = ( + tokens: marked.TokensList, + dateFormat: string, +): string | undefined => { + let adrDateTime: DateTime | undefined; + const dateLine = ( + tokens.find( + t => + t.type === 'heading' && + t.depth === 2 && + t.text.toLowerCase().includes('date:'), + ) as marked.Tokens.Text + )?.text + .toLowerCase() + .split('\n') + .find(l => /^date:/.test(l)); + + if (dateLine) { + adrDateTime = DateTime.fromFormat( + dateLine.replace(/^date:/i, '').trim(), + dateFormat, + ); + } + return adrDateTime?.isValid + ? adrDateTime.toFormat(MADR_DATE_FORMAT) + : undefined; +}; + +const getDateForV2Format = ( + tokens: marked.TokensList, + dateFormat: string, +): string | undefined => { + const listTokens = (tokens.find(t => t.type === 'list') as marked.Tokens.List) + ?.items; const adrDateTime = DateTime.fromFormat( listTokens ?.find(t => /^date:/i.test(t.text)) @@ -47,13 +90,30 @@ export const madrParser = (content: string, dateFormat = MADR_DATE_FORMAT) => { .trim() ?? '', dateFormat, ); - const adrDate = adrDateTime?.isValid + return adrDateTime?.isValid ? adrDateTime.toFormat(MADR_DATE_FORMAT) : undefined; +}; + +const getStatus = (tokens: marked.TokensList): string | undefined => { + return getStatusForV3Format(tokens) ?? getStatusForV2Format(tokens); +}; +const getDate = ( + tokens: marked.TokensList, + dateFormat: string, +): string | undefined => + getDateForV3Format(tokens, dateFormat) ?? + getDateForV2Format(tokens, dateFormat); + +export const madrParser = (content: string, dateFormat = MADR_DATE_FORMAT) => { + const tokens = marked.lexer(content); + if (!tokens.length) { + throw new Error('ADR has no content'); + } return { - title: adrTitle, - status: adrStatus, - date: adrDate, + title: getTitle(tokens), + status: getStatus(tokens), + date: getDate(tokens, dateFormat), }; };