feat: add rollback_on_failure feature (#88)
Lint / pre-commit Linting (push) Successful in 44s
Test / Run Tests (push) Successful in 1m2s

This PR adds the ability to download existing code before deployment and automatically roll back if the post-deployment monitor detects a failure.

Reviewed-on: #88
Reviewed-by: LLMReview <27+autoreview@noreplay.horstenkamp.eu>
This commit was merged in pull request #88.
This commit is contained in:
2026-05-17 13:13:58 +02:00
parent 076e96f3de
commit d0a08da728
5 changed files with 161 additions and 48 deletions
+82 -25
View File
@@ -118,17 +118,19 @@ export function validateAuthentication(token, username, password) {
* @param {'ignore'|'warn'|'fail'} action
* @param {boolean} flag - Only acts when true
* @param {string} message - Passed to core.warning / core.setFailed
* @returns {boolean} - Returns true if the action was 'fail' and the flag was true.
*/
export function applyOnAction(action, flag, message) {
if (!flag) return;
if (!flag) return false;
if (action === "warn") {
core.warning(message);
return;
return false;
}
if (action === "fail") {
core.setFailed(message);
return true;
}
// 'ignore' → no-op
return false;
}
/**
@@ -179,33 +181,72 @@ export async function postCode() {
return;
}
let api = new ScreepsAPI(login_arguments);
if (token) {
if (!token) {
core.info(`Logging in as user ${username}`);
try {
await api.auth(username, password, login_arguments);
} catch (err) {
core.error(`Authentication error: ${err}`);
throw err;
}
}
let oldCode = null;
let rollbackOnFailure = false;
try {
rollbackOnFailure = core.getBooleanInput("rollback_on_failure");
} catch (e) {
// getBooleanInput throws if not 'true' or 'false', ignore
}
if (rollbackOnFailure) {
core.info(
`Downloading existing code from branch ${branch} for potential rollback...`,
);
try {
const getResponse = await api.code.get(branch);
if (getResponse && getResponse.ok && getResponse.modules) {
oldCode = getResponse.modules;
core.info(
`Successfully downloaded existing code (modules: ${Object.keys(oldCode).join(", ")})`,
);
} else {
core.setFailed(
`Failed to download existing code, but rollback_on_failure is enabled. Aborting deployment.`,
);
return;
}
} catch (err) {
core.setFailed(
`Error downloading existing code: ${err.message}. Aborting deployment.`,
);
return;
}
}
try {
const response = await api.code.set(branch, files_to_push);
core.info(JSON.stringify(response, null, 2));
core.info(`Code set successfully to ${branch}`);
} else {
core.info(`Logging in as user ${username}`);
await Promise.resolve()
.then(() => api.auth(username, password, login_arguments))
.then(() => api.code.set(branch, files_to_push))
.then(() => {
core.info(`Code set successfully to ${branch}`);
})
.catch((err) => {
core.error(`Upload error: ${err}`);
throw err;
});
} catch (err) {
core.error(`Upload error: ${err}`);
throw err;
}
// Console monitoring (optional)
const monitorTicks = parseInt(core.getInput("monitor") || "0", 10);
if (monitorTicks > 0) {
const onTraceback = core.getInput("on_traceback") || "fail";
const onErrorLog = core.getInput("on_error_log") || "warn";
const onWarningLog = core.getInput("on_warning_log") || "ignore";
const result = await monitorConsole(api, {
monitor: monitorTicks,
logToFile: core.getBooleanInput("log_to_file"),
onTraceback: core.getInput("on_traceback") || "fail",
onErrorLog: core.getInput("on_error_log") || "warn",
onWarningLog: core.getInput("on_warning_log") || "ignore",
onTraceback,
onErrorLog,
onWarningLog,
monitorInterval: parseInt(core.getInput("monitor_interval") || "10", 10),
hostname,
shard: core.getInput("shard") || undefined,
@@ -215,21 +256,37 @@ export async function postCode() {
core.setOutput("saw_error_log", String(result.sawErrorLog));
core.setOutput("saw_warning_log", String(result.sawWarningLog));
applyOnAction(
core.getInput("on_traceback"),
const fail1 = applyOnAction(
onTraceback,
result.sawTraceback,
"Screeps console: traceback detected",
);
applyOnAction(
core.getInput("on_error_log"),
const fail2 = applyOnAction(
onErrorLog,
result.sawErrorLog,
"Screeps console: error log output detected",
);
applyOnAction(
core.getInput("on_warning_log"),
const fail3 = applyOnAction(
onWarningLog,
result.sawWarningLog,
"Screeps console: warning log output detected",
);
const shouldFail = fail1 || fail2 || fail3;
if (shouldFail && rollbackOnFailure && oldCode) {
core.info(
"Action failed based on monitor configuration. Rolling back to previous code...",
);
try {
await api.code.set(branch, oldCode);
core.info(
`Successfully rolled back to previous code on branch ${branch}.`,
);
} catch (err) {
core.error(`Rollback failed: ${err}`);
}
}
}
}