Skip to content

Commit

Permalink
Added support for fetching/adding/removing labels and adding a comment.
Browse files Browse the repository at this point in the history
  • Loading branch information
psmyrek committed Dec 5, 2023
1 parent cac6d91 commit db5c671
Show file tree
Hide file tree
Showing 15 changed files with 553 additions and 74 deletions.
3 changes: 3 additions & 0 deletions packages/ckeditor5-dev-stale-bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Also, the `CKE5_GITHUB_TOKEN` environment variable is required to run the script
The following configuration options are supported by the stale bot:

* `REPOSITORY_SLUG` – Required. The repository name in the format of `owner/name`, where stale bot will check for stale issues and pull requests.
* `STALE_LABELS` – Required. List of label names to apply on staled issues and pull requests.
* `STALE_ISSUE_MESSAGE` – Required. A comment that is added on the staled issues.
* `STALE_PR_MESSAGE` – Required. A comment that is added on the staled pull requests.
* `DAYS_BEFORE_STALE` – Optional, 365 by default. The number of days without the required activity that qualifies an issue or pull request to be marked as stale. The dates taken into account are:
* the creation date,
* the last edit of an issue or pull request,
Expand Down
92 changes: 56 additions & 36 deletions packages/ckeditor5-dev-stale-bot/bin/stale-bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* For licensing, see LICENSE.md.
*/

const ora = require( 'ora' );
const fs = require( 'fs-extra' );
const chalk = require( 'chalk' );
const createSpinner = require( './utils/createspinner' );
const parseArguments = require( './utils/parsearguments' );
const prepareOptions = require( './utils/prepareoptions' );
const validateConfig = require( './utils/validateconfig' );
const prepareSearchOptions = require( './utils/preparesearchoptions' );
const GitHubRepository = require( '../lib/githubrepository' );

main().catch( error => {
Expand All @@ -31,70 +32,89 @@ async function main() {

const config = await fs.readJson( configPath );

if ( !config.REPOSITORY_SLUG ) {
throw new Error( 'Missing configuration option: REPOSITORY_SLUG' );
}

validateConfig( config );
printWelcomeMessage( dryRun );

const githubRepository = new GitHubRepository( process.env.CKE5_GITHUB_TOKEN );

const viewerLogin = await githubRepository.getViewerLogin();
const spinner = createSpinner();

const spinner = ora().start();
const { issuesToStale, pullRequestsToStale } = await searchToStale( githubRepository, config, spinner );

printStatus( spinner, '🔎 Searching for issues to stale...' );
if ( !issuesToStale.length && !pullRequestsToStale.length ) {
spinner.instance.stop();

const issuesToStaleOptions = prepareOptions( viewerLogin, 'issue', config );
const issuesToStale = await githubRepository.searchIssuesToStale( issuesToStaleOptions, onProgress( spinner ) );
console.log( chalk.green.bold( '✨ No issues or pull requests found that should be marked as stale.' ) );

printStatus( spinner, '🔎 Searching for pull requests to stale...' );
return;
}

const pullRequestsToStaleOptions = prepareOptions( viewerLogin, 'pr', config );
const pullRequestsToStale = await githubRepository.searchIssuesToStale( pullRequestsToStaleOptions, onProgress( spinner ) );
const entries = [ ...issuesToStale, ...pullRequestsToStale ];

spinner.stop();
if ( !dryRun ) {
await markAsStale( githubRepository, entries, config, spinner );
}

if ( !issuesToStale.length && !pullRequestsToStale.length ) {
console.log( chalk.green.bold( '✨ No issues or pull requests found that should be marked as stale.' ) );
spinner.instance.stop();

return;
}
const statusMessage = dryRun ?
'🔖 The following issues or pull requests should be marked as stale:\n' :
'🔖 The following issues or pull requests were marked as stale:\n';

console.log( chalk.blue.bold( '🔖 The following issues or pull requests should be marked as stale:\n' ) );
console.log( chalk.blue.bold( statusMessage ) );

[ ...issuesToStale, ...pullRequestsToStale ].forEach( entry => console.log( entry.slug ) );
entries.forEach( entry => console.log( entry.url ) );
}

function printWelcomeMessage( dryRun ) {
const message = [
'',
`🦾 ${ chalk.bold( 'STALE BOT' ) }`,
'',
dryRun ?
chalk.italic( `The --dry-run flag is ${ chalk.green.bold( 'ON' ) }, so bot does not perform any changes.` ) :
chalk.italic( `The --dry-run flag is ${ chalk.red.bold( 'OFF' ) }, so bot makes use of your real, live, production data.` ),
chalk.italic( `The --dry-run flag is ${ chalk.red.bold( 'OFF' ) }, so bot makes use of real, live, production data.` ),
''
];

console.log( message.join( '\n' ) );
}

function printStatus( spinner, text ) {
spinner.text = text;
async function searchToStale( githubRepository, config, spinner ) {
const viewerLogin = await githubRepository.getViewerLogin();

if ( !spinner.isSpinning ) {
console.log( text );
}
}
spinner.printStatus( '🔎 Searching for issues to stale...' );

const issuesToStaleOptions = prepareSearchOptions( viewerLogin, 'issue', config );
const issuesToStale = await githubRepository.searchIssuesToStale( issuesToStaleOptions, spinner.onProgressFactory() );

function onProgress( spinner ) {
const title = spinner.text;
spinner.printStatus( '🔎 Searching for pull requests to stale...' );

return ( { done, total } ) => {
const progress = total ? Math.round( ( done / total ) * 100 ) : 0;
const text = `${ title } ${ chalk.bold( `${ progress }%` ) }`;
const pullRequestsToStaleOptions = prepareSearchOptions( viewerLogin, 'pr', config );
const pullRequestsToStale = await githubRepository.searchIssuesToStale( pullRequestsToStaleOptions, spinner.onProgressFactory() );

printStatus( spinner, text );
return {
issuesToStale,
pullRequestsToStale
};
}

async function markAsStale( githubRepository, entries, config, spinner ) {
spinner.printStatus( '🔎 Fetching stale labels...' );

const staleLabels = await githubRepository.getLabels( config.REPOSITORY_SLUG, config.STALE_LABELS );

spinner.printStatus( '🔎 Marking found issues and pull requests as stale...' );

const onProgress = spinner.onProgressFactory();

for ( const entry of entries ) {
onProgress( {
done: entries.indexOf( entry ),
total: entries.length
} );

const staleMessage = entry.type === 'Issue' ? config.STALE_ISSUE_MESSAGE : config.STALE_PR_MESSAGE;

await githubRepository.addLabels( entry.id, staleLabels.map( label => label.id ) );
await githubRepository.addComment( entry.id, staleMessage );
}
}
44 changes: 44 additions & 0 deletions packages/ckeditor5-dev-stale-bot/bin/utils/createspinner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

const ora = require( 'ora' );
const chalk = require( 'chalk' );

/**
* Creates the spinner instance with methods to update spinner text.
*
* @returns {Object} data
* @returns {ora.Ora} data.instance
* @returns {Function} data.printStatus
* @returns {Function} data.onProgressFactory
*/
module.exports = function createSpinner() {
const instance = ora().start();

const printStatus = text => {
instance.text = text;

if ( !instance.isSpinning ) {
console.log( text );
}
};

const onProgressFactory = () => {
const title = instance.text;

return ( { done, total } ) => {
const progress = total ? Math.round( ( done / total ) * 100 ) : 0;
const text = `${ title } ${ chalk.bold( `${ progress }%` ) }`;

printStatus( text );
};
};

return {
instance,
printStatus,
onProgressFactory
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const { subDays, formatISO } = require( 'date-fns' );
* @param {String} viewerLogin The GitHub login of the currently authenticated user.
* @param {'issue'|'pr'} type Type of GitHub resource.
* @param {Config} config Configuration options.
* @returns {Options} Converted options.
* @returns {SearchOptions} Converted options.
*/
module.exports = function prepareOptions( viewerLogin, type, config ) {
module.exports = function prepareSearchOptions( viewerLogin, type, config ) {
const {
REPOSITORY_SLUG,
DAYS_BEFORE_STALE = 365,
Expand Down Expand Up @@ -56,7 +56,7 @@ module.exports = function prepareOptions( viewerLogin, type, config ) {
*/

/**
* @typedef {Object} Options
* @typedef {Object} SearchOptions
* @property {String} type
* @property {String} repositorySlug
* @property {String} staleDate
Expand Down
28 changes: 28 additions & 0 deletions packages/ckeditor5-dev-stale-bot/bin/utils/validateconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* Checks if all required fields in the configuration exist.
*
* @param {Config} config Configuration options.
* @returns {Boolean}
*/
module.exports = function validateConfig( config ) {
if ( !config.REPOSITORY_SLUG ) {
throw new Error( 'Missing configuration option: REPOSITORY_SLUG' );
}

if ( !config.STALE_LABELS ) {
throw new Error( 'Missing configuration option: STALE_LABELS' );
}

if ( !config.STALE_ISSUE_MESSAGE ) {
throw new Error( 'Missing configuration option: STALE_ISSUE_MESSAGE' );
}

if ( !config.STALE_PR_MESSAGE ) {
throw new Error( 'Missing configuration option: STALE_PR_MESSAGE' );
}
};

0 comments on commit db5c671

Please sign in to comment.