Merge pull request #6634 from backstage/mob/yaml-support
[TechDocs] Add support for mkdocs.yaml
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
TechDocs generator stage now supports `mkdocs.yaml` file, in addition to `.yml`
|
||||
depending on whichever is present at the time of generation. (Assumes the
|
||||
latest `spotify/techdocs` container, running mkdocs `v1.2.2` or greater).
|
||||
@@ -23,6 +23,7 @@ import { RemoteProtocol } from '../prepare/types';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getGeneratorKey,
|
||||
getMkdocsYml,
|
||||
getRepoUrlFromLocationAnnotation,
|
||||
isValidRepoUrlForMkdocs,
|
||||
patchMkdocsYmlPreBuild,
|
||||
@@ -53,6 +54,9 @@ const mkdocsYmlWithValidDocDir = fs.readFileSync(
|
||||
const mkdocsYmlWithInvalidDocDir = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir.yml'),
|
||||
);
|
||||
const mkdocsYmlWithInvalidDocDir2 = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir2.yml'),
|
||||
);
|
||||
const mockLogger = getVoidLogger();
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
@@ -334,48 +338,68 @@ describe('helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMkdocsYaml', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/mkdocs.yml': mkdocsYml,
|
||||
'/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
|
||||
'/mkdocs_valid_doc_dir.yml': mkdocsYmlWithValidDocDir,
|
||||
'/mkdocs_invalid_doc_dir.yml': mkdocsYmlWithInvalidDocDir,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMkdocsYml', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const inputDir = resolvePath(__filename, '../__fixtures__/');
|
||||
|
||||
it('returns expected contents when .yml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
const { path: mkdocsPath, content } = await getMkdocsYml(inputDir);
|
||||
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
});
|
||||
|
||||
it('returns expected contents when .yaml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yaml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
const { path: mkdocsPath, content } = await getMkdocsYml(inputDir);
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
});
|
||||
|
||||
it('throws when neither .yml nor .yaml file is present', async () => {
|
||||
const invalidInputDir = resolvePath(__filename);
|
||||
await expect(getMkdocsYml(invalidInputDir)).rejects.toThrowError(
|
||||
/Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMkdocsYaml', () => {
|
||||
const inputDir = resolvePath(__filename, '../__fixtures__/');
|
||||
|
||||
it('should return true on when no docs_dir present', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYml.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return true on when a valid docs_dir is present', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_valid_doc_dir.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithValidDocDir.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return false on absolute doc_dir path', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithInvalidDocDir.toString()),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should return false on doc_dir path that traverses directory structure backwards', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir2.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithInvalidDocDir2.toString()),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should validate files with custom yaml tags', async () => {
|
||||
await expect(
|
||||
validateMkdocsYaml(inputDir, '/mkdocs_with_extensions.yml'),
|
||||
validateMkdocsYaml(inputDir, mkdocsYmlWithExtensions.toString()),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import { isChildPath } from '@backstage/backend-common';
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { ParsedLocationAnnotation } from '../../helpers';
|
||||
@@ -155,26 +155,50 @@ const MKDOCS_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @param {string} inputDir base dir to be searched for either an mkdocs.yml or
|
||||
* mkdocs.yaml file.
|
||||
*/
|
||||
export const getMkdocsYml = async (
|
||||
inputDir: string,
|
||||
): Promise<{ path: string; content: string }> => {
|
||||
let mkdocsYmlPath: string;
|
||||
let mkdocsYmlFileString: string;
|
||||
try {
|
||||
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yaml');
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch {
|
||||
try {
|
||||
mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml for validation: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
path: mkdocsYmlPath,
|
||||
content: mkdocsYmlFileString,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validating mkdocs config file for incorrect/insecure values
|
||||
* Throws on invalid configs
|
||||
*
|
||||
* @param {string} inputDir base dir to be used as a docs_dir path validity check
|
||||
* @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
|
||||
* @param {string} mkdocsYmlFileString The string contents of the loaded
|
||||
* mkdocs.yml or equivalent of a docs site
|
||||
*/
|
||||
export const validateMkdocsYaml = async (
|
||||
inputDir: string,
|
||||
mkdocsYmlPath: string,
|
||||
mkdocsYmlFileString: string,
|
||||
) => {
|
||||
let mkdocsYmlFileString;
|
||||
try {
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not read MkDocs YAML config file ${mkdocsYmlPath} before for validation: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
const mkdocsYml: any = yaml.load(mkdocsYmlFileString, {
|
||||
schema: MKDOCS_SCHEMA,
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getMkdocsYml,
|
||||
patchMkdocsYmlPreBuild,
|
||||
runCommand,
|
||||
storeEtagMetadata,
|
||||
@@ -71,10 +72,8 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
logger: childLogger,
|
||||
logStream,
|
||||
}: GeneratorRunOptions): Promise<void> {
|
||||
// TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out
|
||||
// the correct file name.
|
||||
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
|
||||
const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
|
||||
const { path: mkdocsYmlPath, content } = await getMkdocsYml(inputDir);
|
||||
if (parsedLocationAnnotation) {
|
||||
await patchMkdocsYmlPreBuild(
|
||||
mkdocsYmlPath,
|
||||
@@ -83,7 +82,7 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
);
|
||||
}
|
||||
|
||||
await validateMkdocsYaml(inputDir, mkdocsYmlPath);
|
||||
await validateMkdocsYaml(inputDir, content);
|
||||
|
||||
// Directories to bind on container
|
||||
const mountDirs = {
|
||||
|
||||
Reference in New Issue
Block a user