diff --git a/.changeset/techdocs-typescript-isnt-fun.md b/.changeset/techdocs-typescript-isnt-fun.md new file mode 100644 index 0000000000..9341c1c14a --- /dev/null +++ b/.changeset/techdocs-typescript-isnt-fun.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +It is no longer required to provide a generator and a preparer to the TechDocs +router factory when running TechDocs in the "recommended" (e.g. externally +prepared and generated docs) configuration. diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index 947439533a..3458a14307 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -14,7 +14,7 @@ import { PreparerBuilder } from '@backstage/techdocs-common'; import { PublisherBase } from '@backstage/techdocs-common'; // @public (undocumented) -export function createRouter({ preparers, generators, publisher, config, logger, discovery, }: RouterOptions): Promise; +export function createRouter(options: RouterOptions): Promise; export * from "@backstage/techdocs-common"; diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index eceae2e3d3..7563dfa2f9 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -31,7 +31,11 @@ import { Logger } from 'winston'; import { DocsBuilder } from '../DocsBuilder'; import { shouldCheckForUpdate } from '../DocsBuilder/BuildMetadataStorage'; -type RouterOptions = { +/** + * All of the required dependencies for running TechDocs in the "out-of-the-box" + * deployment configuration (prepare/generate/publish all in the Backend). + */ +type OutOfTheBoxDeploymentOptions = { preparers: PreparerBuilder; generators: GeneratorBuilder; publisher: PublisherBase; @@ -41,15 +45,39 @@ type RouterOptions = { config: Config; }; -export async function createRouter({ - preparers, - generators, - publisher, - config, - logger, - discovery, -}: RouterOptions): Promise { +/** + * Required dependencies for running TechDocs in the "recommended" deployment + * configuration (prepare/generate handled externally in CI/CD). + */ +type RecommendedDeploymentOptions = { + publisher: PublisherBase; + logger: Logger; + discovery: PluginEndpointDiscovery; + config: Config; +}; + +/** + * One of the two deployment configurations must be provided. + */ +type RouterOptions = + | RecommendedDeploymentOptions + | OutOfTheBoxDeploymentOptions; + +/** + * Typeguard to help createRouter() understand when we are in a "recommended" + * deployment vs. when we are in an out-of-the-box deployment configuration. + */ +function isOutOfTheBoxOption( + opt: RouterOptions, +): opt is OutOfTheBoxDeploymentOptions { + return (opt as OutOfTheBoxDeploymentOptions).preparers !== undefined; +} + +export async function createRouter( + options: RouterOptions, +): Promise { const router = Router(); + const { publisher, config, logger, discovery } = options; router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => { const { kind, namespace, name } = req.params; @@ -158,58 +186,63 @@ export async function createRouter({ }); return; } - const docsBuilder = new DocsBuilder({ - preparers, - generators, - publisher, - logger, - entity, - config, - }); - let foundDocs = false; - switch (publisherType) { - case 'local': - case 'awsS3': - case 'azureBlobStorage': - case 'openStackSwift': - case 'googleGcs': { - // This block should be valid for all storage implementations. So no need to duplicate in future, - // add the publisher type in the list here. - const updated = await docsBuilder.build(); - if (!updated) { - throw new NotModifiedError(); - } + // Set up a DocsBuilder if "out-of-the-box" configuration is provided. + if (isOutOfTheBoxOption(options)) { + const { preparers, generators } = options; + const docsBuilder = new DocsBuilder({ + preparers, + generators, + publisher, + logger, + entity, + config, + }); + let foundDocs = false; + switch (publisherType) { + case 'local': + case 'awsS3': + case 'azureBlobStorage': + case 'openStackSwift': + case 'googleGcs': { + // This block should be valid for all storage implementations. So no need to duplicate in future, + // add the publisher type in the list here. + const updated = await docsBuilder.build(); - // With a maximum of ~5 seconds wait, check if the files got published and if docs will be fetched - // on the user's page. If not, respond with a message asking them to check back later. - // The delay here is to make sure GCS/AWS/etc. registers newly uploaded files which is usually <1 second - for (let attempt = 0; attempt < 5; attempt++) { - if (await publisher.hasDocsBeenGenerated(entity)) { - foundDocs = true; - break; + if (!updated) { + throw new NotModifiedError(); } - await new Promise(r => setTimeout(r, 1000)); + + // With a maximum of ~5 seconds wait, check if the files got published and if docs will be fetched + // on the user's page. If not, respond with a message asking them to check back later. + // The delay here is to make sure GCS/AWS/etc. registers newly uploaded files which is usually <1 second + for (let attempt = 0; attempt < 5; attempt++) { + if (await publisher.hasDocsBeenGenerated(entity)) { + foundDocs = true; + break; + } + await new Promise(r => setTimeout(r, 1000)); + } + if (!foundDocs) { + logger.error( + 'Published files are taking longer to show up in storage. Something went wrong.', + ); + throw new NotFoundError( + 'Sorry! It took too long for the generated docs to show up in storage. Check back later.', + ); + } + + res + .status(201) + .json({ message: 'Docs updated or did not need updating' }); + break; } - if (!foundDocs) { - logger.error( - 'Published files are taking longer to show up in storage. Something went wrong.', - ); + + default: throw new NotFoundError( - 'Sorry! It took too long for the generated docs to show up in storage. Check back later.', + `Publisher type ${publisherType} is not supported by techdocs-backend docs builder.`, ); - } - - res - .status(201) - .json({ message: 'Docs updated or did not need updating' }); - break; } - - default: - throw new NotFoundError( - `Publisher type ${publisherType} is not supported by techdocs-backend docs builder.`, - ); } });