Merge pull request #31838 from karthikjeeyar/mkdocs-patch
feat(techdocs): add app-config option to disable external font download
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@techdocs/cli': minor
|
||||
---
|
||||
|
||||
Add support for disabling external font downloads via techdocs-cli `techdocs-cli generate --disableExternalFonts`, useful for air-gapped Backstage instances.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': minor
|
||||
'@backstage/plugin-techdocs-node': minor
|
||||
---
|
||||
|
||||
Add support for disabling external font downloads via app-config option `techdocs.generator.mkdocs.disableExternalFonts`, useful for air-gapped Backstage instances.
|
||||
@@ -345,6 +345,7 @@ params
|
||||
parseable
|
||||
passthrough
|
||||
passwordless
|
||||
patcher
|
||||
Patrik
|
||||
pattison
|
||||
Peloton
|
||||
@@ -590,4 +591,4 @@ Zhou
|
||||
zod
|
||||
Zolotusky
|
||||
zoomable
|
||||
zsh
|
||||
zsh
|
||||
@@ -149,6 +149,8 @@ Options:
|
||||
Defaults to false, which means that the techdocs-core plugin is always added to the mkdocs file.
|
||||
--legacyCopyReadmeMdToIndexMd Attempt to ensure an index.md exists falling back to using <docs-dir>/README.md or README.md
|
||||
in case a default <docs-dir>/index.md is not provided. (default: false)
|
||||
--disableExternalFonts Disable external font downloads for all TechDocs sites. Useful for air-gapped environments
|
||||
where Google fonts cannot be accessed. (default: false)
|
||||
--runAsDefaultUser Bypass setting the container user as the same user and group id as host for Linux and MacOS (default: false)
|
||||
-v, --verbose Enable verbose output. (default: false)
|
||||
-h, --help display help for command
|
||||
|
||||
@@ -97,6 +97,33 @@ techdocs:
|
||||
legacyCopyReadmeMdToIndexMd: false
|
||||
```
|
||||
|
||||
#### Disable external fonts
|
||||
|
||||
`techdocs.generator.mkdocs.disableExternalFonts`
|
||||
|
||||
(Optional) Use this when the generator cannot reach the internet (for example air-gapped or restricted networks). MkDocs Material otherwise tries to download the Roboto font from Google during generation.
|
||||
|
||||
When `true`, TechDocs patches each `mkdocs.yml` during generation: if no `theme` section exists it adds `name: material` and `font: false`; if a `theme` exists but `font` is omitted, it sets `font: false`; if `font` is already set in the file, your value is left unchanged.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
generator:
|
||||
mkdocs:
|
||||
disableExternalFonts: true
|
||||
```
|
||||
|
||||
Alternatively, configure `mkdocs.yml` manually:
|
||||
|
||||
```yaml
|
||||
theme:
|
||||
name: material
|
||||
font: false
|
||||
```
|
||||
|
||||
**Note:** When using `theme.font` in `mkdocs.yml`, `theme.name: material` is required. If `font` is already set in the file, app-config patching does not override it; it only adds `font: false` when `font` was not configured.
|
||||
|
||||
#### Default Plugins
|
||||
|
||||
`techdocs.generator.mkdocs.defaultPlugins`
|
||||
|
||||
@@ -373,9 +373,24 @@ on how you have configured your `template.yaml`.
|
||||
|
||||
Done! You now have support for TechDocs in your own software template!
|
||||
|
||||
### Prevent download of Google fonts
|
||||
### Disable external fonts
|
||||
|
||||
If your Backstage instance does not have internet access, the generation will fail. TechDocs tries to download the Roboto font from Google. You can disable it by adding the following lines to mkdocs.yaml:
|
||||
`techdocs.generator.mkdocs.disableExternalFonts`
|
||||
|
||||
(Optional) Use this when the generator cannot reach the internet (for example air-gapped or restricted networks). MkDocs Material otherwise tries to download the Roboto font from Google during generation.
|
||||
|
||||
When `true`, TechDocs patches each `mkdocs.yml` during generation: if no `theme` section exists it adds `name: material` and `font: false`; if a `theme` exists but `font` is omitted, it sets `font: false`; if `font` is already set in the file, your value is left unchanged.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
generator:
|
||||
mkdocs:
|
||||
disableExternalFonts: true
|
||||
```
|
||||
|
||||
Alternatively, configure `mkdocs.yml` manually:
|
||||
|
||||
```yaml
|
||||
theme:
|
||||
@@ -383,11 +398,19 @@ theme:
|
||||
font: false
|
||||
```
|
||||
|
||||
:::note Note
|
||||
**Note:** When using `theme.font` in `mkdocs.yml`, `theme.name: material` is required. If `font` is already set in the file, app-config patching does not override it; it only adds `font: false` when `font` was not configured.
|
||||
|
||||
The addition `name: material` is necessary. Otherwise it will not work
|
||||
#### Using techdocs-cli in CI/CD
|
||||
|
||||
:::
|
||||
When generating TechDocs sites in CI/CD workflows using `techdocs-cli`, you can
|
||||
use the `--disableExternalFonts` flag:
|
||||
|
||||
```bash
|
||||
techdocs-cli generate --disableExternalFonts
|
||||
```
|
||||
|
||||
This will automatically patch the `mkdocs.yml` file during the generation
|
||||
process, just like the `app-config.yaml` option does for local generation.
|
||||
|
||||
## How to enable iframes in TechDocs
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ Usage: techdocs-cli generate|build [options]
|
||||
|
||||
Options:
|
||||
--defaultPlugin [defaultPlugins...]
|
||||
--disableExternalFonts
|
||||
--docker-image <DOCKER_IMAGE>
|
||||
--etag <ETAG>
|
||||
--legacyCopyReadmeMdToIndexMd
|
||||
|
||||
@@ -42,6 +42,7 @@ export default async function generate(opts: OptionValues) {
|
||||
const dockerImage = opts.dockerImage;
|
||||
const pullImage = opts.pull;
|
||||
const legacyCopyReadmeMdToIndexMd = opts.legacyCopyReadmeMdToIndexMd;
|
||||
const disableExternalFonts = opts.disableExternalFonts;
|
||||
const defaultPlugins = opts.defaultPlugin;
|
||||
|
||||
logger.info(`Using source dir ${sourceDir}`);
|
||||
@@ -64,6 +65,7 @@ export default async function generate(opts: OptionValues) {
|
||||
mkdocs: {
|
||||
legacyCopyReadmeMdToIndexMd,
|
||||
omitTechdocsCorePlugin,
|
||||
disableExternalFonts,
|
||||
defaultPlugins,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -70,6 +70,11 @@ export function registerCommands(program: Command) {
|
||||
'Attempt to ensure an index.md exists falling back to using <docs-dir>/README.md or README.md in case a default <docs-dir>/index.md is not provided.',
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
'--disableExternalFonts',
|
||||
'Disable external font downloads by default by setting theme.font: false in mkdocs.yml when not already configured. Useful for air-gapped environments where Google fonts cannot be accessed.',
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
'--defaultPlugin [defaultPlugins...]',
|
||||
'Plugins which should be added automatically to the mkdocs.yaml file',
|
||||
|
||||
Vendored
+8
@@ -76,6 +76,14 @@ export interface Config {
|
||||
* @see https://www.mkdocs.org/user-guide/configuration/#hooks
|
||||
*/
|
||||
dangerouslyAllowAdditionalKeys?: string[];
|
||||
|
||||
/**
|
||||
* Disable external fonts for all TechDocs sites.
|
||||
* If not set, the default value is false.
|
||||
* If set to true, the external font will be disabled for all TechDocs sites.
|
||||
* If set to false, the external font will be enabled for all TechDocs sites.
|
||||
*/
|
||||
disableExternalFonts?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
patchMkdocsYmlPreBuild,
|
||||
patchMkdocsYmlWithPlugins,
|
||||
sanitizeMkdocsYml,
|
||||
patchMkdocsYmlWithFontDisabled,
|
||||
} from './mkdocsPatchers';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
@@ -468,6 +469,119 @@ 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
|
||||
`,
|
||||
'mkdocs_with_theme_non_material.yml': `site_name: Test Site
|
||||
docs_dir: docs
|
||||
theme:
|
||||
name: test-theme
|
||||
`,
|
||||
});
|
||||
mockLogger.debug.mockClear();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should not patch when theme name is not material', async () => {
|
||||
const fixturePath = mockDir.resolve('mkdocs_with_theme_non_material.yml');
|
||||
const before = await fs.readFile(fixturePath, 'utf8');
|
||||
|
||||
await patchMkdocsYmlWithFontDisabled(fixturePath, mockLogger);
|
||||
|
||||
await expect(fs.readFile(fixturePath, 'utf8')).resolves.toEqual(before);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'mkdocs.yml theme is not "material"; skipping font disabling patch',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchIndexPreBuild', () => {
|
||||
afterEach(() => {
|
||||
warn.mockClear();
|
||||
|
||||
@@ -25,11 +25,23 @@ import { toError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
const MATERIAL_THEME = 'material';
|
||||
|
||||
type MkDocsThemeObject = {
|
||||
name?: string;
|
||||
font?: boolean;
|
||||
};
|
||||
|
||||
function isThemeObject(theme: unknown): theme is MkDocsThemeObject {
|
||||
return typeof theme === 'object' && theme !== null && !Array.isArray(theme);
|
||||
}
|
||||
|
||||
type MkDocsObject = {
|
||||
plugins?: string[];
|
||||
docs_dir: string;
|
||||
repo_url?: string;
|
||||
edit_uri?: string;
|
||||
theme?: MkDocsThemeObject;
|
||||
};
|
||||
|
||||
const patchMkdocsFile = async (
|
||||
@@ -188,6 +200,43 @@ 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;
|
||||
}
|
||||
|
||||
const theme = mkdocsYml.theme;
|
||||
if (isThemeObject(theme)) {
|
||||
// Theme section exists. Only modify it when the configured theme is Material
|
||||
if (theme.name === MATERIAL_THEME && !('font' in theme)) {
|
||||
theme.font = false;
|
||||
return true;
|
||||
}
|
||||
if (theme.name !== MATERIAL_THEME) {
|
||||
logger.debug(
|
||||
'mkdocs.yml theme is not "material"; skipping font disabling patch',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize mkdocs.yml by keeping only allowed configuration keys.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user