feat: support custom default plugins for techdocs

Signed-off-by: Dominik Schwank <dominik.schwank@sda.se>
This commit is contained in:
Dominik Schwank
2023-07-17 16:23:14 +02:00
parent dcd7e1622c
commit 10a86bd4ae
8 changed files with 102 additions and 14 deletions
+18
View File
@@ -0,0 +1,18 @@
---
'@backstage/plugin-techdocs-backend': minor
'@backstage/plugin-techdocs-node': minor
---
Add support for default mkdocs plugins.
So far only `techdocs-core` was added as default to the list of mkdocs plugins. In case you use a
custom image for techdocs, you also might want to add custom default plugins for mkdocs.
With this change one can do that - example:
```yaml
techdocs:
generator:
mkdocs:
defaultPlugins:
- section-index
```
+9
View File
@@ -49,6 +49,15 @@ techdocs:
# will be broken in these scenarios.
legacyCopyReadmeMdToIndexMd: false
# (Optional) Configures the default plugins which should be added
# automatically to every mkdocs.yaml file. This simplifies the usage as
# e.g. styling plugins can be added once for all.
# Make sure that the defined plugins are installed locally / in the Docker
# image.
# By default, only the techdocs-core plugin will be added (except if
# omitTechdocsCorePlugin: true).
defaultPlugins: ['techdocs-core']
# techdocs.builder can be either 'local' or 'external'.
# Using the default build strategy, if builder is set to 'local' and you open a TechDocs page,
# techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwards.
+5
View File
@@ -57,6 +57,11 @@ export interface Config {
* will be broken in these scenarios.
*/
legacyCopyReadmeMdToIndexMd?: boolean;
/**
* List of mkdocs plugins which should be added as default to all mkdocs.yml files.
*/
defaultPlugins?: string[];
};
};
@@ -33,7 +33,7 @@ import {
} from './helpers';
import {
patchMkdocsYmlPreBuild,
pathMkdocsYmlWithTechdocsPlugin,
patchMkdocsYmlWithPlugins,
} from './mkdocsPatchers';
import yaml from 'js-yaml';
@@ -310,7 +310,7 @@ describe('helpers', () => {
});
});
describe('pathMkdocsYmlWithTechdocsPlugin', () => {
describe('patchMkdocsYmlWithPlugins', () => {
beforeEach(() => {
mockFs({
'/mkdocs_with_techdocs_plugin.yml': mkdocsYmlWithTechdocsPlugins,
@@ -319,7 +319,7 @@ describe('helpers', () => {
});
});
it('should not add additional plugins if techdocs exists already in mkdocs file', async () => {
await pathMkdocsYmlWithTechdocsPlugin(
await patchMkdocsYmlWithPlugins(
'/mkdocs_with_techdocs_plugin.yml',
mockLogger,
);
@@ -334,7 +334,7 @@ describe('helpers', () => {
expect(parsedYml.plugins).toContain('techdocs-core');
});
it("should add the needed plugin if it doesn't exist in mkdocs file", async () => {
await pathMkdocsYmlWithTechdocsPlugin(
await patchMkdocsYmlWithPlugins(
'/mkdocs_without_plugins.yml',
mockLogger,
);
@@ -347,7 +347,7 @@ describe('helpers', () => {
expect(parsedYml.plugins).toContain('techdocs-core');
});
it('should not override existing plugins', async () => {
await pathMkdocsYmlWithTechdocsPlugin(
await patchMkdocsYmlWithPlugins(
'/mkdocs_with_additional_plugins.yml',
mockLogger,
);
@@ -362,6 +362,23 @@ describe('helpers', () => {
expect(parsedYml.plugins).toContain('not-techdocs-core');
expect(parsedYml.plugins).toContain('also-not-techdocs-core');
});
it('should add all provided default plugins', async () => {
await patchMkdocsYmlWithPlugins(
'/mkdocs_with_additional_plugins.yml',
mockLogger,
['techdocs-core', 'custom-plugin'],
);
const updatedMkdocsYml = await fs.readFile(
'/mkdocs_with_additional_plugins.yml',
);
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
plugins: string[];
};
expect(parsedYml.plugins).toHaveLength(4);
expect(parsedYml.plugins).toContain('techdocs-core');
expect(parsedYml.plugins).toContain('custom-plugin');
});
});
describe('patchIndexPreBuild', () => {
@@ -138,27 +138,34 @@ export const patchMkdocsYmlPreBuild = async (
* Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.
*
* List of tasks:
* - Add techdocs-core plugin to mkdocs file if it doesn't exist
* - Add all provided default plugins
*
* This function will not throw an error since this is not critical to the whole TechDocs pipeline.
* Instead it will log warnings if there are any errors in reading, parsing or writing YAML.
*
* @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site
* @param logger - A logger instance
* @param defaultPlugins - List of default mkdocs plugins
*/
export const pathMkdocsYmlWithTechdocsPlugin = async (
export const patchMkdocsYmlWithPlugins = async (
mkdocsYmlPath: string,
logger: Logger,
defaultPlugins: string[] = ['techdocs-core'],
) => {
await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {
// Modify mkdocs.yaml to contain the needed techdocs-core plugin if it is not there
// Modify mkdocs.yaml to contain the required default plugins
if (!('plugins' in mkdocsYml)) {
mkdocsYml.plugins = ['techdocs-core'];
mkdocsYml.plugins = defaultPlugins;
return true;
}
if (mkdocsYml.plugins && !mkdocsYml.plugins.includes('techdocs-core')) {
mkdocsYml.plugins.push('techdocs-core');
if (
mkdocsYml.plugins &&
!defaultPlugins.every(plugin => mkdocsYml.plugins!.includes(plugin))
) {
mkdocsYml.plugins = [
...new Set([...mkdocsYml.plugins, ...defaultPlugins]),
];
return true;
}
return false;
@@ -157,4 +157,24 @@ describe('readGeneratorConfig', () => {
legacyCopyReadmeMdToIndexMd: true,
});
});
it('should read the default plugins config', () => {
const config = new ConfigReader({
techdocs: {
generator: {
runIn: 'docker',
dockerImage: 'my-org/techdocs',
pullImage: false,
mkdocs: { defaultPlugins: ['mkdocs-custom-plugin'] },
},
},
});
expect(readGeneratorConfig(config, logger)).toEqual({
runIn: 'docker',
dockerImage: 'my-org/techdocs',
pullImage: false,
defaultPlugins: ['mkdocs-custom-plugin'],
});
});
});
@@ -33,7 +33,7 @@ import {
import {
patchMkdocsYmlPreBuild,
pathMkdocsYmlWithTechdocsPlugin,
patchMkdocsYmlWithPlugins,
} from './mkdocsPatchers';
import {
GeneratorBase,
@@ -121,10 +121,18 @@ export class TechdocsGenerator implements GeneratorBase {
await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir });
}
if (!this.options.omitTechdocsCoreMkdocsPlugin) {
await pathMkdocsYmlWithTechdocsPlugin(mkdocsYmlPath, childLogger);
// patch the list of mkdocs plugins
const defaultPlugins = this.options.defaultPlugins ?? [];
if (
!this.options.omitTechdocsCoreMkdocsPlugin &&
!defaultPlugins.includes('techdocs-core')
) {
defaultPlugins.push('techdocs-core');
}
await patchMkdocsYmlWithPlugins(mkdocsYmlPath, childLogger, defaultPlugins);
// Directories to bind on container
const mountDirs = {
[inputDir]: '/input',
@@ -233,5 +241,8 @@ export function readGeneratorConfig(
legacyCopyReadmeMdToIndexMd: config.getOptionalBoolean(
'techdocs.generator.mkdocs.legacyCopyReadmeMdToIndexMd',
),
defaultPlugins: config.getOptionalStringArray(
'techdocs.generator.mkdocs.defaultPlugins',
),
};
}
@@ -41,6 +41,7 @@ export type GeneratorConfig = {
pullImage?: boolean;
omitTechdocsCoreMkdocsPlugin?: boolean;
legacyCopyReadmeMdToIndexMd?: boolean;
defaultPlugins?: string[];
};
/**