From d06b30b050ba9e76d270d2692f21aa1664415aea Mon Sep 17 00:00:00 2001 From: Morgan Bentell Date: Wed, 4 Oct 2023 12:08:52 +0200 Subject: [PATCH] add --mkdocs-config-file-name cli argument to the techdocs-cli serve command Signed-off-by: Morgan Bentell --- .changeset/nine-flies-move.md | 6 +++ packages/techdocs-cli/src/commands/index.ts | 4 ++ .../techdocs-cli/src/commands/serve/serve.ts | 11 +++-- packages/techdocs-cli/src/lib/mkdocsServer.ts | 23 +++++++++-- .../src/stages/generate/helpers.test.ts | 40 ++++++++++++++++--- .../src/stages/generate/helpers.ts | 29 +++++++++++--- 6 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 .changeset/nine-flies-move.md diff --git a/.changeset/nine-flies-move.md b/.changeset/nine-flies-move.md new file mode 100644 index 0000000000..0e7bca2836 --- /dev/null +++ b/.changeset/nine-flies-move.md @@ -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. with the serve command using the `--mkdocs-config-file-name` argument diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts index 3321bb7f43..10a00da513 100644 --- a/packages/techdocs-cli/src/commands/index.ts +++ b/packages/techdocs-cli/src/commands/index.ts @@ -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 ', + 'Mkdocs config file name', + ) .hook('preAction', command => { if ( command.opts().previewAppPort !== defaultPreviewAppPort && diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 9e2e521546..5fcf1787d3 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -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 diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.ts b/packages/techdocs-cli/src/lib/mkdocsServer.ts index 5ca7e47573..42409acb46 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.ts @@ -25,6 +25,7 @@ export const runMkdocsServer = async (options: { dockerOptions?: string[]; stdoutLogFunc?: LogFunc; stderrLogFunc?: LogFunc; + mkdocsConfigFileName?: string; }): Promise => { 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, + }, + ); }; diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index 22940e9ce8..5556f8aae4 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -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', () => { diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index e16293f906..9e24a5edfe 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -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(