feat: Add retry mechanism with configurable attempts, wait time, and timeout

New input parameters:
- max-attempts (default: 1) - Maximum number of build attempts
- retry-wait-seconds (default: 0) - Delay between retry attempts
- timeout-minutes (default: 0) - Timeout per attempt (0 = no timeout)

Implementation:
- Wraps build execution in retry loop with comprehensive logging
- Adds timeout support per attempt using Promise.race()
- Fully backward compatible (default values maintain current behavior)
- Adds 2 test cases

Signed-off-by: Mor Weinberger <test@example.com>
This commit is contained in:
Mor Weinberger 2025-10-28 10:26:39 +02:00
parent 9e436ba9f2
commit e4bf8b61e3
7 changed files with 201 additions and 26 deletions

View file

@ -879,6 +879,77 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`]
);
});
describe('getInputs', () => {
beforeEach(() => {
process.env = Object.keys(process.env).reduce((object, key) => {
if (!key.startsWith('INPUT_')) {
object[key] = process.env[key];
}
return object;
}, {});
});
test('should parse retry inputs with default values', async () => {
setInput('context', '.');
setInput('load', 'false');
setInput('no-cache', 'false');
setInput('push', 'false');
setInput('pull', 'false');
setInput('max-attempts', '1');
setInput('retry-wait-seconds', '5');
setInput('timeout-minutes', '0');
const inputs = await context.getInputs();
expect(inputs['max-attempts']).toBe(1);
expect(inputs['retry-wait-seconds']).toBe(5);
expect(inputs['timeout-minutes']).toBe(0);
});
test('should parse retry inputs with custom values', async () => {
setInput('context', '.');
setInput('max-attempts', '3');
setInput('retry-wait-seconds', '30');
setInput('timeout-minutes', '10');
setInput('load', 'false');
setInput('no-cache', 'false');
setInput('push', 'false');
setInput('pull', 'false');
const inputs = await context.getInputs();
expect(inputs['max-attempts']).toBe(3);
expect(inputs['retry-wait-seconds']).toBe(30);
expect(inputs['timeout-minutes']).toBe(10);
});
test('should parse invalid retry inputs as NaN', async () => {
setInput('context', '.');
setInput('max-attempts', 'invalid');
setInput('retry-wait-seconds', 'abc');
setInput('load', 'false');
setInput('no-cache', 'false');
setInput('push', 'false');
setInput('pull', 'false');
const inputs = await context.getInputs();
expect(isNaN(inputs['max-attempts'])).toBe(true);
expect(isNaN(inputs['retry-wait-seconds'])).toBe(true);
});
test('should parse negative and zero values', async () => {
setInput('context', '.');
setInput('max-attempts', '0');
setInput('retry-wait-seconds', '-10');
setInput('load', 'false');
setInput('no-cache', 'false');
setInput('push', 'false');
setInput('pull', 'false');
const inputs = await context.getInputs();
expect(inputs['max-attempts']).toBe(0);
expect(inputs['retry-wait-seconds']).toBe(-10);
});
});
// See: https://github.com/actions/toolkit/blob/a1b068ec31a042ff1e10a522d8fdf0b8869d53ca/packages/core/src/core.ts#L89
function getInputName(name: string): string {
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;