Set the correct edit_uri or repo_url for documentation pages that are hosted on GitHub and GitLab
Signed-off-by: Dominik Henneke <dominik.henneke@sda-se.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/techdocs-common': minor
|
||||
---
|
||||
|
||||
Set the correct `edit_uri` or `repo_url` for documentation pages that are hosted on GitHub and GitLab.
|
||||
|
||||
The constructor of the `TechDocsGenerator` changed.
|
||||
Prefer the use of `TechdocsGenerator.fromConfig(…)` instead:
|
||||
|
||||
```diff
|
||||
- const techdocsGenerator = new TechdocsGenerator({
|
||||
+ const techdocsGenerator = TechdocsGenerator.fromConfig(config, {
|
||||
logger,
|
||||
containerRunner,
|
||||
- config,
|
||||
});
|
||||
```
|
||||
@@ -230,10 +230,12 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
logger,
|
||||
containerRunner,
|
||||
config,
|
||||
scmIntegrations,
|
||||
}: {
|
||||
logger: Logger_2;
|
||||
containerRunner: ContainerRunner;
|
||||
config: Config;
|
||||
scmIntegrations: ScmIntegrationRegistry;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
@@ -245,7 +247,7 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
containerRunner: ContainerRunner;
|
||||
logger: Logger_2;
|
||||
},
|
||||
): Promise<TechdocsGenerator>;
|
||||
): TechdocsGenerator;
|
||||
// (undocumented)
|
||||
run({
|
||||
inputDir,
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"aws-sdk": "^2.840.0",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"git-url-parse": "~11.4.4",
|
||||
"js-yaml": "^4.0.0",
|
||||
"json5": "^2.1.3",
|
||||
"mime-types": "^2.1.27",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
site_name: Test site name
|
||||
site_description: Test site description
|
||||
|
||||
edit_uri: https://github.com/backstage/backstage/edit/main/docs
|
||||
@@ -44,10 +44,9 @@ describe('generators', () => {
|
||||
|
||||
it('should return correct registered generator', async () => {
|
||||
const generators = new Generators();
|
||||
const techdocs = new TechdocsGenerator({
|
||||
const techdocs = TechdocsGenerator.fromConfig(new ConfigReader({}), {
|
||||
logger,
|
||||
containerRunner,
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
generators.register('techdocs', techdocs);
|
||||
|
||||
@@ -38,10 +38,9 @@ export class Generators implements GeneratorBuilder {
|
||||
): Promise<GeneratorBuilder> {
|
||||
const generators = new Generators();
|
||||
|
||||
const techdocsGenerator = new TechdocsGenerator({
|
||||
const techdocsGenerator = TechdocsGenerator.fromConfig(config, {
|
||||
logger,
|
||||
containerRunner,
|
||||
config,
|
||||
});
|
||||
generators.register('techdocs', techdocsGenerator);
|
||||
|
||||
|
||||
@@ -13,19 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import os from 'os';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { ParsedLocationAnnotation } from '../../helpers';
|
||||
import { RemoteProtocol } from '../prepare/types';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getGeneratorKey,
|
||||
getMkdocsYml,
|
||||
getRepoUrlFromLocationAnnotation,
|
||||
isValidRepoUrlForMkdocs,
|
||||
patchMkdocsYmlPreBuild,
|
||||
storeEtagMetadata,
|
||||
validateMkdocsYaml,
|
||||
@@ -48,6 +49,9 @@ const mkdocsYmlWithExtensions = fs.readFileSync(
|
||||
const mkdocsYmlWithRepoUrl = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'),
|
||||
);
|
||||
const mkdocsYmlWithEditUri = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_with_edit_uri.yml'),
|
||||
);
|
||||
const mkdocsYmlWithValidDocDir = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_valid_doc_dir.yml'),
|
||||
);
|
||||
@@ -60,6 +64,8 @@ const mkdocsYmlWithInvalidDocDir2 = fs.readFileSync(
|
||||
const mockLogger = getVoidLogger();
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({}));
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('getGeneratorKey', () => {
|
||||
it('should return techdocs as the only generator key', () => {
|
||||
@@ -68,120 +74,85 @@ describe('helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidRepoUrlForMkdocs', () => {
|
||||
it('should return true for valid repo_url values for mkdocs', () => {
|
||||
const validRepoUrls = [
|
||||
'https://github.com/org/repo',
|
||||
'https://github.com/backstage/backstage/',
|
||||
'https://github.com/org123/repo1-2-3/',
|
||||
'http://github.com/insecureOrg/insecureRepo',
|
||||
'https://gitlab.com/org/repo',
|
||||
'https://gitlab.com/backstage/backstage/',
|
||||
'https://gitlab.com/org123/repo1-2-3/',
|
||||
'http://gitlab.com/insecureOrg/insecureRepo',
|
||||
];
|
||||
|
||||
const validRemoteProtocols = ['github', 'gitlab'];
|
||||
|
||||
validRepoUrls.forEach(url => {
|
||||
validRemoteProtocols.forEach(targetType => {
|
||||
expect(
|
||||
isValidRepoUrlForMkdocs(url, targetType as RemoteProtocol),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false for invalid repo_urls values for mkdocs', () => {
|
||||
const invalidRepoUrls = [
|
||||
'git@github.com:org/repo',
|
||||
'https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend',
|
||||
];
|
||||
|
||||
invalidRepoUrls.forEach(url => {
|
||||
expect(isValidRepoUrlForMkdocs(url, 'github')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false for unsupported remote protocols', () => {
|
||||
const validRepoUrl = 'https://github.com/backstage/backstage';
|
||||
|
||||
const unsupportedRemoteProtocols = ['dir', 'file', 'url'];
|
||||
|
||||
unsupportedRemoteProtocols.forEach(targetType => {
|
||||
expect(
|
||||
isValidRepoUrlForMkdocs(validRepoUrl, targetType as RemoteProtocol),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRepoUrlFromLocationAnnotation', () => {
|
||||
it('should return undefined for unsupported location type', () => {
|
||||
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
|
||||
it.each`
|
||||
url | repo_url | edit_uri
|
||||
${'https://github.com/backstage/backstage'} | ${'https://github.com/backstage/backstage'} | ${undefined}
|
||||
${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${undefined} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/docs'}
|
||||
${'https://github.com/backstage/backstage/tree/main/'} | ${undefined} | ${'https://github.com/backstage/backstage/edit/main/docs'}
|
||||
${'https://gitlab.com/backstage/backstage'} | ${'https://gitlab.com/backstage/backstage'} | ${undefined}
|
||||
${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${undefined} | ${'https://gitlab.com/backstage/backstage/-/edit/main/examples/techdocs/docs'}
|
||||
${'https://gitlab.com/backstage/backstage/-/blob/main/'} | ${undefined} | ${'https://gitlab.com/backstage/backstage/-/edit/main/docs'}
|
||||
`('should convert $url', ({ url: target, repo_url, edit_uri }) => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target,
|
||||
};
|
||||
|
||||
expect(
|
||||
getRepoUrlFromLocationAnnotation(
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
),
|
||||
).toEqual({ repo_url, edit_uri });
|
||||
});
|
||||
|
||||
it.each`
|
||||
url | edit_uri
|
||||
${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/custom/folder'}
|
||||
${'https://github.com/backstage/backstage/tree/main/'} | ${'https://github.com/backstage/backstage/edit/main/custom/folder'}
|
||||
${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${'https://gitlab.com/backstage/backstage/-/edit/main/examples/techdocs/custom/folder'}
|
||||
${'https://gitlab.com/backstage/backstage/-/blob/main/'} | ${'https://gitlab.com/backstage/backstage/-/edit/main/custom/folder'}
|
||||
`(
|
||||
'should convert $url with custom docsFolder',
|
||||
({ url: target, edit_uri }) => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target,
|
||||
};
|
||||
|
||||
expect(
|
||||
getRepoUrlFromLocationAnnotation(
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
'./custom/folder',
|
||||
),
|
||||
).toEqual({ edit_uri });
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
url
|
||||
${'https://bitbucket.org/backstage/backstage/src/master/examples/techdocs/'}
|
||||
${'https://bitbucket.org/backstage/backstage/src/master/'}
|
||||
${'https://dev.azure.com/organization/project/_git/repository?path=%2Fexamples%2Ftechdocs'}
|
||||
${'https://dev.azure.com/organization/project/_git/repository?path=%2F'}
|
||||
`('should ignore $url', ({ url: target }) => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target,
|
||||
};
|
||||
|
||||
expect(
|
||||
getRepoUrlFromLocationAnnotation(
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should ignore unsupported location type', () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'dir',
|
||||
target: '/home/user/workspace/docs-repository',
|
||||
};
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'file' as RemoteProtocol,
|
||||
target: '/home/user/workspace/docs-repository/catalog-info.yaml',
|
||||
};
|
||||
|
||||
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target: 'https://my-website.com/storage/this/docs/repository',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct target url for supported hosts', () => {
|
||||
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/backstage/backstage.git',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
|
||||
'https://github.com/backstage/backstage',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/org/repo',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
|
||||
'https://github.com/org/repo',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
|
||||
type: 'gitlab' as RemoteProtocol,
|
||||
target: 'https://gitlab.com/org/repo',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
|
||||
'https://gitlab.com/org/repo',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation4: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
target:
|
||||
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation4)).toBe(
|
||||
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
);
|
||||
expect(
|
||||
getRepoUrlFromLocationAnnotation(
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
),
|
||||
).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,6 +161,7 @@ describe('helpers', () => {
|
||||
mockFs({
|
||||
'/mkdocs.yml': mkdocsYml,
|
||||
'/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
|
||||
'/mkdocs_with_edit_uri.yml': mkdocsYmlWithEditUri,
|
||||
'/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
|
||||
});
|
||||
});
|
||||
@@ -198,9 +170,9 @@ describe('helpers', () => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should add repo_url to mkdocs.yml', async () => {
|
||||
it('should add edit_uri to mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
type: 'url',
|
||||
target: 'https://github.com/backstage/backstage',
|
||||
};
|
||||
|
||||
@@ -208,6 +180,7 @@ describe('helpers', () => {
|
||||
'/mkdocs.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs.yml');
|
||||
@@ -219,7 +192,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should add repo_url to mkdocs.yml that contains custom yaml tags', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
type: 'url',
|
||||
target: 'https://github.com/backstage/backstage',
|
||||
};
|
||||
|
||||
@@ -227,6 +200,7 @@ describe('helpers', () => {
|
||||
'/mkdocs_with_extensions.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_extensions.yml');
|
||||
@@ -241,7 +215,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should not override existing repo_url in mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github' as RemoteProtocol,
|
||||
type: 'url',
|
||||
target: 'https://github.com/neworg/newrepo',
|
||||
};
|
||||
|
||||
@@ -249,6 +223,7 @@ describe('helpers', () => {
|
||||
'/mkdocs_with_repo_url.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml');
|
||||
@@ -260,6 +235,29 @@ describe('helpers', () => {
|
||||
'repo_url: https://github.com/neworg/newrepo',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not override existing edit_uri in mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target: 'https://github.com/neworg/newrepo',
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_edit_uri.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_edit_uri.yml');
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'edit_uri: https://github.com/backstage/backstage/edit/main/docs',
|
||||
);
|
||||
expect(updatedMkdocsYml.toString()).not.toContain(
|
||||
'https://github.com/neworg/newrepo',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addBuildTimestampMetadata', () => {
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { isChildPath } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
@@ -80,63 +82,42 @@ export const runCommand = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if mkdocs can compile docs with provided repo_url
|
||||
*
|
||||
* Valid repo_url examples in mkdocs.yml
|
||||
* - https://github.com/backstage/backstage
|
||||
* - https://gitlab.com/org/repo/
|
||||
* - http://github.com/backstage/backstage
|
||||
* - A http(s) protocol URL to the root of the repository
|
||||
*
|
||||
* Invalid repo_url examples in mkdocs.yml
|
||||
* - https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component
|
||||
* - (anything that is not valid as described above)
|
||||
*
|
||||
* @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml
|
||||
* @param {string} locationType Type of source code host - github, gitlab, dir, url, etc.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidRepoUrlForMkdocs = (
|
||||
repoUrl: string,
|
||||
locationType: string,
|
||||
): boolean => {
|
||||
// Trim trailing slash
|
||||
const cleanRepoUrl = repoUrl.replace(/\/$/, '');
|
||||
|
||||
if (locationType === 'github' || locationType === 'gitlab') {
|
||||
// A valid repoUrl to the root of the repository will be split into 5 strings if split using the / delimiter.
|
||||
// We do not want URLs which have more than that number of forward slashes since they will signify a non-root location
|
||||
// Note: This is not the best possible implementation but will work most of the times.. Feel free to improve or
|
||||
// highlight edge cases.
|
||||
return cleanRepoUrl.split('/').length === 5;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a valid URL of the repository used in backstage.io/techdocs-ref annotation.
|
||||
* Return undefined if the `target` is not valid in context of repo_url in mkdocs.yml
|
||||
* Alter URL so that it is a valid repo_url config in mkdocs.yml
|
||||
* Return the source url for MkDocs based on the backstage.io/techdocs-ref annotation.
|
||||
* Depending on the type of target, it can either return a repo_url, an edit_uri, both, or none.
|
||||
*
|
||||
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
|
||||
* @returns {string | undefined}
|
||||
* @param {ScmIntegrationRegistry} scmIntegrations the scmIntegration to do url transformations
|
||||
* @param {string} docsFolder the configured docs folder in the mkdocs.yml (defaults to 'docs')
|
||||
* @returns the settings for the mkdocs.yml
|
||||
*/
|
||||
export const getRepoUrlFromLocationAnnotation = (
|
||||
parsedLocationAnnotation: ParsedLocationAnnotation,
|
||||
): string | undefined => {
|
||||
scmIntegrations: ScmIntegrationRegistry,
|
||||
docsFolder: string = 'docs',
|
||||
): { repo_url?: string; edit_uri?: string } => {
|
||||
const { type: locationType, target } = parsedLocationAnnotation;
|
||||
|
||||
// Add more options from the RemoteProtocol type of parsedLocationAnnotation.type here
|
||||
// when TechDocs supports more hosts and if mkdocs can generated an Edit URL for them.
|
||||
const supportedHosts = ['github', 'gitlab'];
|
||||
if (locationType === 'url') {
|
||||
const integration = scmIntegrations.byUrl(target);
|
||||
|
||||
if (supportedHosts.includes(locationType)) {
|
||||
// Trim .git or .git/ from the end of repository url
|
||||
return target.replace(/.git\/*$/, '');
|
||||
// We only support it for github and gitlab for now as the edit_uri
|
||||
// is not properly supported for others yet.
|
||||
if (integration && ['github', 'gitlab'].includes(integration.type)) {
|
||||
// handle the case where a user manually writes url:https://github.com/backstage/backstage i.e. without /blob/...
|
||||
const { filepathtype } = gitUrlParse(target);
|
||||
if (filepathtype === '') {
|
||||
return { repo_url: target };
|
||||
}
|
||||
|
||||
const sourceFolder = integration.resolveUrl({
|
||||
url: `./${docsFolder}`,
|
||||
base: target,
|
||||
});
|
||||
return { edit_uri: integration.resolveEditUrl(sourceFolder) };
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return {};
|
||||
};
|
||||
|
||||
class UnknownTag {
|
||||
@@ -217,7 +198,7 @@ export const validateMkdocsYaml = async (
|
||||
* Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.
|
||||
*
|
||||
* List of tasks:
|
||||
* - Add repo_url if it does not exists
|
||||
* - Add repo_url or edit_uri if it does not exists
|
||||
* If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default.
|
||||
* If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get
|
||||
* the repository URL.
|
||||
@@ -228,11 +209,13 @@ export const validateMkdocsYaml = async (
|
||||
* @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
|
||||
* @param {Logger} logger
|
||||
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
|
||||
* @param {ScmIntegrationRegistry} scmIntegrations the scmIntegration to do url transformations
|
||||
*/
|
||||
export const patchMkdocsYmlPreBuild = async (
|
||||
mkdocsYmlPath: string,
|
||||
logger: Logger,
|
||||
parsedLocationAnnotation: ParsedLocationAnnotation,
|
||||
scmIntegrations: ScmIntegrationRegistry,
|
||||
) => {
|
||||
let mkdocsYmlFileString;
|
||||
try {
|
||||
@@ -260,16 +243,25 @@ export const patchMkdocsYmlPreBuild = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// Add repo_url to mkdocs.yml if it is missing. This will enable the Page edit button generated by MkDocs.
|
||||
if (!('repo_url' in mkdocsYml)) {
|
||||
const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation);
|
||||
if (repoUrl !== undefined) {
|
||||
// mkdocs.yml will not build with invalid repo_url. So, make sure it is valid.
|
||||
// TODO: this is no longer working/meaningful because annotation type is
|
||||
// now only ever "url" or "dir." Should be re-implemented!
|
||||
if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) {
|
||||
mkdocsYml.repo_url = repoUrl;
|
||||
}
|
||||
// Add edit_uri and/or repo_url to mkdocs.yml if it is missing.
|
||||
// This will enable the Page edit button generated by MkDocs.
|
||||
// If the either has been set, keep the original value
|
||||
if (!('repo_url' in mkdocsYml) && !('edit_uri' in mkdocsYml)) {
|
||||
const result = getRepoUrlFromLocationAnnotation(
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
mkdocsYml.docs_dir,
|
||||
);
|
||||
|
||||
if (result.repo_url || result.edit_uri) {
|
||||
mkdocsYml.repo_url = result.repo_url;
|
||||
mkdocsYml.edit_uri = result.edit_uri;
|
||||
|
||||
logger.info(
|
||||
`Set ${JSON.stringify(
|
||||
result,
|
||||
)}. You can disable this feature by manually setting 'repo_url' or 'edit_uri' according to the MkDocs documentation at https://www.mkdocs.org/user-guide/configuration/#repo_url`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
ScmIntegrationRegistry,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
addBuildTimestampMetadata,
|
||||
getMkdocsYml,
|
||||
@@ -39,29 +43,39 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
private readonly logger: Logger;
|
||||
private readonly containerRunner: ContainerRunner;
|
||||
private readonly options: GeneratorConfig;
|
||||
private readonly scmIntegrations: ScmIntegrationRegistry;
|
||||
|
||||
static async fromConfig(
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
{
|
||||
containerRunner,
|
||||
logger,
|
||||
}: { containerRunner: ContainerRunner; logger: Logger },
|
||||
) {
|
||||
return new TechdocsGenerator({ logger, containerRunner, config });
|
||||
const scmIntegrations = ScmIntegrations.fromConfig(config);
|
||||
return new TechdocsGenerator({
|
||||
logger,
|
||||
containerRunner,
|
||||
config,
|
||||
scmIntegrations,
|
||||
});
|
||||
}
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
containerRunner,
|
||||
config,
|
||||
scmIntegrations,
|
||||
}: {
|
||||
logger: Logger;
|
||||
containerRunner: ContainerRunner;
|
||||
config: Config;
|
||||
scmIntegrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
this.logger = logger;
|
||||
this.options = readGeneratorConfig(config, logger);
|
||||
this.containerRunner = containerRunner;
|
||||
this.scmIntegrations = scmIntegrations;
|
||||
}
|
||||
|
||||
public async run({
|
||||
@@ -74,16 +88,19 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
}: GeneratorRunOptions): Promise<void> {
|
||||
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
|
||||
const { path: mkdocsYmlPath, content } = await getMkdocsYml(inputDir);
|
||||
|
||||
// validate the docs_dir first
|
||||
await validateMkdocsYaml(inputDir, content);
|
||||
|
||||
if (parsedLocationAnnotation) {
|
||||
await patchMkdocsYmlPreBuild(
|
||||
mkdocsYmlPath,
|
||||
childLogger,
|
||||
parsedLocationAnnotation,
|
||||
this.scmIntegrations,
|
||||
);
|
||||
}
|
||||
|
||||
await validateMkdocsYaml(inputDir, content);
|
||||
|
||||
// Directories to bind on container
|
||||
const mountDirs = {
|
||||
[inputDir]: '/input',
|
||||
|
||||
@@ -70,10 +70,9 @@ export async function startStandaloneServer(
|
||||
const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
|
||||
const generators = new Generators();
|
||||
const techdocsGenerator = new TechdocsGenerator({
|
||||
const techdocsGenerator = TechdocsGenerator.fromConfig(config, {
|
||||
logger,
|
||||
containerRunner,
|
||||
config,
|
||||
});
|
||||
generators.register('techdocs', techdocsGenerator);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user