add --mkdocs-config-file-name cli argument to the techdocs-cli serve command

Signed-off-by: Morgan Bentell <mbentell@spotify.com>
This commit is contained in:
Morgan Bentell
2023-10-04 12:08:52 +02:00
parent 73083595bc
commit d06b30b050
6 changed files with 93 additions and 20 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@techdocs/cli': minor
'@backstage/plugin-techdocs-node': minor
---
Add possibility to use a mkdocs config file with a different name than `mkdocs.<yaml|yml> with the serve command using the `--mkdocs-config-file-name` argument
@@ -285,6 +285,10 @@ export function registerCommands(program: Command) {
'Port for the preview app to be served on',
defaultPreviewAppPort,
)
.option(
'-c, --mkdocs-config-file-name <FILENAME>',
'Mkdocs config file name',
)
.hook('preAction', command => {
if (
command.opts().previewAppPort !== defaultPreviewAppPort &&
@@ -65,11 +65,13 @@ export default async function serve(opts: OptionValues) {
const mkdocsExpectedDevAddr = opts.docker
? mkdocsDockerAddr
: mkdocsLocalAddr;
const mkdocsConfigFileName = opts.mkdocsConfigFileName;
const siteName = opts.siteName;
const { path: mkdocsYmlPath, configIsTemporary } = await getMkdocsYml(
'./',
opts.siteName,
);
const { path: mkdocsYmlPath, configIsTemporary } = await getMkdocsYml('./', {
name: siteName,
mkdocsConfigFileName,
});
let mkdocsServerHasStarted = false;
const mkdocsLogFunc: LogFunc = data => {
@@ -104,6 +106,7 @@ export default async function serve(opts: OptionValues) {
useDocker: opts.docker,
stdoutLogFunc: mkdocsLogFunc,
stderrLogFunc: mkdocsLogFunc,
mkdocsConfigFileName: mkdocsYmlPath,
});
// Wait until mkdocs server has started so that Backstage starts with docs loaded
+19 -4
View File
@@ -25,6 +25,7 @@ export const runMkdocsServer = async (options: {
dockerOptions?: string[];
stdoutLogFunc?: LogFunc;
stderrLogFunc?: LogFunc;
mkdocsConfigFileName?: string;
}): Promise<ChildProcess> => {
const port = options.port ?? '8000';
const useDocker = options.useDocker ?? true;
@@ -51,6 +52,9 @@ export const runMkdocsServer = async (options: {
'serve',
'--dev-addr',
`0.0.0.0:${port}`,
...(options.mkdocsConfigFileName
? ['--config-file', options.mkdocsConfigFileName]
: []),
],
{
stdoutLogFunc: options.stdoutLogFunc,
@@ -59,8 +63,19 @@ export const runMkdocsServer = async (options: {
);
}
return await run('mkdocs', ['serve', '--dev-addr', `127.0.0.1:${port}`], {
stdoutLogFunc: options.stdoutLogFunc,
stderrLogFunc: options.stderrLogFunc,
});
return await run(
'mkdocs',
[
'serve',
'--dev-addr',
`127.0.0.1:${port}`,
...(options.mkdocsConfigFileName
? ['--config-file', options.mkdocsConfigFileName]
: []),
],
{
stdoutLogFunc: options.stdoutLogFunc,
stderrLogFunc: options.stderrLogFunc,
},
);
};
@@ -540,7 +540,7 @@ describe('helpers', () => {
});
describe('getMkdocsYml', () => {
const siteOptions = {
const defaultOptions = {
name: mockEntity.metadata.title,
};
@@ -550,7 +550,7 @@ describe('helpers', () => {
path: mkdocsPath,
content,
configIsTemporary,
} = await getMkdocsYml(mockDir.path, siteOptions);
} = await getMkdocsYml(mockDir.path, defaultOptions);
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yml'));
expect(content).toBe(mkdocsYml.toString());
@@ -563,14 +563,14 @@ describe('helpers', () => {
path: mkdocsPath,
content,
configIsTemporary,
} = await getMkdocsYml(mockDir.path, siteOptions);
} = await getMkdocsYml(mockDir.path, defaultOptions);
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yaml'));
expect(content).toBe(mkdocsYml.toString());
expect(configIsTemporary).toBe(false);
});
it('returns expected contents when default file is present', async () => {
const defaultSiteOptions = {
const options = {
name: 'Default Test site name',
};
const mockPathExists = jest.spyOn(fs, 'pathExists');
@@ -580,21 +580,49 @@ describe('helpers', () => {
path: mkdocsPath,
content,
configIsTemporary,
} = await getMkdocsYml(mockDir.path, defaultSiteOptions);
} = await getMkdocsYml(mockDir.path, options);
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yml'));
expect(content.split(/[\r\n]+/g)).toEqual(
mkdocsDefaultYml.toString().split(/[\r\n]+/g),
);
expect(configIsTemporary).toBe(true);
mockPathExists.mockRestore();
});
it('throws when neither .yml nor .yaml nor default file is present', async () => {
const invalidInputDir = resolvePath(__filename);
await expect(getMkdocsYml(invalidInputDir, siteOptions)).rejects.toThrow(
await expect(
getMkdocsYml(invalidInputDir, defaultOptions),
).rejects.toThrow(
/Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml or default for validation/,
);
});
it('returns expected content when custom file is specified', async () => {
const options = { mkdocsConfigFileName: 'another-name.yaml' };
mockDIr.setContent({'mkdocs.yml': mkdocsYml})
const {
path: mkdocsPath,
content,
configIsTemporary,
} = await getMkdocsYml(inputDir, options);
expect(mkdocsPath).toBe(key);
expect(content).toBe(mkdocsYml.toString());
expect(configIsTemporary).toBe(false);
});
it('throws when specifying a specific mkdocs config file that does not exist', async () => {
const options = { mkdocsConfigFileName: 'another-name.yaml' };
mockDir.setContent({ 'mkdocs.yml': mkdocsDefaultYml });
await expect(getMkdocsYml(inputDir, options)).rejects.toThrow(
/The specified file .* does not exist/,
);
});
});
describe('validateMkdocsYaml', () => {
@@ -185,21 +185,38 @@ export const generateMkdocsYml = async (
};
/**
* Finds and loads the contents of either an mkdocs.yml or mkdocs.yaml file,
* depending on which is present (MkDocs supports both as of v1.2.2).
* Finds and loads the contents of an mkdocs.yml, mkdocs.yaml file, a file
* with a specified name or an ad-hoc created file with minimal config.
* @public
*
* @param inputDir - base dir to be searched for either an mkdocs.yml or mkdocs.yaml file.
* @param siteOptions - options for the site: `name` property will be used in mkdocs.yml for the
* required `site_name` property, default value is "Documentation Site"
* @param options - ```
* {
* name: default mkdocs site_name to be used with a ad hoc file default value is "Documentation Site"
* mkdocsConfigFileName (optional): a non-default file name to be used as the config
* }```
*/
export const getMkdocsYml = async (
inputDir: string,
siteOptions?: { name?: string },
options?: { name?: string; mkdocsConfigFileName?: string },
): Promise<{ path: string; content: string; configIsTemporary: boolean }> => {
let mkdocsYmlPath: string;
let mkdocsYmlFileString: string;
try {
if (options?.mkdocsConfigFileName) {
mkdocsYmlPath = path.join(inputDir, options.mkdocsConfigFileName);
if (!(await fs.pathExists(mkdocsYmlPath))) {
throw new Error(`The specified file ${mkdocsYmlPath} does not exist`);
}
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
return {
path: mkdocsYmlPath,
content: mkdocsYmlFileString,
configIsTemporary: false,
};
}
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yaml');
if (await fs.pathExists(mkdocsYmlPath)) {
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
@@ -221,7 +238,7 @@ export const getMkdocsYml = async (
}
// No mkdocs file, generate it
await generateMkdocsYml(inputDir, siteOptions);
await generateMkdocsYml(inputDir, options);
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
} catch (error) {
throw new ForwardedError(