diff --git a/README.md b/README.md index eb65b46b9..701ff4146 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This can be achieved with the following [configuration in the action](https://do ```yaml permissions: + actions: write # for caching state contents: write # only for delete-branch option issues: write pull-requests: write @@ -97,6 +98,7 @@ Every argument is optional. | [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | | | [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | | | [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` | +| [cache-prefix](#cache-prefix) | Add a prefix to the stored cache | | ### List of output options @@ -547,6 +549,11 @@ If set to `true`, only the issues or the pull requests with an assignee will be Default value: `false` +#### cache-prefix + +Beneficial so the action has a more unique cache key. Useful when calling this action multiple times, independent of each other. +An example for usage would be closing all PRs with `x` label after 7 days in one action, and closing all PRs except for `x` label after 10 days in another. + ### Usage See also [action.yml](./action.yml) for a comprehensive list of all the options. diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 0265b6446..1f3786df7 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -55,5 +55,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ ignorePrUpdates: undefined, exemptDraftPr: false, closeIssueReason: 'not_planned', - includeOnlyAssigned: false + includeOnlyAssigned: false, + cachePrefix: '' }); diff --git a/action.yml b/action.yml index d55f8547c..efe80a867 100644 --- a/action.yml +++ b/action.yml @@ -204,6 +204,10 @@ inputs: description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.' default: 'false' required: false + cache-prefix: + description: 'Add a prefix to the stored cache. Useful when using this action multiple times with different params.' + default: '' + required: false outputs: closed-issues-prs: description: 'List of all closed issues and pull requests.' diff --git a/dist/index.js b/dist/index.js index f2786a0f6..9946252e9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1644,22 +1644,27 @@ const resetCacheWithOctokit = (cacheKey) => __awaiter(void 0, void 0, void 0, fu } }); class StateCacheStorage { + constructor(options) { + this.statePrefix = options.cachePrefix; + } save(serializedState) { return __awaiter(this, void 0, void 0, function* () { const tmpDir = mkTempDir(); - const filePath = path_1.default.join(tmpDir, STATE_FILE); + const stateFile = `${this.statePrefix}_${STATE_FILE}`; + const filePath = path_1.default.join(tmpDir, stateFile); fs_1.default.writeFileSync(filePath, serializedState); try { - const cacheExists = yield checkIfCacheExists(CACHE_KEY); + const cacheKey = `${this.statePrefix}${CACHE_KEY}`; + const cacheExists = yield checkIfCacheExists(cacheKey); if (cacheExists) { - yield resetCacheWithOctokit(CACHE_KEY); + yield resetCacheWithOctokit(cacheKey); } const fileSize = fs_1.default.statSync(filePath).size; if (fileSize === 0) { core.info(`the state will be removed`); return; } - yield cache.saveCache([path_1.default.dirname(filePath)], CACHE_KEY); + yield cache.saveCache([path_1.default.dirname(filePath)], cacheKey); } catch (error) { core.warning(`Saving the state was not successful due to "${error.message || 'unknown reason'}"`); @@ -1672,20 +1677,22 @@ class StateCacheStorage { restore() { return __awaiter(this, void 0, void 0, function* () { const tmpDir = mkTempDir(); - const filePath = path_1.default.join(tmpDir, STATE_FILE); + const stateFile = `${this.statePrefix}_${STATE_FILE}`; + const filePath = path_1.default.join(tmpDir, stateFile); unlinkSafely(filePath); try { - const cacheExists = yield checkIfCacheExists(CACHE_KEY); + const cacheKey = `${this.statePrefix}${CACHE_KEY}`; + const cacheExists = yield checkIfCacheExists(cacheKey); if (!cacheExists) { core.info('The saved state was not found, the process starts from the first issue.'); return ''; } - yield cache.restoreCache([path_1.default.dirname(filePath)], CACHE_KEY); + yield cache.restoreCache([path_1.default.dirname(filePath)], cacheKey); if (!fs_1.default.existsSync(filePath)) { core.warning('Unknown error when unpacking the cache, the process starts from the first issue.'); return ''; } - return fs_1.default.readFileSync(path_1.default.join(tmpDir, STATE_FILE), { + return fs_1.default.readFileSync(path_1.default.join(tmpDir, stateFile), { encoding: 'utf8' }); } @@ -2222,6 +2229,7 @@ var Option; Option["IgnorePrUpdates"] = "ignore-pr-updates"; Option["ExemptDraftPr"] = "exempt-draft-pr"; Option["CloseIssueReason"] = "close-issue-reason"; + Option["CachePrefix"] = "cache-prefix"; })(Option || (exports.Option = Option = {})); @@ -2567,7 +2575,8 @@ function _getAndValidateArgs() { ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', closeIssueReason: core.getInput('close-issue-reason'), - includeOnlyAssigned: core.getInput('include-only-assigned') === 'true' + includeOnlyAssigned: core.getInput('include-only-assigned') === 'true', + cachePrefix: core.getInput('cache-prefix') }; for (const numberInput of ['days-before-stale']) { if (isNaN(parseFloat(core.getInput(numberInput)))) { @@ -2694,7 +2703,7 @@ exports.getStateInstance = void 0; const state_1 = __nccwpck_require__(5186); const state_cache_storage_1 = __nccwpck_require__(3709); const getStateInstance = (options) => { - const storage = new state_cache_storage_1.StateCacheStorage(); + const storage = new state_cache_storage_1.StateCacheStorage(options); return new state_1.State(storage, options); }; exports.getStateInstance = getStateInstance; diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index a2c82e268..8fd704d89 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -64,7 +64,8 @@ describe('Issue', (): void => { ignorePrUpdates: undefined, exemptDraftPr: false, closeIssueReason: '', - includeOnlyAssigned: false + includeOnlyAssigned: false, + cachePrefix: '' }; issueInterface = { title: 'dummy-title', diff --git a/src/classes/state/state-cache-storage.ts b/src/classes/state/state-cache-storage.ts index b30b503b6..2eefd5b90 100644 --- a/src/classes/state/state-cache-storage.ts +++ b/src/classes/state/state-cache-storage.ts @@ -6,6 +6,7 @@ import * as core from '@actions/core'; import {context, getOctokit} from '@actions/github'; import {retry as octokitRetry} from '@octokit/plugin-retry'; import * as cache from '@actions/cache'; +import {IIssuesProcessorOptions} from '../../interfaces/issues-processor-options'; const CACHE_KEY = '_state'; const STATE_FILE = 'state.txt'; @@ -65,15 +66,26 @@ const resetCacheWithOctokit = async (cacheKey: string): Promise => { } }; export class StateCacheStorage implements IStateStorage { + /** + * @private don't mutate in the debug mode + */ + private readonly statePrefix: string; + + constructor(options: IIssuesProcessorOptions) { + this.statePrefix = options.cachePrefix; + } + async save(serializedState: string): Promise { const tmpDir = mkTempDir(); - const filePath = path.join(tmpDir, STATE_FILE); + const stateFile = `${this.statePrefix}_${STATE_FILE}`; + const filePath = path.join(tmpDir, stateFile); fs.writeFileSync(filePath, serializedState); try { - const cacheExists = await checkIfCacheExists(CACHE_KEY); + const cacheKey = `${this.statePrefix}${CACHE_KEY}`; + const cacheExists = await checkIfCacheExists(cacheKey); if (cacheExists) { - await resetCacheWithOctokit(CACHE_KEY); + await resetCacheWithOctokit(cacheKey); } const fileSize = fs.statSync(filePath).size; @@ -82,7 +94,7 @@ export class StateCacheStorage implements IStateStorage { return; } - await cache.saveCache([path.dirname(filePath)], CACHE_KEY); + await cache.saveCache([path.dirname(filePath)], cacheKey); } catch (error) { core.warning( `Saving the state was not successful due to "${ @@ -96,10 +108,12 @@ export class StateCacheStorage implements IStateStorage { async restore(): Promise { const tmpDir = mkTempDir(); - const filePath = path.join(tmpDir, STATE_FILE); + const stateFile = `${this.statePrefix}_${STATE_FILE}`; + const filePath = path.join(tmpDir, stateFile); unlinkSafely(filePath); try { - const cacheExists = await checkIfCacheExists(CACHE_KEY); + const cacheKey = `${this.statePrefix}${CACHE_KEY}`; + const cacheExists = await checkIfCacheExists(cacheKey); if (!cacheExists) { core.info( 'The saved state was not found, the process starts from the first issue.' @@ -107,7 +121,7 @@ export class StateCacheStorage implements IStateStorage { return ''; } - await cache.restoreCache([path.dirname(filePath)], CACHE_KEY); + await cache.restoreCache([path.dirname(filePath)], cacheKey); if (!fs.existsSync(filePath)) { core.warning( @@ -115,7 +129,7 @@ export class StateCacheStorage implements IStateStorage { ); return ''; } - return fs.readFileSync(path.join(tmpDir, STATE_FILE), { + return fs.readFileSync(path.join(tmpDir, stateFile), { encoding: 'utf8' }); } catch (error) { diff --git a/src/enums/option.ts b/src/enums/option.ts index 7a9bff026..ae56d3109 100644 --- a/src/enums/option.ts +++ b/src/enums/option.ts @@ -48,5 +48,6 @@ export enum Option { IgnoreIssueUpdates = 'ignore-issue-updates', IgnorePrUpdates = 'ignore-pr-updates', ExemptDraftPr = 'exempt-draft-pr', - CloseIssueReason = 'close-issue-reason' + CloseIssueReason = 'close-issue-reason', + CachePrefix = 'cache-prefix' } diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 930992284..63da21222 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions { exemptDraftPr: boolean; closeIssueReason: string; includeOnlyAssigned: boolean; + cachePrefix: string; } diff --git a/src/main.ts b/src/main.ts index a7836c160..71ee237e2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -123,7 +123,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', closeIssueReason: core.getInput('close-issue-reason'), - includeOnlyAssigned: core.getInput('include-only-assigned') === 'true' + includeOnlyAssigned: core.getInput('include-only-assigned') === 'true', + cachePrefix: core.getInput('cache-prefix') }; for (const numberInput of ['days-before-stale']) { diff --git a/src/services/state.service.ts b/src/services/state.service.ts index 77e403ecc..31e30b40c 100644 --- a/src/services/state.service.ts +++ b/src/services/state.service.ts @@ -4,6 +4,6 @@ import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; import {StateCacheStorage} from '../classes/state/state-cache-storage'; export const getStateInstance = (options: IIssuesProcessorOptions): IState => { - const storage = new StateCacheStorage(); + const storage = new StateCacheStorage(options); return new State(storage, options); };