WIP
This commit is contained in:
parent
f32c78a94b
commit
d6ddd8aa45
@ -1,2 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
.env
|
||||||
|
@ -29,11 +29,14 @@ export const GEMINI_MODEL = process.env.GEMINI_MODEL || 'gemini-1.5-pro';
|
|||||||
|
|
||||||
// Function configuration
|
// Function configuration
|
||||||
export const DEBUG = process.env.DEBUG === 'true';
|
export const DEBUG = process.env.DEBUG === 'true';
|
||||||
|
export const USE_LOCAL_REPO = process.env.USE_LOCAL_REPO === 'true';
|
||||||
|
|
||||||
// Validate required configuration
|
// Validate required configuration
|
||||||
export function validateConfig(): void {
|
export function validateConfig(): void {
|
||||||
const missingVars: string[] = [];
|
const missingVars: string[] = [];
|
||||||
|
|
||||||
|
// Only check for main repo URL and credentials if not using local repo
|
||||||
|
if (!USE_LOCAL_REPO) {
|
||||||
if (!MAIN_REPO_URL) {
|
if (!MAIN_REPO_URL) {
|
||||||
missingVars.push('MAIN_REPO_URL');
|
missingVars.push('MAIN_REPO_URL');
|
||||||
}
|
}
|
||||||
@ -41,6 +44,7 @@ export function validateConfig(): void {
|
|||||||
if (!MAIN_REPO_TOKEN && (!MAIN_REPO_USERNAME || !MAIN_REPO_PASSWORD)) {
|
if (!MAIN_REPO_TOKEN && (!MAIN_REPO_USERNAME || !MAIN_REPO_PASSWORD)) {
|
||||||
missingVars.push('MAIN_REPO_TOKEN or MAIN_REPO_USERNAME/MAIN_REPO_PASSWORD');
|
missingVars.push('MAIN_REPO_TOKEN or MAIN_REPO_USERNAME/MAIN_REPO_PASSWORD');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!GOOGLE_CLOUD_PROJECT_ID) {
|
if (!GOOGLE_CLOUD_PROJECT_ID) {
|
||||||
missingVars.push('GOOGLE_CLOUD_PROJECT_ID');
|
missingVars.push('GOOGLE_CLOUD_PROJECT_ID');
|
||||||
@ -53,6 +57,14 @@ export function validateConfig(): void {
|
|||||||
|
|
||||||
// Get repository credentials for the main repository
|
// Get repository credentials for the main repository
|
||||||
export function getMainRepoCredentials(): { type: 'username-password' | 'token'; username?: string; password?: string; token?: string } {
|
export function getMainRepoCredentials(): { type: 'username-password' | 'token'; username?: string; password?: string; token?: string } {
|
||||||
|
if (USE_LOCAL_REPO) {
|
||||||
|
// Return dummy credentials when using local repo
|
||||||
|
return {
|
||||||
|
type: 'token',
|
||||||
|
token: 'dummy-token-for-local-repo'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (MAIN_REPO_TOKEN) {
|
if (MAIN_REPO_TOKEN) {
|
||||||
return {
|
return {
|
||||||
type: 'token',
|
type: 'token',
|
||||||
|
@ -6,7 +6,7 @@ import { validateConfig } from './config';
|
|||||||
try {
|
try {
|
||||||
validateConfig();
|
validateConfig();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Configuration error:', error.message);
|
console.error('Configuration error:', error instanceof Error ? error.message : String(error));
|
||||||
// Don't throw here to allow the function to start, but it will fail when executed
|
// Don't throw here to allow the function to start, but it will fail when executed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,28 @@ describe('ProjectService', () => {
|
|||||||
});
|
});
|
||||||
expect(fs.readFileSync).toHaveBeenCalledWith('path/to/project/INFO.md', 'utf-8');
|
expect(fs.readFileSync).toHaveBeenCalledWith('path/to/project/INFO.md', 'utf-8');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle project information that does not follow the expected format', async () => {
|
||||||
|
const infoContent = `# Project Name
|
||||||
|
|
||||||
|
This is a project description.
|
||||||
|
Some other content that doesn't match the expected format.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Mock fs.readFileSync to return malformed INFO.md content
|
||||||
|
(fs.readFileSync as jest.Mock).mockReturnValueOnce(infoContent);
|
||||||
|
|
||||||
|
const project = await projectService.readProjectInfo('path/to/project', 'project');
|
||||||
|
|
||||||
|
expect(project).toEqual({
|
||||||
|
name: 'project',
|
||||||
|
path: 'path/to/project',
|
||||||
|
repoHost: undefined,
|
||||||
|
repoUrl: undefined,
|
||||||
|
jiraComponent: undefined
|
||||||
|
});
|
||||||
|
expect(fs.readFileSync).toHaveBeenCalledWith('path/to/project/INFO.md', 'utf-8');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findWorkitems', () => {
|
describe('findWorkitems', () => {
|
||||||
|
@ -126,21 +126,16 @@ export class GeminiService {
|
|||||||
|
|
||||||
const currentDate = new Date().toISOString();
|
const currentDate = new Date().toISOString();
|
||||||
|
|
||||||
|
// Send the AI.md file directly to Gemini without hardcoded instructions
|
||||||
const prompt = `
|
const prompt = `
|
||||||
You are tasked with creating a Cucumber feature file based on a workitem description.
|
|
||||||
|
|
||||||
Project Guidelines:
|
|
||||||
${guidelines}
|
${guidelines}
|
||||||
|
|
||||||
Workitem:
|
Workitem:
|
||||||
${workitemContent}
|
${workitemContent}
|
||||||
|
|
||||||
Create a Cucumber feature file that implements this workitem according to the guidelines.
|
Include the following comment at the top of the generated file:
|
||||||
Include the following comment at the top of the file:
|
|
||||||
# Generated by prompts-to-test-spec on ${currentDate}
|
# Generated by prompts-to-test-spec on ${currentDate}
|
||||||
# Source: ${workitemName}
|
# Source: ${workitemName}
|
||||||
|
|
||||||
The feature file should be complete and ready to use in a Cucumber test suite.
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await generativeModel.generateContent({
|
const result = await generativeModel.generateContent({
|
||||||
@ -148,7 +143,86 @@ The feature file should be complete and ready to use in a Cucumber test suite.
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
const generatedText = response.candidates[0].content.parts[0].text;
|
const generatedText = response.candidates[0]?.content?.parts[0]?.text || '';
|
||||||
|
|
||||||
|
return generatedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a pull request description using Gemini API
|
||||||
|
* @param processedWorkitems List of processed workitems
|
||||||
|
* @returns Generated pull request description
|
||||||
|
*/
|
||||||
|
async generatePullRequestDescription(
|
||||||
|
processedWorkitems: { workitem: Workitem; success: boolean; error?: string }[]
|
||||||
|
): Promise<string> {
|
||||||
|
const generativeModel = this.vertexAI.getGenerativeModel({
|
||||||
|
model: this.model,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepare workitem data for the prompt
|
||||||
|
const added: string[] = [];
|
||||||
|
const updated: string[] = [];
|
||||||
|
const deleted: string[] = [];
|
||||||
|
const failed: string[] = [];
|
||||||
|
|
||||||
|
for (const item of processedWorkitems) {
|
||||||
|
const { workitem, success, error } = item;
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
failed.push(`- ${workitem.name}: ${error}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workitem.isActive) {
|
||||||
|
deleted.push(`- ${workitem.name}`);
|
||||||
|
} else if (workitem.implementation) {
|
||||||
|
updated.push(`- ${workitem.name}`);
|
||||||
|
} else {
|
||||||
|
added.push(`- ${workitem.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a structured summary of changes for Gemini
|
||||||
|
let workitemSummary = '';
|
||||||
|
|
||||||
|
if (added.length > 0) {
|
||||||
|
workitemSummary += 'Added workitems:\n' + added.join('\n') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated.length > 0) {
|
||||||
|
workitemSummary += 'Updated workitems:\n' + updated.join('\n') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted.length > 0) {
|
||||||
|
workitemSummary += 'Deleted workitems:\n' + deleted.join('\n') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed.length > 0) {
|
||||||
|
workitemSummary += 'Failed workitems:\n' + failed.join('\n') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = `
|
||||||
|
You are tasked with creating a pull request description for changes to test specifications.
|
||||||
|
|
||||||
|
The following is a summary of the changes made:
|
||||||
|
${workitemSummary}
|
||||||
|
|
||||||
|
Create a clear, professional pull request description that:
|
||||||
|
1. Explains that this PR was automatically generated by the prompts-to-test-spec function
|
||||||
|
2. Summarizes the changes (added, updated, deleted, and failed workitems)
|
||||||
|
3. Uses markdown formatting for better readability
|
||||||
|
4. Keeps the description concise but informative
|
||||||
|
|
||||||
|
The pull request description should be ready to use without further editing.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await generativeModel.generateContent({
|
||||||
|
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await result.response;
|
||||||
|
const generatedText = response.candidates[0]?.content?.parts[0]?.text || '';
|
||||||
|
|
||||||
return generatedText;
|
return generatedText;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
getGiteaCredentials,
|
getGiteaCredentials,
|
||||||
GOOGLE_CLOUD_PROJECT_ID,
|
GOOGLE_CLOUD_PROJECT_ID,
|
||||||
GOOGLE_CLOUD_LOCATION,
|
GOOGLE_CLOUD_LOCATION,
|
||||||
GEMINI_MODEL
|
GEMINI_MODEL,
|
||||||
|
USE_LOCAL_REPO
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
export class ProcessorService {
|
export class ProcessorService {
|
||||||
@ -42,9 +43,15 @@ export class ProcessorService {
|
|||||||
);
|
);
|
||||||
this.pullRequestService = new PullRequestService();
|
this.pullRequestService = new PullRequestService();
|
||||||
|
|
||||||
// Get main repository URL and credentials
|
// Get main repository URL and credentials only if not using local repo
|
||||||
|
if (!USE_LOCAL_REPO) {
|
||||||
this.mainRepoUrl = MAIN_REPO_URL;
|
this.mainRepoUrl = MAIN_REPO_URL;
|
||||||
this.mainRepoCredentials = getMainRepoCredentials();
|
this.mainRepoCredentials = getMainRepoCredentials();
|
||||||
|
} else {
|
||||||
|
// Set dummy values when using local repo
|
||||||
|
this.mainRepoUrl = '';
|
||||||
|
this.mainRepoCredentials = getMainRepoCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize other credentials
|
// Initialize other credentials
|
||||||
this.githubCredentials = getGithubCredentials();
|
this.githubCredentials = getGithubCredentials();
|
||||||
@ -84,12 +91,19 @@ export class ProcessorService {
|
|||||||
const results: ProcessResult[] = [];
|
const results: ProcessResult[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Clone the main repository
|
// Use local repository or clone the main repository
|
||||||
|
let mainRepoPath: string;
|
||||||
|
if (USE_LOCAL_REPO) {
|
||||||
|
console.log('Using local repository path');
|
||||||
|
mainRepoPath = path.resolve(__dirname, '../../..');
|
||||||
|
console.log(`Resolved local repository path: ${mainRepoPath}`);
|
||||||
|
} else {
|
||||||
console.log(`Cloning main repository: ${this.mainRepoUrl}`);
|
console.log(`Cloning main repository: ${this.mainRepoUrl}`);
|
||||||
const mainRepoPath = await this.repositoryService.cloneMainRepository(
|
mainRepoPath = await this.repositoryService.cloneMainRepository(
|
||||||
this.mainRepoUrl,
|
this.mainRepoUrl,
|
||||||
this.mainRepoCredentials
|
this.mainRepoCredentials
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Find all projects in the prompts directory
|
// Find all projects in the prompts directory
|
||||||
const promptsDir = path.join(mainRepoPath, 'src', 'prompts');
|
const promptsDir = path.join(mainRepoPath, 'src', 'prompts');
|
||||||
@ -98,10 +112,23 @@ export class ProcessorService {
|
|||||||
|
|
||||||
console.log(`Found ${projects.length} projects`);
|
console.log(`Found ${projects.length} projects`);
|
||||||
|
|
||||||
|
// Log details of each project
|
||||||
|
if (projects.length > 0) {
|
||||||
|
console.log('Projects found:');
|
||||||
|
projects.forEach((project, index) => {
|
||||||
|
console.log(` ${index + 1}. ${project.name} (${project.path})`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('No projects found. Check if the prompts directory exists and contains project folders.');
|
||||||
|
}
|
||||||
|
|
||||||
// Process each project
|
// Process each project
|
||||||
|
console.log('Starting to process projects...');
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
try {
|
try {
|
||||||
|
console.log(`Starting processing of project: ${project.name}`);
|
||||||
const result = await this.processProject(project);
|
const result = await this.processProject(project);
|
||||||
|
console.log(`Finished processing project: ${project.name}`);
|
||||||
results.push(result);
|
results.push(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error processing project ${project.name}:`, error);
|
console.error(`Error processing project ${project.name}:`, error);
|
||||||
@ -112,6 +139,7 @@ export class ProcessorService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(`Finished processing all ${projects.length} projects`);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -14,22 +14,38 @@ export class ProjectService {
|
|||||||
async findProjects(promptsDir: string): Promise<Project[]> {
|
async findProjects(promptsDir: string): Promise<Project[]> {
|
||||||
const projects: Project[] = [];
|
const projects: Project[] = [];
|
||||||
|
|
||||||
|
console.log(`ProjectService: Searching for projects in ${promptsDir}`);
|
||||||
|
|
||||||
|
// Check if prompts directory exists
|
||||||
|
if (!fs.existsSync(promptsDir)) {
|
||||||
|
console.log(`ProjectService: Directory does not exist: ${promptsDir}`);
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
// Get all directories in the prompts directory
|
// Get all directories in the prompts directory
|
||||||
const entries = fs.readdirSync(promptsDir, { withFileTypes: true });
|
const entries = fs.readdirSync(promptsDir, { withFileTypes: true });
|
||||||
const projectDirs = entries.filter(entry => entry.isDirectory());
|
const projectDirs = entries.filter(entry => entry.isDirectory());
|
||||||
|
|
||||||
|
console.log(`ProjectService: Found ${projectDirs.length} potential project directories`);
|
||||||
|
|
||||||
for (const dir of projectDirs) {
|
for (const dir of projectDirs) {
|
||||||
const projectPath = path.join(promptsDir, dir.name);
|
const projectPath = path.join(promptsDir, dir.name);
|
||||||
const infoPath = path.join(projectPath, 'INFO.md');
|
const infoPath = path.join(projectPath, 'INFO.md');
|
||||||
|
|
||||||
|
console.log(`ProjectService: Checking directory: ${dir.name}`);
|
||||||
|
|
||||||
// Skip directories without INFO.md
|
// Skip directories without INFO.md
|
||||||
if (!fs.existsSync(infoPath)) {
|
if (!fs.existsSync(infoPath)) {
|
||||||
|
console.log(`ProjectService: Skipping ${dir.name} - no INFO.md file found`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`ProjectService: Found INFO.md in ${dir.name}, reading project info`);
|
||||||
|
|
||||||
// Read project info
|
// Read project info
|
||||||
const project = await this.readProjectInfo(projectPath, dir.name);
|
const project = await this.readProjectInfo(projectPath, dir.name);
|
||||||
projects.push(project);
|
projects.push(project);
|
||||||
|
console.log(`ProjectService: Added project: ${project.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
@ -43,6 +59,7 @@ export class ProjectService {
|
|||||||
*/
|
*/
|
||||||
async readProjectInfo(projectPath: string, projectName: string): Promise<Project> {
|
async readProjectInfo(projectPath: string, projectName: string): Promise<Project> {
|
||||||
const infoPath = path.join(projectPath, 'INFO.md');
|
const infoPath = path.join(projectPath, 'INFO.md');
|
||||||
|
console.log(`ProjectService: Reading project info from ${infoPath}`);
|
||||||
const infoContent = fs.readFileSync(infoPath, 'utf-8');
|
const infoContent = fs.readFileSync(infoPath, 'utf-8');
|
||||||
|
|
||||||
// Parse INFO.md content
|
// Parse INFO.md content
|
||||||
@ -50,13 +67,20 @@ export class ProjectService {
|
|||||||
const repoUrlMatch = infoContent.match(/- \[[ x]\] Repo url: (.*)/);
|
const repoUrlMatch = infoContent.match(/- \[[ x]\] Repo url: (.*)/);
|
||||||
const jiraComponentMatch = infoContent.match(/- \[[ x]\] Jira component: (.*)/);
|
const jiraComponentMatch = infoContent.match(/- \[[ x]\] Jira component: (.*)/);
|
||||||
|
|
||||||
return {
|
const project = {
|
||||||
name: projectName,
|
name: projectName,
|
||||||
path: projectPath,
|
path: projectPath,
|
||||||
repoHost: repoHostMatch ? repoHostMatch[1].trim() : undefined,
|
repoHost: repoHostMatch ? repoHostMatch[1].trim() : undefined,
|
||||||
repoUrl: repoUrlMatch ? repoUrlMatch[1].trim() : undefined,
|
repoUrl: repoUrlMatch ? repoUrlMatch[1].trim() : undefined,
|
||||||
jiraComponent: jiraComponentMatch ? jiraComponentMatch[1].trim() : undefined
|
jiraComponent: jiraComponentMatch ? jiraComponentMatch[1].trim() : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(`ProjectService: Project info for ${projectName}:`);
|
||||||
|
console.log(` - Repository host: ${project.repoHost || 'Not found'}`);
|
||||||
|
console.log(` - Repository URL: ${project.repoUrl || 'Not found'}`);
|
||||||
|
console.log(` - Jira component: ${project.jiraComponent || 'Not found'}`);
|
||||||
|
|
||||||
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,8 +4,14 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { Project, RepoCredentials, Workitem } from '../types';
|
import { Project, RepoCredentials, Workitem } from '../types';
|
||||||
|
import { GeminiService } from './gemini-service';
|
||||||
|
|
||||||
export class PullRequestService {
|
export class PullRequestService {
|
||||||
|
private geminiService: GeminiService;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.geminiService = new GeminiService();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create a pull request for changes in a repository
|
* Create a pull request for changes in a repository
|
||||||
* @param project Project information
|
* @param project Project information
|
||||||
@ -26,7 +32,7 @@ export class PullRequestService {
|
|||||||
|
|
||||||
// Generate PR title and description
|
// Generate PR title and description
|
||||||
const title = `Update workitems: ${new Date().toISOString().split('T')[0]}`;
|
const title = `Update workitems: ${new Date().toISOString().split('T')[0]}`;
|
||||||
const description = this.generatePullRequestDescription(processedWorkitems);
|
const description = await this.generatePullRequestDescription(processedWorkitems);
|
||||||
|
|
||||||
// Determine the repository host type and create PR accordingly
|
// Determine the repository host type and create PR accordingly
|
||||||
if (project.repoHost.includes('github.com')) {
|
if (project.repoHost.includes('github.com')) {
|
||||||
@ -142,53 +148,14 @@ export class PullRequestService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a description for the pull request
|
* Generate a description for the pull request using Gemini
|
||||||
* @param processedWorkitems List of processed workitems
|
* @param processedWorkitems List of processed workitems
|
||||||
* @returns Pull request description
|
* @returns Pull request description
|
||||||
*/
|
*/
|
||||||
private generatePullRequestDescription(
|
private async generatePullRequestDescription(
|
||||||
processedWorkitems: { workitem: Workitem; success: boolean; error?: string }[]
|
processedWorkitems: { workitem: Workitem; success: boolean; error?: string }[]
|
||||||
): string {
|
): Promise<string> {
|
||||||
const added: string[] = [];
|
// Use Gemini to generate the pull request description
|
||||||
const updated: string[] = [];
|
return await this.geminiService.generatePullRequestDescription(processedWorkitems);
|
||||||
const deleted: string[] = [];
|
|
||||||
const failed: string[] = [];
|
|
||||||
|
|
||||||
for (const item of processedWorkitems) {
|
|
||||||
const { workitem, success, error } = item;
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
failed.push(`- ${workitem.name}: ${error}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workitem.isActive) {
|
|
||||||
deleted.push(`- ${workitem.name}`);
|
|
||||||
} else if (workitem.implementation) {
|
|
||||||
updated.push(`- ${workitem.name}`);
|
|
||||||
} else {
|
|
||||||
added.push(`- ${workitem.name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = 'This PR was automatically generated by the prompts-to-test-spec function.\n\n';
|
|
||||||
|
|
||||||
if (added.length > 0) {
|
|
||||||
description += '## Added\n' + added.join('\n') + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated.length > 0) {
|
|
||||||
description += '## Updated\n' + updated.join('\n') + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleted.length > 0) {
|
|
||||||
description += '## Deleted\n' + deleted.join('\n') + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed.length > 0) {
|
|
||||||
description += '## Failed\n' + failed.join('\n') + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return description;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,5 @@ export interface ProcessResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}[];
|
}[];
|
||||||
pullRequestUrl?: string;
|
pullRequestUrl?: string;
|
||||||
|
error?: string;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user