mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 03:32:54 -05:00
* [AI] Desktop client, E2E, loot-core, sync-server and tooling updates Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor database handling in various modules to use async/await for improved readability and error handling. This includes updates to database opening and closing methods across multiple files, ensuring consistent asynchronous behavior. Additionally, minor adjustments were made to encryption functions to support async operations. * Refactor sync migration tests to utilize async/await for improved readability. Updated transaction handling to streamline event expectations and cleanup process. * Refactor various functions to utilize async/await for improved readability and error handling. Updated service stopping, encryption, and file upload/download methods to ensure consistent asynchronous behavior across the application. * Refactor BudgetFileSelection component to use async/await for onSelect method, enhancing error handling and readability. Update merge tests to utilize async/await for improved clarity in transaction merging expectations. * Refactor filesystem module to use async/await for init function and related database operations, enhancing error handling and consistency across file interactions. Updated tests to reflect asynchronous behavior in database operations and file writing. * Fix typo in init function declaration to ensure it returns a Promise<void> instead of Proise<void>. * Update VRT screenshots Auto-generated by VRT workflow PR: #6987 * Update tests to use async/await for init function in web filesystem, ensuring consistent asynchronous behavior in database operations. * Update VRT screenshot for payees filter test to reflect recent changes * [AI] Fix no-floating-promises lint error in desktop-electron Wrapped queuedClientWinLogs.map() with Promise.all and void operator to properly handle the array of promises for executing queued logs. Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com> * Refactor promise handling in global and sync event handlers * Update VRT screenshots Auto-generated by VRT workflow PR: #6987 --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
182 lines
4.8 KiB
TypeScript
182 lines
4.8 KiB
TypeScript
import { exec } from 'node:child_process';
|
|
import { existsSync, writeFile } from 'node:fs';
|
|
import { exit } from 'node:process';
|
|
|
|
import prompts from 'prompts';
|
|
|
|
async function run() {
|
|
const username = await execAsync(
|
|
"gh api user --jq '.login'",
|
|
'To avoid having to enter your username, consider installing the official GitHub CLI (https://github.com/cli/cli) and logging in with `gh auth login`.',
|
|
);
|
|
const activePr = await getActivePr(username);
|
|
if (activePr) {
|
|
console.log(
|
|
`Found potentially matching PR ${activePr.number}: ${activePr.title}`,
|
|
);
|
|
}
|
|
const initialPrNumber = activePr?.number ?? (await getNextPrNumber());
|
|
|
|
const result = await prompts([
|
|
{
|
|
name: 'githubUsername',
|
|
message: 'Comma-separated GitHub username(s)',
|
|
type: 'text',
|
|
initial: username,
|
|
},
|
|
{
|
|
name: 'pullRequestNumber',
|
|
message: 'PR Number',
|
|
type: 'number',
|
|
initial: initialPrNumber,
|
|
},
|
|
{
|
|
name: 'releaseNoteType',
|
|
message: 'Release Note Type',
|
|
type: 'select',
|
|
choices: [
|
|
{ title: '✨ Features', value: 'Features' },
|
|
{ title: '👍 Enhancements', value: 'Enhancements' },
|
|
{ title: '🐛 Bugfixes', value: 'Bugfixes' },
|
|
{ title: '⚙️ Maintenance', value: 'Maintenance' },
|
|
],
|
|
},
|
|
{
|
|
name: 'oneLineSummary',
|
|
message: 'Brief Summary',
|
|
type: 'text',
|
|
initial: activePr?.title,
|
|
},
|
|
]);
|
|
|
|
if (
|
|
!result.githubUsername ||
|
|
!result.oneLineSummary ||
|
|
!result.releaseNoteType ||
|
|
!result.pullRequestNumber
|
|
) {
|
|
console.log('All questions must be answered. Exiting');
|
|
exit(1);
|
|
}
|
|
|
|
const fileContents = getFileContents(
|
|
result.releaseNoteType,
|
|
result.githubUsername,
|
|
result.oneLineSummary,
|
|
);
|
|
const prNumber = result.pullRequestNumber;
|
|
|
|
const filepath = `./upcoming-release-notes/${prNumber}.md`;
|
|
if (existsSync(filepath)) {
|
|
const { confirm } = await prompts({
|
|
name: 'confirm',
|
|
type: 'confirm',
|
|
message: `This will overwrite the existing release note ${filepath} Are you sure?`,
|
|
});
|
|
if (!confirm) {
|
|
console.log('Exiting');
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
writeFile(filepath, fileContents, err => {
|
|
if (err) {
|
|
console.error('Failed to write release note file:', err);
|
|
exit(1);
|
|
} else {
|
|
console.log(`Release note generated successfully: ${filepath}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// makes an attempt to find an existing open PR from <username>:<branch>
|
|
async function getActivePr(
|
|
username: string,
|
|
): Promise<{ number: number; title: string } | undefined> {
|
|
if (!username) {
|
|
return undefined;
|
|
}
|
|
const branchName = await execAsync('git rev-parse --abbrev-ref HEAD');
|
|
if (!branchName) {
|
|
return undefined;
|
|
}
|
|
const forkHead = `${username}:${branchName}`;
|
|
return getPrNumberFromHead(forkHead);
|
|
}
|
|
|
|
async function getPrNumberFromHead(
|
|
head: string,
|
|
): Promise<{ number: number; title: string } | undefined> {
|
|
try {
|
|
// head is a weird query parameter in this API call. If nothing matches, it
|
|
// will return as if the head query parameter doesn't exist. To get around
|
|
// this, we make the page size 2 and only return the number if the length.
|
|
const resp = await fetch(
|
|
'https://api.github.com/repos/actualbudget/actual/pulls?state=open&per_page=2&head=' +
|
|
head,
|
|
);
|
|
if (!resp.ok) {
|
|
console.warn('error fetching from github pulls api:', resp.status);
|
|
return undefined;
|
|
}
|
|
const ghResponse = await resp.json();
|
|
if (ghResponse?.length === 1) {
|
|
return ghResponse[0];
|
|
} else {
|
|
return undefined;
|
|
}
|
|
} catch (e) {
|
|
console.warn('error fetching from github pulls api:', e);
|
|
}
|
|
}
|
|
|
|
async function getNextPrNumber(): Promise<number> {
|
|
try {
|
|
const resp = await fetch(
|
|
'https://api.github.com/repos/actualbudget/actual/issues?state=all&per_page=1',
|
|
);
|
|
if (!resp.ok) {
|
|
throw new Error(`API responded with status: ${resp.status}`);
|
|
}
|
|
const ghResponse = await resp.json();
|
|
const latestPrNumber = ghResponse?.[0]?.number;
|
|
if (!latestPrNumber) {
|
|
console.error(
|
|
'Could not find latest issue number in GitHub API response',
|
|
ghResponse,
|
|
);
|
|
exit(1);
|
|
}
|
|
return latestPrNumber + 1;
|
|
} catch (error) {
|
|
console.error('Failed to fetch next PR number:', error);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
function getFileContents(type: string, username: string, summary: string) {
|
|
return `---
|
|
category: ${type}
|
|
authors: [${username}]
|
|
---
|
|
|
|
${summary}
|
|
`;
|
|
}
|
|
|
|
// simple exec that fails silently and returns an empty string on failure
|
|
async function execAsync(cmd: string, errorLog?: string): Promise<string> {
|
|
return new Promise<string>(res => {
|
|
exec(cmd, (error, stdout) => {
|
|
if (error) {
|
|
console.log(errorLog);
|
|
res('');
|
|
} else {
|
|
res(stdout.trim());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void run();
|