From bca27b366253f7cfd632d65e4bf402d04d14f414 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 Sep 2021 14:56:30 +0200 Subject: [PATCH 1/4] techdocs: use README.md as index fallback Signed-off-by: Vincenzo Scamporlino --- .../src/stages/generate/helpers.test.ts | 53 +++++++++++++++++++ .../src/stages/generate/helpers.ts | 40 ++++++++++++++ .../src/stages/generate/techdocs.ts | 2 + 3 files changed, 95 insertions(+) diff --git a/packages/techdocs-common/src/stages/generate/helpers.test.ts b/packages/techdocs-common/src/stages/generate/helpers.test.ts index fcf506098e..4a862de024 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.test.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.test.ts @@ -27,6 +27,7 @@ import { getGeneratorKey, getMkdocsYml, getRepoUrlFromLocationAnnotation, + patchIndexPreBuild, patchMkdocsYmlPreBuild, storeEtagMetadata, validateMkdocsYaml, @@ -286,6 +287,58 @@ describe('helpers', () => { }); }); + describe('patchIndexPreBuild', () => { + it('should have no effect if docs/index.md exists', async () => { + mockFs({ + '/docs/index.md': 'index.md content', + '/docs/README.md': 'docs/README.md content', + }); + + await patchIndexPreBuild({ inputDir: '/', logger: mockLogger }); + + expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( + 'index.md content', + ); + mockFs.restore(); + }); + + it("should use docs/README.md if docs/index.md doesn't exists", async () => { + mockFs({ + '/docs/README.md': 'docs/README.md content', + '/README.md': 'main README.md content', + }); + + await patchIndexPreBuild({ inputDir: '/', logger: mockLogger }); + + expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( + 'docs/README.md content', + ); + mockFs.restore(); + }); + + it('should use README.md if neither docs/index.md or docs/README.md exist', async () => { + mockFs({ + '/README.md': 'main README.md content', + }); + + await patchIndexPreBuild({ inputDir: '/', logger: mockLogger }); + + expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( + 'main README.md content', + ); + mockFs.restore(); + }); + + it('should not use any file as index.md if no one matches the requirements', async () => { + mockFs({}); + + await patchIndexPreBuild({ inputDir: '/', logger: mockLogger }); + + expect(() => fs.readFileSync('/docs/index.md', 'utf-8')).toThrow(); + mockFs.restore(); + }); + }); + describe('addBuildTimestampMetadata', () => { beforeEach(() => { mockFs.restore(); diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 0ac05e7dd2..4f9c235d32 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -286,6 +286,46 @@ export const patchMkdocsYmlPreBuild = async ( } }; +/** + * Update docs/index.md file before TechDocs generator uses it to generate docs site, + * falling back to docs/README.md or README.md in case a default docs/index.md + * is not provided. + */ +export const patchIndexPreBuild = async ({ + inputDir, + logger, +}: { + inputDir: string; + logger: Logger; +}) => { + const docsPath = path.join(inputDir, 'docs'); + const indexMdPath = path.join(docsPath, 'index.md'); + + try { + await fs.promises.access(indexMdPath); + return; + } catch { + logger.warn('docs/index.md not found.'); + } + const fallbacks = [ + path.join(docsPath, 'README.md'), + path.join(inputDir, 'README.md'), + ]; + + await fs.promises.mkdir(docsPath, { recursive: true }); + for (const filePath of fallbacks) { + try { + await fs.copyFile(filePath, indexMdPath); + return; + } catch (error) { + logger.warn(`${path.relative(inputDir, filePath)} not found.`); + } + } + logger.warn( + `Could not find any techdocs' index file. Please make sure at least one of docs/index.md docs/README.md README.md exists.`, + ); +}; + /** * Update the techdocs_metadata.json to add a new build timestamp metadata. Create the .json file if it doesn't exist. * diff --git a/packages/techdocs-common/src/stages/generate/techdocs.ts b/packages/techdocs-common/src/stages/generate/techdocs.ts index 44a9d5a14a..1129259d43 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.ts @@ -25,6 +25,7 @@ import { import { addBuildTimestampMetadata, getMkdocsYml, + patchIndexPreBuild, patchMkdocsYmlPreBuild, runCommand, storeEtagMetadata, @@ -102,6 +103,7 @@ export class TechdocsGenerator implements GeneratorBase { parsedLocationAnnotation, this.scmIntegrations, ); + await patchIndexPreBuild({ inputDir, logger: childLogger }); } // Directories to bind on container From 2bc10af8a48844c935e5c79848056e5a4dc3eecd Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 20 Oct 2021 13:12:31 +0200 Subject: [PATCH 2/4] fix comments Signed-off-by: Vincenzo Scamporlino --- .../src/stages/generate/helpers.ts | 35 +++++++++++++------ .../src/stages/generate/techdocs.ts | 4 +-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 4f9c235d32..34ce6f6e8b 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -174,24 +174,31 @@ export const getMkdocsYml = async ( * @param {string} inputDir base dir to be used as a docs_dir path validity check * @param {string} mkdocsYmlFileString The string contents of the loaded * mkdocs.yml or equivalent of a docs site + * @returns the parsed docs_dir or undefined */ export const validateMkdocsYaml = async ( inputDir: string, mkdocsYmlFileString: string, -) => { - const mkdocsYml: any = yaml.load(mkdocsYmlFileString, { +): Promise => { + const mkdocsYml = yaml.load(mkdocsYmlFileString, { schema: MKDOCS_SCHEMA, }); + if (mkdocsYml === null || typeof mkdocsYml !== 'object') { + return undefined; + } + + const parsedMkdocsYml: Record = mkdocsYml; if ( - mkdocsYml.docs_dir && - !isChildPath(inputDir, resolvePath(inputDir, mkdocsYml.docs_dir)) + parsedMkdocsYml.docs_dir && + !isChildPath(inputDir, resolvePath(inputDir, parsedMkdocsYml.docs_dir)) ) { throw new Error( `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons. Use relative paths instead which are resolved relative to your mkdocs.yml file location.`, ); } + return parsedMkdocsYml.docs_dir; }; /** @@ -294,25 +301,27 @@ export const patchMkdocsYmlPreBuild = async ( export const patchIndexPreBuild = async ({ inputDir, logger, + docsDir = 'docs', }: { inputDir: string; logger: Logger; + docsDir?: string; }) => { - const docsPath = path.join(inputDir, 'docs'); + const docsPath = path.join(inputDir, docsDir); const indexMdPath = path.join(docsPath, 'index.md'); - try { - await fs.promises.access(indexMdPath); + if (await fs.pathExists(indexMdPath)) { return; - } catch { - logger.warn('docs/index.md not found.'); } + logger.warn(`${path.join(docsDir, 'index.md')} not found.`); const fallbacks = [ path.join(docsPath, 'README.md'), + path.join(docsPath, 'readme.md'), path.join(inputDir, 'README.md'), + path.join(inputDir, 'readme.md'), ]; - await fs.promises.mkdir(docsPath, { recursive: true }); + await fs.ensureDir(docsPath); for (const filePath of fallbacks) { try { await fs.copyFile(filePath, indexMdPath); @@ -321,8 +330,12 @@ export const patchIndexPreBuild = async ({ logger.warn(`${path.relative(inputDir, filePath)} not found.`); } } + logger.warn( - `Could not find any techdocs' index file. Please make sure at least one of docs/index.md docs/README.md README.md exists.`, + `Could not find any techdocs' index file. Please make sure at least one of ${[ + indexMdPath, + ...fallbacks, + ].join(' ')} exists.`, ); }; diff --git a/packages/techdocs-common/src/stages/generate/techdocs.ts b/packages/techdocs-common/src/stages/generate/techdocs.ts index 1129259d43..17e7c60bad 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.ts @@ -94,7 +94,7 @@ export class TechdocsGenerator implements GeneratorBase { const { path: mkdocsYmlPath, content } = await getMkdocsYml(inputDir); // validate the docs_dir first - await validateMkdocsYaml(inputDir, content); + const docsDir = await validateMkdocsYaml(inputDir, content); if (parsedLocationAnnotation) { await patchMkdocsYmlPreBuild( @@ -103,7 +103,7 @@ export class TechdocsGenerator implements GeneratorBase { parsedLocationAnnotation, this.scmIntegrations, ); - await patchIndexPreBuild({ inputDir, logger: childLogger }); + await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir }); } // Directories to bind on container From 59b45a4396e66ff64b0daf65dbf3e7055f6ef892 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 20 Oct 2021 13:22:34 +0200 Subject: [PATCH 3/4] Add checks for warnings Signed-off-by: Vincenzo Scamporlino --- .../src/stages/generate/helpers.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/techdocs-common/src/stages/generate/helpers.test.ts b/packages/techdocs-common/src/stages/generate/helpers.test.ts index 4a862de024..fdb164f44a 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.test.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.test.ts @@ -66,6 +66,8 @@ const mkdocsYmlWithComments = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_with_comments.yml'), ); const mockLogger = getVoidLogger(); +const warn = jest.spyOn(mockLogger, 'warn'); + const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({})); @@ -288,6 +290,9 @@ describe('helpers', () => { }); describe('patchIndexPreBuild', () => { + afterEach(() => { + warn.mockClear(); + }); it('should have no effect if docs/index.md exists', async () => { mockFs({ '/docs/index.md': 'index.md content', @@ -299,6 +304,7 @@ describe('helpers', () => { expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( 'index.md content', ); + expect(warn).not.toHaveBeenCalledWith(); mockFs.restore(); }); @@ -313,6 +319,7 @@ describe('helpers', () => { expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( 'docs/README.md content', ); + expect(warn.mock.calls).toEqual([['docs/index.md not found.']]); mockFs.restore(); }); @@ -326,6 +333,11 @@ describe('helpers', () => { expect(fs.readFileSync('/docs/index.md', 'utf-8')).toEqual( 'main README.md content', ); + expect(warn.mock.calls).toEqual([ + ['docs/index.md not found.'], + ['docs/README.md not found.'], + ['docs/readme.md not found.'], + ]); mockFs.restore(); }); @@ -335,6 +347,16 @@ describe('helpers', () => { await patchIndexPreBuild({ inputDir: '/', logger: mockLogger }); expect(() => fs.readFileSync('/docs/index.md', 'utf-8')).toThrow(); + expect(warn.mock.calls).toEqual([ + ['docs/index.md not found.'], + ['docs/README.md not found.'], + ['docs/readme.md not found.'], + ['README.md not found.'], + ['readme.md not found.'], + [ + "Could not find any techdocs' index file. Please make sure at least one of /docs/index.md /docs/README.md /docs/readme.md /README.md /readme.md exists.", + ], + ]); mockFs.restore(); }); }); @@ -459,7 +481,7 @@ describe('helpers', () => { it('should return true on when a valid docs_dir is present', async () => { await expect( validateMkdocsYaml(inputDir, mkdocsYmlWithValidDocDir.toString()), - ).resolves.toBeUndefined(); + ).resolves.toBe('docs/'); }); it('should return false on absolute doc_dir path', async () => { From 87f5b9db13a0641bd07f4d4aaba07311579eb702 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 20 Oct 2021 13:26:25 +0200 Subject: [PATCH 4/4] Add changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/eighty-forks-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eighty-forks-fry.md diff --git a/.changeset/eighty-forks-fry.md b/.changeset/eighty-forks-fry.md new file mode 100644 index 0000000000..4a25dd128a --- /dev/null +++ b/.changeset/eighty-forks-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Use docs/README.md or README.md as fallback if docs/index.md is missing