feat(techdocs): add app-config option to disable external font download

Signed-off-by: Karthik <karthik.jk11@gmail.com>
This commit is contained in:
Karthik
2025-11-21 10:26:20 +05:30
parent 292b6431a2
commit 5ef8d166cb
7 changed files with 188 additions and 2 deletions
@@ -37,6 +37,7 @@ import {
patchMkdocsYmlPreBuild,
patchMkdocsYmlWithPlugins,
sanitizeMkdocsYml,
patchMkdocsYmlWithFontDisabled
} from './mkdocsPatchers';
import yaml from 'js-yaml';
@@ -468,6 +469,101 @@ describe('helpers', () => {
});
});
describe('patchMkdocsYmlWithFontDisabled', () => {
beforeEach(() => {
mockDir.setContent({
'mkdocs_without_theme.yml': `site_name: Test Site
docs_dir: docs
`,
'mkdocs_with_theme_no_font.yml': `site_name: Test Site
docs_dir: docs
theme:
name: material
`,
'mkdocs_with_theme_font_true.yml': `site_name: Test Site
docs_dir: docs
theme:
name: material
font: true
`,
'mkdocs_with_theme_font_false.yml': `site_name: Test Site
docs_dir: docs
theme:
name: material
font: false
`,
});
});
it('should create theme section with font disabled when no theme exists', async () => {
await patchMkdocsYmlWithFontDisabled(
mockDir.resolve('mkdocs_without_theme.yml'),
mockLogger,
);
const updatedMkdocsYml = await fs.readFile(
mockDir.resolve('mkdocs_without_theme.yml'),
);
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
theme?: { name?: string; font?: boolean };
};
expect(parsedYml.theme).toBeDefined();
expect(parsedYml.theme?.name).toBe('material');
expect(parsedYml.theme?.font).toBe(false);
});
it('should add font: false when theme exists but font is not configured', async () => {
await patchMkdocsYmlWithFontDisabled(
mockDir.resolve('mkdocs_with_theme_no_font.yml'),
mockLogger,
);
const updatedMkdocsYml = await fs.readFile(
mockDir.resolve('mkdocs_with_theme_no_font.yml'),
);
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
theme?: { name?: string; font?: boolean };
};
expect(parsedYml.theme).toBeDefined();
expect(parsedYml.theme?.name).toBe('material');
expect(parsedYml.theme?.font).toBe(false);
});
it('should not override font when font is already set to true', async () => {
await patchMkdocsYmlWithFontDisabled(
mockDir.resolve('mkdocs_with_theme_font_true.yml'),
mockLogger,
);
const updatedMkdocsYml = await fs.readFile(
mockDir.resolve('mkdocs_with_theme_font_true.yml'),
);
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
theme?: { name?: string; font?: boolean };
};
expect(parsedYml.theme).toBeDefined();
expect(parsedYml.theme?.name).toBe('material');
expect(parsedYml.theme?.font).toBe(true);
});
it('should not override font when font is already set to false', async () => {
await patchMkdocsYmlWithFontDisabled(
mockDir.resolve('mkdocs_with_theme_font_false.yml'),
mockLogger,
);
const updatedMkdocsYml = await fs.readFile(
mockDir.resolve('mkdocs_with_theme_font_false.yml'),
);
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
theme?: { name?: string; font?: boolean };
};
expect(parsedYml.theme).toBeDefined();
expect(parsedYml.theme?.name).toBe('material');
expect(parsedYml.theme?.font).toBe(false);
});
});
describe('patchIndexPreBuild', () => {
afterEach(() => {
warn.mockClear();
@@ -25,11 +25,17 @@ import { assertError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { LoggerService } from '@backstage/backend-plugin-api';
const MATERIAL_THEME = 'material';
type MkDocsObject = {
plugins?: string[];
docs_dir: string;
repo_url?: string;
edit_uri?: string;
theme?: {
name?: string;
font?: boolean;
};
};
const patchMkdocsFile = async (
@@ -185,6 +191,39 @@ export const patchMkdocsYmlWithPlugins = async (
});
};
/**
* Disable external font download for the material theme.
* @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site
* @param logger
*/
export const patchMkdocsYmlWithFontDisabled = async (
mkdocsYmlPath: string,
logger: LoggerService,
) => {
await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {
if (!('theme' in mkdocsYml)) {
// No theme section exists, create it with font disabled
mkdocsYml.theme = {
name: MATERIAL_THEME,
font: false,
};
return true;
}
// Theme section exists, check if font is not configured, add font: false
if (
mkdocsYml.theme &&
typeof mkdocsYml.theme === 'object' &&
!('font' in mkdocsYml.theme)
) {
mkdocsYml.theme.font = false;
return true;
}
return false;
});
};
/**
* Sanitize mkdocs.yml by keeping only allowed configuration keys.
*
@@ -249,3 +288,4 @@ export const sanitizeMkdocsYml = async (
return true;
});
};
@@ -32,6 +32,7 @@ import {
import {
patchMkdocsYmlPreBuild,
patchMkdocsYmlWithFontDisabled,
patchMkdocsYmlWithPlugins,
sanitizeMkdocsYml,
} from './mkdocsPatchers';
@@ -150,6 +151,9 @@ export class TechdocsGenerator implements GeneratorBase {
}
await patchMkdocsYmlWithPlugins(mkdocsYmlPath, childLogger, defaultPlugins);
if (this.options.disableExternalFonts) {
await patchMkdocsYmlWithFontDisabled(mkdocsYmlPath, childLogger);
}
// Directories to bind on container
const mountDirs = {
@@ -264,5 +268,8 @@ export function readGeneratorConfig(
dangerouslyAllowAdditionalKeys: config.getOptionalStringArray(
'techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys',
),
disableExternalFonts: config.getOptionalBoolean(
'techdocs.generator.mkdocs.disableExternalFonts'
),
};
}
@@ -46,6 +46,7 @@ export type GeneratorConfig = {
legacyCopyReadmeMdToIndexMd?: boolean;
defaultPlugins?: string[];
dangerouslyAllowAdditionalKeys?: string[];
disableExternalFonts?: boolean;
};
/**