mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Auto generate Release Notes (#5182)
* AI Generated release notes * lint * removedverbose console.log
This commit is contained in:
75
.github/actions/ai-generated-release-notes/check-first-comment.js
vendored
Executable file
75
.github/actions/ai-generated-release-notes/check-first-comment.js
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import fs from 'fs';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.GITHUB_REPOSITORY;
|
||||
const issueNumber = process.env.GITHUB_EVENT_ISSUE_NUMBER;
|
||||
const commentId = process.env.GITHUB_EVENT_COMMENT_ID;
|
||||
|
||||
if (!token || !repo || !issueNumber || !commentId) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
function setOutput(name, value) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
async function checkFirstComment() {
|
||||
try {
|
||||
console.log('Fetching comments with Octokit...');
|
||||
|
||||
// Get all comments with automatic pagination
|
||||
const comments = await octokit.paginate(octokit.rest.issues.listComments, {
|
||||
owner,
|
||||
repo: repoName,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
|
||||
console.log(`Total comments found: ${comments.length}`);
|
||||
|
||||
// Filter for CodeRabbit summary comments (containing the specific marker)
|
||||
const coderabbitSummaryComments = comments.filter(comment => {
|
||||
const isCodeRabbit = comment.user.login === 'coderabbitai[bot]';
|
||||
const hasSummaryMarker = comment.body.includes(
|
||||
'<!-- This is an auto-generated comment: summarize by coderabbit.ai -->',
|
||||
);
|
||||
|
||||
if (isCodeRabbit) {
|
||||
console.log(
|
||||
`CodeRabbit comment found (ID: ${comment.id}), has summary marker: ${hasSummaryMarker}`,
|
||||
);
|
||||
}
|
||||
|
||||
return isCodeRabbit && hasSummaryMarker;
|
||||
});
|
||||
|
||||
const isFirstSummaryComment =
|
||||
coderabbitSummaryComments.length === 1 &&
|
||||
coderabbitSummaryComments[0].id == commentId;
|
||||
|
||||
console.log(
|
||||
`CodeRabbit summary comments found: ${coderabbitSummaryComments.length}`,
|
||||
);
|
||||
console.log(`Current comment ID: ${commentId}`);
|
||||
console.log(`Is first summary comment: ${isFirstSummaryComment}`);
|
||||
setOutput('result', isFirstSummaryComment);
|
||||
} catch (error) {
|
||||
console.log('Error checking CodeRabbit comment:', error.message);
|
||||
console.log('Stack:', error.stack);
|
||||
setOutput('result', 'false');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
checkFirstComment().catch(error => {
|
||||
console.log('Unhandled error:', error.message);
|
||||
console.log('Stack:', error.stack);
|
||||
setOutput('result', 'false');
|
||||
process.exit(1);
|
||||
});
|
||||
76
.github/actions/ai-generated-release-notes/check-release-notes-exists.js
vendored
Executable file
76
.github/actions/ai-generated-release-notes/check-release-notes-exists.js
vendored
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import fs from 'fs';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.GITHUB_REPOSITORY;
|
||||
const issueNumber = process.env.GITHUB_EVENT_ISSUE_NUMBER;
|
||||
const prDetailsJson = process.env.PR_DETAILS;
|
||||
|
||||
if (!token || !repo || !issueNumber || !prDetailsJson) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
function setOutput(name, value) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
async function checkReleaseNotesExists() {
|
||||
try {
|
||||
const prDetails = JSON.parse(prDetailsJson);
|
||||
if (!prDetails) {
|
||||
console.log('No PR details available, skipping file check');
|
||||
setOutput('result', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = `upcoming-release-notes/${prDetails.number}.md`;
|
||||
|
||||
// Get PR info to get head SHA
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo: repoName,
|
||||
pull_number: issueNumber,
|
||||
});
|
||||
|
||||
const prHeadSha = pr.head.sha;
|
||||
console.log(
|
||||
`Checking for file on PR branch: ${pr.head.ref} (${prHeadSha})`,
|
||||
);
|
||||
|
||||
// Check if file exists
|
||||
try {
|
||||
await octokit.rest.repos.getContent({
|
||||
owner,
|
||||
repo: repoName,
|
||||
path: fileName,
|
||||
ref: prHeadSha,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Release notes file already exists on PR branch: ${fileName}`,
|
||||
);
|
||||
setOutput('result', 'true');
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
console.log(
|
||||
`No existing release notes file found on PR branch: ${fileName}`,
|
||||
);
|
||||
setOutput('result', 'false');
|
||||
} else {
|
||||
console.log('Error checking file existence:', error.message);
|
||||
setOutput('result', 'false');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error in file existence check:', error.message);
|
||||
setOutput('result', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
checkReleaseNotesExists();
|
||||
76
.github/actions/ai-generated-release-notes/comment-on-pr.js
vendored
Executable file
76
.github/actions/ai-generated-release-notes/comment-on-pr.js
vendored
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.GITHUB_REPOSITORY;
|
||||
const issueNumber = process.env.GITHUB_EVENT_ISSUE_NUMBER;
|
||||
const summaryDataJson = process.env.SUMMARY_DATA;
|
||||
const category = process.env.CATEGORY;
|
||||
|
||||
if (!token || !repo || !issueNumber || !summaryDataJson || !category) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
async function commentOnPR() {
|
||||
try {
|
||||
const summaryData = JSON.parse(summaryDataJson);
|
||||
|
||||
if (!summaryData) {
|
||||
console.log('No summary data available, skipping comment');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!category || category === 'null') {
|
||||
console.log('No valid category available, skipping comment');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean category for display
|
||||
const cleanCategory =
|
||||
typeof category === 'string'
|
||||
? category.replace(/^["']|["']$/g, '')
|
||||
: category;
|
||||
|
||||
// Get PR info for the file URL
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo: repoName,
|
||||
pull_number: issueNumber,
|
||||
});
|
||||
|
||||
const prBranch = pr.head.ref;
|
||||
const headOwner = pr.head.repo.owner.login;
|
||||
const headRepo = pr.head.repo.name;
|
||||
const fileUrl = `https://github.com/${headOwner}/${headRepo}/blob/${prBranch}/upcoming-release-notes/${summaryData.prNumber}.md`;
|
||||
|
||||
const commentBody = [
|
||||
'🤖 **Auto-generated Release Notes**',
|
||||
'',
|
||||
`Hey @${summaryData.author}! I've automatically created a release notes file based on CodeRabbit's analysis:`,
|
||||
'',
|
||||
`**Category:** ${cleanCategory}`,
|
||||
`**Summary:** ${summaryData.summary}`,
|
||||
`**File:** [upcoming-release-notes/${summaryData.prNumber}.md](${fileUrl})`,
|
||||
'',
|
||||
'The release notes file has been committed to the repository. You can edit it if needed before merging.',
|
||||
].join('\n');
|
||||
|
||||
await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo: repoName,
|
||||
issue_number: issueNumber,
|
||||
body: commentBody,
|
||||
});
|
||||
|
||||
console.log('✅ Successfully commented on PR');
|
||||
} catch (error) {
|
||||
console.log('Error commenting on PR:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
commentOnPR();
|
||||
96
.github/actions/ai-generated-release-notes/create-release-notes-file.js
vendored
Executable file
96
.github/actions/ai-generated-release-notes/create-release-notes-file.js
vendored
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.GITHUB_REPOSITORY;
|
||||
const issueNumber = process.env.GITHUB_EVENT_ISSUE_NUMBER;
|
||||
const summaryDataJson = process.env.SUMMARY_DATA;
|
||||
const category = process.env.CATEGORY;
|
||||
|
||||
if (!token || !repo || !issueNumber || !summaryDataJson || !category) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
async function createReleaseNotesFile() {
|
||||
try {
|
||||
const summaryData = JSON.parse(summaryDataJson);
|
||||
|
||||
console.log('Debug - Category value:', category);
|
||||
console.log('Debug - Category type:', typeof category);
|
||||
console.log('Debug - Category JSON stringified:', JSON.stringify(category));
|
||||
|
||||
if (!summaryData) {
|
||||
console.log('No summary data available, cannot create file');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!category || category === 'null') {
|
||||
console.log('No valid category available, cannot create file');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create file content - ensure category is not quoted
|
||||
const cleanCategory =
|
||||
typeof category === 'string'
|
||||
? category.replace(/^["']|["']$/g, '')
|
||||
: category;
|
||||
console.log('Debug - Clean category:', cleanCategory);
|
||||
|
||||
const fileContent = `---
|
||||
category: ${cleanCategory}
|
||||
authors: [${summaryData.author}]
|
||||
---
|
||||
|
||||
${summaryData.summary}`;
|
||||
|
||||
const fileName = `upcoming-release-notes/${summaryData.prNumber}.md`;
|
||||
|
||||
console.log(`Creating release notes file: ${fileName}`);
|
||||
console.log('File content:');
|
||||
console.log(fileContent);
|
||||
|
||||
// Get PR info
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo: repoName,
|
||||
pull_number: issueNumber,
|
||||
});
|
||||
|
||||
const prBranch = pr.head.ref;
|
||||
const headOwner = pr.head.repo.owner.login;
|
||||
const headRepo = pr.head.repo.name;
|
||||
|
||||
console.log(
|
||||
`Committing to PR branch: ${headOwner}/${headRepo}:${prBranch}`,
|
||||
);
|
||||
|
||||
// Create the file via GitHub API on the PR branch
|
||||
await octokit.rest.repos.createOrUpdateFileContents({
|
||||
owner: headOwner,
|
||||
repo: headRepo,
|
||||
path: fileName,
|
||||
message: `Add release notes for PR #${summaryData.prNumber}`,
|
||||
content: Buffer.from(`${fileContent}\n\n`).toString('base64'),
|
||||
branch: prBranch,
|
||||
committer: {
|
||||
name: 'github-actions[bot]',
|
||||
email: 'github-actions[bot]@users.noreply.github.com',
|
||||
},
|
||||
author: {
|
||||
name: 'github-actions[bot]',
|
||||
email: 'github-actions[bot]@users.noreply.github.com',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Successfully created release notes file: ${fileName}`);
|
||||
} catch (error) {
|
||||
console.log('Error creating release notes file:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
createReleaseNotesFile();
|
||||
118
.github/actions/ai-generated-release-notes/determine-category.js
vendored
Executable file
118
.github/actions/ai-generated-release-notes/determine-category.js
vendored
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const commentBody = process.env.GITHUB_EVENT_COMMENT_BODY;
|
||||
const prDetailsJson = process.env.PR_DETAILS;
|
||||
const summaryDataJson = process.env.SUMMARY_DATA;
|
||||
const openaiApiKey = process.env.OPENAI_API_KEY;
|
||||
|
||||
if (!commentBody || !prDetailsJson || !summaryDataJson || !openaiApiKey) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function setOutput(name, value) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
const prDetails = JSON.parse(prDetailsJson);
|
||||
const summaryData = JSON.parse(summaryDataJson);
|
||||
|
||||
if (!summaryData || !prDetails) {
|
||||
console.log('Missing data for categorization');
|
||||
setOutput('result', 'null');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const data = JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are categorizing pull requests for release notes. You must respond with exactly one of these categories: "Features", "Enhancements", "Bugfix", or "Maintenance". No other text or explanation.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `PR Title: ${prDetails.title}\n\nGenerated Summary: ${summaryData.summary}\n\nCodeRabbit Analysis:\n${commentBody}\n\nCategories:\n- Features: New functionality or capabilities\n- Bugfix: Fixes for broken or incorrect behavior\n- Enhancements: Improvements to existing functionality\n- Maintenance: Code cleanup, refactoring, dependencies, etc.\n\nWhat category does this PR belong to?`,
|
||||
},
|
||||
],
|
||||
max_tokens: 10,
|
||||
temperature: 0.1,
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: 'api.openai.com',
|
||||
path: '/v1/chat/completions',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${openaiApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const req = https.request(options, res => {
|
||||
let responseData = '';
|
||||
res.on('data', chunk => (responseData += chunk));
|
||||
res.on('end', () => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.log('OpenAI API error for categorization');
|
||||
setOutput('result', 'null');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(responseData);
|
||||
console.log('OpenAI raw response:', JSON.stringify(response, null, 2));
|
||||
|
||||
const rawContent = response.choices[0].message.content.trim();
|
||||
console.log('Raw content from OpenAI:', rawContent);
|
||||
|
||||
let category;
|
||||
try {
|
||||
category = JSON.parse(rawContent);
|
||||
console.log('Parsed category:', category);
|
||||
} catch (parseError) {
|
||||
console.log(
|
||||
'JSON parse error, using raw content:',
|
||||
parseError.message,
|
||||
);
|
||||
category = rawContent;
|
||||
}
|
||||
|
||||
// Validate the category response
|
||||
const validCategories = [
|
||||
'Features',
|
||||
'Bugfix',
|
||||
'Enhancements',
|
||||
'Maintenance',
|
||||
];
|
||||
if (validCategories.includes(category)) {
|
||||
console.log('OpenAI categorized as:', category);
|
||||
setOutput('result', category);
|
||||
} else {
|
||||
console.log('Invalid category from OpenAI:', category);
|
||||
console.log('Valid categories are:', validCategories);
|
||||
setOutput('result', 'null');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error parsing OpenAI response:', error.message);
|
||||
setOutput('result', 'null');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
console.log('Error in categorization:', error.message);
|
||||
setOutput('result', 'null');
|
||||
});
|
||||
|
||||
req.write(data);
|
||||
req.end();
|
||||
} catch (error) {
|
||||
console.log('Error in categorization:', error.message);
|
||||
setOutput('result', 'null');
|
||||
}
|
||||
97
.github/actions/ai-generated-release-notes/generate-summary.js
vendored
Executable file
97
.github/actions/ai-generated-release-notes/generate-summary.js
vendored
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const commentBody = process.env.GITHUB_EVENT_COMMENT_BODY;
|
||||
const prDetailsJson = process.env.PR_DETAILS;
|
||||
const openaiApiKey = process.env.OPENAI_API_KEY;
|
||||
|
||||
if (!commentBody || !prDetailsJson || !openaiApiKey) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function setOutput(name, value) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
const prDetails = JSON.parse(prDetailsJson);
|
||||
|
||||
if (!prDetails) {
|
||||
console.log('No PR details available, cannot generate summary');
|
||||
setOutput('result', 'null');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log('CodeRabbit comment body:', commentBody);
|
||||
|
||||
const data = JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are a technical writer helping to create concise release notes. Generate a maximum 15-word summary that describes what this PR does. Focus on the user-facing changes or bug fixes. Do not include "This PR" or similar phrases - just describe the change directly. Start with a base form verb (e.g., "Add" not "Adds", "Fix" not "Fixes", "Introduce" not "Introduces").',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `PR Title: ${prDetails.title}\n\nCodeRabbit Analysis:\n${commentBody}\n\nPlease provide a concise summary (max 15 words) of what this PR accomplishes.`,
|
||||
},
|
||||
],
|
||||
max_tokens: 50,
|
||||
temperature: 0.3,
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: 'api.openai.com',
|
||||
path: '/v1/chat/completions',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${openaiApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const req = https.request(options, res => {
|
||||
let responseData = '';
|
||||
res.on('data', chunk => (responseData += chunk));
|
||||
res.on('end', () => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.log(`OpenAI API error: ${res.statusCode} ${res.statusMessage}`);
|
||||
setOutput('result', 'null');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(responseData);
|
||||
const summary = response.choices[0].message.content.trim();
|
||||
|
||||
console.log('Generated summary:', summary);
|
||||
|
||||
const result = {
|
||||
summary: summary,
|
||||
prNumber: prDetails.number,
|
||||
author: prDetails.author,
|
||||
};
|
||||
|
||||
setOutput('result', JSON.stringify(result));
|
||||
} catch (error) {
|
||||
console.log('Error parsing OpenAI response:', error.message);
|
||||
setOutput('result', 'null');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
console.log('Error generating summary:', error.message);
|
||||
setOutput('result', 'null');
|
||||
});
|
||||
|
||||
req.write(data);
|
||||
req.end();
|
||||
} catch (error) {
|
||||
console.log('Error generating summary:', error.message);
|
||||
setOutput('result', 'null');
|
||||
}
|
||||
59
.github/actions/ai-generated-release-notes/pr-details.js
vendored
Executable file
59
.github/actions/ai-generated-release-notes/pr-details.js
vendored
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import fs from 'fs';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.GITHUB_REPOSITORY;
|
||||
const issueNumber = process.env.GITHUB_EVENT_ISSUE_NUMBER;
|
||||
|
||||
if (!token || !repo || !issueNumber) {
|
||||
console.log('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
function setOutput(name, value) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
async function getPRDetails() {
|
||||
try {
|
||||
console.log(
|
||||
`Fetching PR details for ${owner}/${repoName}#${issueNumber}...`,
|
||||
);
|
||||
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo: repoName,
|
||||
pull_number: issueNumber,
|
||||
});
|
||||
|
||||
console.log('PR details fetched successfully');
|
||||
console.log('- PR Number:', pr.number);
|
||||
console.log('- PR Author:', pr.user.login);
|
||||
console.log('- PR Title:', pr.title);
|
||||
|
||||
const result = {
|
||||
number: pr.number,
|
||||
author: pr.user.login,
|
||||
title: pr.title,
|
||||
};
|
||||
|
||||
setOutput('result', JSON.stringify(result));
|
||||
} catch (error) {
|
||||
console.log('Error getting PR details:', error.message);
|
||||
console.log('Stack:', error.stack);
|
||||
setOutput('result', 'null');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
getPRDetails().catch(error => {
|
||||
console.log('Unhandled error:', error.message);
|
||||
console.log('Stack:', error.stack);
|
||||
setOutput('result', 'null');
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user