diff --git a/README.md b/README.md index 60c6ba8..6c6969c 100644 --- a/README.md +++ b/README.md @@ -105,9 +105,13 @@ One of `never`, `same_content`, `same_content_newer`, `outdated_runs`, `always`. Returns true if the current run should be skipped according to your configured rules. This should be evaluated for either individual steps or entire jobs. +### `reason` + +The reason why the current run is considered skippable or unskippable. Corresponds approximately to the input options, for example `skip_after_successful_duplicate`. + ### `skipped_by` -Information about the workflow run which caused the current run to be skipped. +Information about the workflow run which caused the current run to be skipped. Returns information only when current run is considered skippable. ## Usage examples diff --git a/action.yml b/action.yml index f634604..acd43ef 100644 --- a/action.yml +++ b/action.yml @@ -36,6 +36,8 @@ inputs: outputs: should_skip: description: 'Returns true if the current run should be skipped according to your configured rules. This should be evaluated for either individual steps or entire jobs.' + reason: + description: 'The reason why the current run is considered skippable or unskippable. Corresponds approximately to the input options, for example `skip_after_successful_duplicate`.' skipped_by: description: 'Information about the workflow run which caused the current run to be skipped.' runs: diff --git a/dist/index.js b/dist/index.js index 00191e2..89f29e8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -147,8 +147,11 @@ function main() { if (e instanceof Error || typeof e === 'string') { core.warning(e); } - core.warning(`Failed to fetch the required workflow information`); - exitSuccess({ shouldSkip: false }); + core.warning('Failed to fetch the required workflow information'); + exitSuccess({ + shouldSkip: false, + reason: 'no_workflow_information' + }); } const cancelOthers = getBooleanInput('cancel_others', false); if (cancelOthers) { @@ -156,20 +159,41 @@ function main() { } if (context.doNotSkip.includes(context.currentRun.event)) { core.info(`Do not skip execution because the workflow was triggered with '${context.currentRun.event}'`); - exitSuccess({ shouldSkip: false }); + exitSuccess({ + shouldSkip: false, + reason: 'do_not_skip' + }); } const skipAfterSuccessfulDuplicates = getBooleanInput('skip_after_successful_duplicate', true); if (skipAfterSuccessfulDuplicates) { - detectSuccessfulDuplicateRuns(context); + const successfulDuplicateRun = detectSuccessfulDuplicateRuns(context); + if (successfulDuplicateRun) { + core.info(`Skip execution because the exact same files have been successfully checked in ${successfulDuplicateRun.html_url}`); + exitSuccess({ + shouldSkip: true, + reason: 'skip_after_successful_duplicate', + skippedBy: successfulDuplicateRun + }); + } } if (context.concurrentSkipping !== 'never') { - detectConcurrentRuns(context); + const concurrentRun = detectConcurrentRuns(context); + if (concurrentRun) { + exitSuccess({ + shouldSkip: true, + reason: 'concurrent_skipping', + skippedBy: concurrentRun + }); + } } if (context.paths.length >= 1 || context.pathsIgnore.length >= 1) { yield backtracePathSkipping(context); } core.info('Do not skip execution because we did not find a transferable run'); - exitSuccess({ shouldSkip: false }); + exitSuccess({ + shouldSkip: false, + reason: 'no_transferable_run' + }); } catch (e) { if (e instanceof Error) { @@ -191,7 +215,7 @@ function cancelOutdatedRuns(context) { run.repo === currentRun.repo); }); if (!cancelVictims.length) { - return core.info(`Did not find other workflow-runs to be cancelled`); + return core.info('Did not find other workflow-runs to be cancelled'); } for (const victim of cancelVictims) { yield cancelWorkflowRun(victim, context); @@ -221,10 +245,7 @@ function detectSuccessfulDuplicateRuns(context) { const successfulDuplicate = duplicateRuns.find(run => { return run.status === 'completed' && run.conclusion === 'success'; }); - if (successfulDuplicate) { - core.info(`Skip execution because the exact same files have been successfully checked in ${successfulDuplicate.html_url}`); - exitSuccess({ shouldSkip: true, skippedBy: successfulDuplicate }); - } + return successfulDuplicate; } function detectConcurrentRuns(context) { const concurrentRuns = context.allRuns.filter(run => { @@ -242,21 +263,21 @@ function detectConcurrentRuns(context) { } if (context.concurrentSkipping === 'always') { core.info(`Skip execution because another instance of the same workflow is already running in ${concurrentRuns[0].html_url}`); - exitSuccess({ shouldSkip: true, skippedBy: concurrentRuns[0] }); + return concurrentRuns[0]; } else if (context.concurrentSkipping === 'outdated_runs') { const newerRun = concurrentRuns.find(run => new Date(run.createdAt).getTime() > new Date(context.currentRun.createdAt).getTime()); if (newerRun) { core.info(`Skip execution because a newer instance of the same workflow is running in ${newerRun.html_url}`); - exitSuccess({ shouldSkip: true, skippedBy: newerRun }); + return newerRun; } } else if (context.concurrentSkipping === 'same_content') { const concurrentDuplicate = concurrentRuns.find(run => run.treeHash === context.currentRun.treeHash); if (concurrentDuplicate) { core.info(`Skip execution because the exact same files are concurrently checked in ${concurrentDuplicate.html_url}`); - exitSuccess({ shouldSkip: true, skippedBy: concurrentDuplicate }); + return concurrentDuplicate; } } else if (context.concurrentSkipping === 'same_content_newer') { @@ -264,10 +285,10 @@ function detectConcurrentRuns(context) { run.runNumber < context.currentRun.runNumber); if (concurrentIsOlder) { core.info(`Skip execution because the exact same files are concurrently checked in older ${concurrentIsOlder.html_url}`); - exitSuccess({ shouldSkip: true, skippedBy: concurrentIsOlder }); + return concurrentIsOlder; } } - core.info(`Did not find any skippable concurrent workflow-runs`); + core.info(`Did not find any concurrent workflow-runs that justify skipping`); } function backtracePathSkipping(context) { var _a, _b; @@ -300,7 +321,7 @@ function exitIfSuccessfulRunExists(commit, context) { }); if (successfulRun) { core.info(`Skip execution because all changes since ${successfulRun.html_url} are in ignored or skipped paths`); - exitSuccess({ shouldSkip: true, skippedBy: successfulRun }); + exitSuccess({ shouldSkip: true, reason: 'paths', skippedBy: successfulRun }); } } function isCommitSkippable(commit, context) { @@ -317,7 +338,7 @@ function isCommitSkippable(commit, context) { return false; } const globOptions = { - dot: true // Match dotfiles. Otherwise dotfiles are ignored unless a . is explicitly defined in the pattern. + dot: true // Match dotfiles. Otherwise dotfiles are ignored unless a "." is explicitly defined in the pattern. }; function isCommitPathIgnored(commit, context) { if (!context.pathsIgnore.length) { @@ -373,7 +394,8 @@ function fetchCommitDetails(sha, context) { } function exitSuccess(args) { core.setOutput('should_skip', args.shouldSkip); - core.setOutput('skipped_by', args.skippedBy); + core.setOutput('reason', args.reason); + core.setOutput('skipped_by', args.skippedBy || {}); return process.exit(0); } function formatCliOptions(options) { diff --git a/src/main.ts b/src/main.ts index 7fc021d..64df985 100644 --- a/src/main.ts +++ b/src/main.ts @@ -168,8 +168,11 @@ async function main(): Promise { if (e instanceof Error || typeof e === 'string') { core.warning(e) } - core.warning(`Failed to fetch the required workflow information`) - exitSuccess({shouldSkip: false}) + core.warning('Failed to fetch the required workflow information') + exitSuccess({ + shouldSkip: false, + reason: 'no_workflow_information' + }) } const cancelOthers = getBooleanInput('cancel_others', false) @@ -180,17 +183,37 @@ async function main(): Promise { core.info( `Do not skip execution because the workflow was triggered with '${context.currentRun.event}'` ) - exitSuccess({shouldSkip: false}) + exitSuccess({ + shouldSkip: false, + reason: 'do_not_skip' + }) } const skipAfterSuccessfulDuplicates = getBooleanInput( 'skip_after_successful_duplicate', true ) if (skipAfterSuccessfulDuplicates) { - detectSuccessfulDuplicateRuns(context) + const successfulDuplicateRun = detectSuccessfulDuplicateRuns(context) + if (successfulDuplicateRun) { + core.info( + `Skip execution because the exact same files have been successfully checked in ${successfulDuplicateRun.html_url}` + ) + exitSuccess({ + shouldSkip: true, + reason: 'skip_after_successful_duplicate', + skippedBy: successfulDuplicateRun + }) + } } if (context.concurrentSkipping !== 'never') { - detectConcurrentRuns(context) + const concurrentRun = detectConcurrentRuns(context) + if (concurrentRun) { + exitSuccess({ + shouldSkip: true, + reason: 'concurrent_skipping', + skippedBy: concurrentRun + }) + } } if (context.paths.length >= 1 || context.pathsIgnore.length >= 1) { await backtracePathSkipping(context) @@ -198,7 +221,10 @@ async function main(): Promise { core.info( 'Do not skip execution because we did not find a transferable run' ) - exitSuccess({shouldSkip: false}) + exitSuccess({ + shouldSkip: false, + reason: 'no_transferable_run' + }) } catch (e) { if (e instanceof Error) { core.error(e) @@ -220,7 +246,7 @@ async function cancelOutdatedRuns(context: WRunContext): Promise { ) }) if (!cancelVictims.length) { - return core.info(`Did not find other workflow-runs to be cancelled`) + return core.info('Did not find other workflow-runs to be cancelled') } for (const victim of cancelVictims) { await cancelWorkflowRun(victim, context) @@ -246,22 +272,19 @@ async function cancelWorkflowRun( } } -function detectSuccessfulDuplicateRuns(context: WRunContext): void { +function detectSuccessfulDuplicateRuns( + context: WRunContext +): WorkflowRun | undefined { const duplicateRuns = context.olderRuns.filter( run => run.treeHash === context.currentRun.treeHash ) const successfulDuplicate = duplicateRuns.find(run => { return run.status === 'completed' && run.conclusion === 'success' }) - if (successfulDuplicate) { - core.info( - `Skip execution because the exact same files have been successfully checked in ${successfulDuplicate.html_url}` - ) - exitSuccess({shouldSkip: true, skippedBy: successfulDuplicate}) - } + return successfulDuplicate } -function detectConcurrentRuns(context: WRunContext): void { +function detectConcurrentRuns(context: WRunContext): WorkflowRun | undefined { const concurrentRuns: WorkflowRun[] = context.allRuns.filter(run => { if (run.status === 'completed') { return false @@ -271,6 +294,7 @@ function detectConcurrentRuns(context: WRunContext): void { } return true }) + if (!concurrentRuns.length) { core.info(`Did not find any concurrent workflow-runs`) return @@ -279,7 +303,7 @@ function detectConcurrentRuns(context: WRunContext): void { core.info( `Skip execution because another instance of the same workflow is already running in ${concurrentRuns[0].html_url}` ) - exitSuccess({shouldSkip: true, skippedBy: concurrentRuns[0]}) + return concurrentRuns[0] } else if (context.concurrentSkipping === 'outdated_runs') { const newerRun = concurrentRuns.find( run => @@ -290,7 +314,7 @@ function detectConcurrentRuns(context: WRunContext): void { core.info( `Skip execution because a newer instance of the same workflow is running in ${newerRun.html_url}` ) - exitSuccess({shouldSkip: true, skippedBy: newerRun}) + return newerRun } } else if (context.concurrentSkipping === 'same_content') { const concurrentDuplicate = concurrentRuns.find( @@ -300,7 +324,7 @@ function detectConcurrentRuns(context: WRunContext): void { core.info( `Skip execution because the exact same files are concurrently checked in ${concurrentDuplicate.html_url}` ) - exitSuccess({shouldSkip: true, skippedBy: concurrentDuplicate}) + return concurrentDuplicate } } else if (context.concurrentSkipping === 'same_content_newer') { const concurrentIsOlder = concurrentRuns.find( @@ -312,10 +336,10 @@ function detectConcurrentRuns(context: WRunContext): void { core.info( `Skip execution because the exact same files are concurrently checked in older ${concurrentIsOlder.html_url}` ) - exitSuccess({shouldSkip: true, skippedBy: concurrentIsOlder}) + return concurrentIsOlder } } - core.info(`Did not find any skippable concurrent workflow-runs`) + core.info(`Did not find any concurrent workflow-runs that justify skipping`) } async function backtracePathSkipping(context: WRunContext): Promise { @@ -358,7 +382,7 @@ function exitIfSuccessfulRunExists( core.info( `Skip execution because all changes since ${successfulRun.html_url} are in ignored or skipped paths` ) - exitSuccess({shouldSkip: true, skippedBy: successfulRun}) + exitSuccess({shouldSkip: true, reason: 'paths', skippedBy: successfulRun}) } } @@ -386,7 +410,7 @@ function isCommitSkippable( } const globOptions = { - dot: true // Match dotfiles. Otherwise dotfiles are ignored unless a . is explicitly defined in the pattern. + dot: true // Match dotfiles. Otherwise dotfiles are ignored unless a "." is explicitly defined in the pattern. } function isCommitPathIgnored( @@ -458,10 +482,12 @@ async function fetchCommitDetails( function exitSuccess(args: { shouldSkip: boolean + reason: string skippedBy?: WorkflowRun }): never { core.setOutput('should_skip', args.shouldSkip) - core.setOutput('skipped_by', args.skippedBy) + core.setOutput('reason', args.reason) + core.setOutput('skipped_by', args.skippedBy || {}) return process.exit(0) }