feat: add readme card plugin on azure devops
Signed-off-by: Alisson Fabiano <afabiano@eshopworld.com>
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
convertDashboardPullRequest,
|
||||
convertPolicy,
|
||||
getArtifactId,
|
||||
replaceReadme,
|
||||
} from '../utils';
|
||||
|
||||
import { TeamMember as AdoTeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
@@ -52,11 +53,13 @@ import {
|
||||
TeamProjectReference,
|
||||
WebApiTeam,
|
||||
} from 'azure-devops-node-api/interfaces/CoreInterfaces';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
export class AzureDevOpsApi {
|
||||
public constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly webApi: WebApi,
|
||||
private readonly urlReader: UrlReader,
|
||||
) {}
|
||||
|
||||
public async getProjects(): Promise<Project[]> {
|
||||
@@ -393,6 +396,24 @@ export class AzureDevOpsApi {
|
||||
|
||||
return buildRuns;
|
||||
}
|
||||
|
||||
public async getReadme(
|
||||
host: string,
|
||||
organization: string,
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
): Promise<string> {
|
||||
const getContentFile = async (
|
||||
path: string,
|
||||
encoding?: BufferEncoding,
|
||||
): Promise<string> => {
|
||||
const url = `https://${host}/${organization}/${projectName}/_git/${repoName}?path=${path}`;
|
||||
const response = await this.urlReader.read(url);
|
||||
return Buffer.from(response).toString(encoding);
|
||||
};
|
||||
const readmeContent = await getContentFile('README.md');
|
||||
return await replaceReadme(readmeContent, getContentFile);
|
||||
}
|
||||
}
|
||||
|
||||
export function mappedRepoBuild(build: Build): RepoBuild {
|
||||
|
||||
@@ -53,6 +53,7 @@ describe('createRouter', () => {
|
||||
getBuildRuns: jest.fn(),
|
||||
getAllTeams: jest.fn(),
|
||||
getTeamMembers: jest.fn(),
|
||||
getReadme: jest.fn(),
|
||||
} as any;
|
||||
const router = await createRouter({
|
||||
azureDevOpsApi,
|
||||
@@ -455,4 +456,61 @@ describe('createRouter', () => {
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /readme/:projectName/:repoName', () => {
|
||||
it('fetches readme file', async () => {
|
||||
const readmeMock = getReadmeMock();
|
||||
azureDevOpsApi.getReadme.mockResolvedValueOnce(readmeMock);
|
||||
const response = await request(app).get(
|
||||
'/readme/myProject/myRepo?path=README.md',
|
||||
);
|
||||
expect(azureDevOpsApi.getReadme).toHaveBeenCalledWith(
|
||||
'host.com',
|
||||
'myOrg',
|
||||
'myProject',
|
||||
'myRepo',
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
content: readmeMock,
|
||||
url: `https://host.com/myOrg/myProject/_git/myRepo?path=README.md`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getReadmeMock() {
|
||||
return `
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
|
||||
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { Config } from '@backstage/config';
|
||||
import { Logger } from 'winston';
|
||||
import { PullRequestsDashboardProvider } from '../api/PullRequestsDashboardProvider';
|
||||
import Router from 'express-promise-router';
|
||||
import { errorHandler } from '@backstage/backend-common';
|
||||
import { errorHandler, UrlReaders } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
|
||||
const DEFAULT_TOP = 10;
|
||||
@@ -46,12 +46,13 @@ export async function createRouter(
|
||||
const token = config.getString('token');
|
||||
const host = config.getString('host');
|
||||
const organization = config.getString('organization');
|
||||
const reader = UrlReaders.default(options);
|
||||
|
||||
const authHandler = getPersonalAccessTokenHandler(token);
|
||||
const webApi = new WebApi(`https://${host}/${organization}`, authHandler);
|
||||
|
||||
const azureDevOpsApi =
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi);
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, reader);
|
||||
|
||||
const pullRequestsDashboardProvider =
|
||||
await PullRequestsDashboardProvider.create(logger, azureDevOpsApi);
|
||||
@@ -191,6 +192,20 @@ export async function createRouter(
|
||||
res.status(200).json(teamIds);
|
||||
});
|
||||
|
||||
router.get('/readme/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const content = await azureDevOpsApi.getReadme(
|
||||
host,
|
||||
organization,
|
||||
projectName,
|
||||
repoName,
|
||||
);
|
||||
res.status(200).json({
|
||||
content,
|
||||
url: `https://${host}/${organization}/${projectName}/_git/${repoName}?path=README.md`,
|
||||
});
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
|
||||
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces';
|
||||
import PathUtils from 'path';
|
||||
|
||||
export function convertDashboardPullRequest(
|
||||
pullRequest: GitPullRequest,
|
||||
@@ -205,6 +206,29 @@ export function convertPolicy(
|
||||
};
|
||||
}
|
||||
|
||||
export async function replaceReadme(
|
||||
content: string,
|
||||
getContentFile: (path: string, encoding?: BufferEncoding) => Promise<string>,
|
||||
) {
|
||||
const regExp =
|
||||
/\[([^\[\]]*)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/gim;
|
||||
const filesPath = content.match(regExp) || [];
|
||||
|
||||
if (filesPath.length === 0) return content;
|
||||
|
||||
let replacedContent = content;
|
||||
for (const filePath of filesPath) {
|
||||
const { label, path, ext } = getPartsFromFilePath(filePath);
|
||||
const mime = getMimeByExtension(ext);
|
||||
const base64 = await getContentFile(path, 'base64');
|
||||
replacedContent = replacedContent.replace(
|
||||
filePath,
|
||||
`[${label}](data:${mime};base64,${base64})`,
|
||||
);
|
||||
}
|
||||
return replacedContent;
|
||||
}
|
||||
|
||||
function convertReviewer(
|
||||
identityRef?: IdentityRefWithVote,
|
||||
): Reviewer | undefined {
|
||||
@@ -263,3 +287,29 @@ function convertCreatedBy(identityRef?: IdentityRef): CreatedBy | undefined {
|
||||
function hasAutoComplete(pullRequest: GitPullRequest): boolean {
|
||||
return pullRequest.isDraft !== true && !!pullRequest.completionOptions;
|
||||
}
|
||||
|
||||
function getPartsFromFilePath(content: string): {
|
||||
label: string;
|
||||
path: string;
|
||||
ext: string;
|
||||
} {
|
||||
const regExp = /\[(.*?)\]\(([^)]+)\)/;
|
||||
const [_, label, path] = regExp.exec(content) || [];
|
||||
return {
|
||||
label,
|
||||
path: path.startsWith('.') ? path.substring(1, path.length) : path,
|
||||
ext: PathUtils.extname(path),
|
||||
};
|
||||
}
|
||||
|
||||
function getMimeByExtension(ext: string): string {
|
||||
return (
|
||||
{
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
}[ext] || ''
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user