run post action cleanup in play.ts (#344)
* run post action cleanup in play script after main completes * clarify that GITHUB_RUN_ID is the actual bail-out gate in play context * treat GITHUB_RUN_ID as optional in post cleanup * replace dynamic import with static import of `runPostCleanup` Export `runPostCleanup` from post.ts and guard the top-level execution with `import.meta.url` so it only auto-runs as an entry point. play.ts now statically imports and calls it. * move runPostCleanup into finally block and let failures propagate * refactor post cleanup into utility module move post cleanup logic into a dedicated utility and keep post.ts as a pure script entrypoint. update play.ts to import the shared utility directly and normalize direct-execution detection. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
3bf2f8596f
commit
9948c08e7d
@@ -1,8 +1,8 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { mkdtemp } from "node:fs/promises";
|
import { mkdtemp } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import arg from "arg";
|
import arg from "arg";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
import type { AgentResult } from "./agents/shared.ts";
|
import type { AgentResult } from "./agents/shared.ts";
|
||||||
@@ -12,6 +12,7 @@ import { log } from "./utils/cli.ts";
|
|||||||
import { runInDocker } from "./utils/docker.ts";
|
import { runInDocker } from "./utils/docker.ts";
|
||||||
import { ensureGitHubToken } from "./utils/github.ts";
|
import { ensureGitHubToken } from "./utils/github.ts";
|
||||||
import { isInsideDocker } from "./utils/globals.ts";
|
import { isInsideDocker } from "./utils/globals.ts";
|
||||||
|
import { runPostCleanup } from "./utils/postCleanup.ts";
|
||||||
import { setupTestRepo } from "./utils/setup.ts";
|
import { setupTestRepo } from "./utils/setup.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,7 +69,13 @@ export async function run(inputsOrPrompt: Inputs | string): Promise<AgentResult>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await main();
|
// wrap main() so post cleanup runs even on failure (mirrors action.yml post-if: "failure() || cancelled()")
|
||||||
|
let result: AgentResult;
|
||||||
|
try {
|
||||||
|
result = await main();
|
||||||
|
} finally {
|
||||||
|
await runPostCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
process.chdir(originalCwd);
|
process.chdir(originalCwd);
|
||||||
|
|
||||||
@@ -95,7 +102,11 @@ export async function run(inputsOrPrompt: Inputs | string): Promise<AgentResult>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
const isDirectExecution = process.argv[1]
|
||||||
|
? import.meta.url === pathToFileURL(resolve(process.argv[1])).href
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (isDirectExecution) {
|
||||||
const args = arg({
|
const args = arg({
|
||||||
"--help": Boolean,
|
"--help": Boolean,
|
||||||
"--raw": String,
|
"--raw": String,
|
||||||
|
|||||||
@@ -25838,7 +25838,7 @@ var require_light = __commonJS({
|
|||||||
}
|
}
|
||||||
return this.Events.trigger("scheduled", { args: this.args, options: this.options });
|
return this.Events.trigger("scheduled", { args: this.args, options: this.options });
|
||||||
}
|
}
|
||||||
async doExecute(chained, clearGlobalState, run2, free) {
|
async doExecute(chained, clearGlobalState, run, free) {
|
||||||
var error2, eventInfo, passed;
|
var error2, eventInfo, passed;
|
||||||
if (this.retryCount === 0) {
|
if (this.retryCount === 0) {
|
||||||
this._assertStatus("RUNNING");
|
this._assertStatus("RUNNING");
|
||||||
@@ -25858,10 +25858,10 @@ var require_light = __commonJS({
|
|||||||
}
|
}
|
||||||
} catch (error1) {
|
} catch (error1) {
|
||||||
error2 = error1;
|
error2 = error1;
|
||||||
return this._onFailure(error2, eventInfo, clearGlobalState, run2, free);
|
return this._onFailure(error2, eventInfo, clearGlobalState, run, free);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doExpire(clearGlobalState, run2, free) {
|
doExpire(clearGlobalState, run, free) {
|
||||||
var error2, eventInfo;
|
var error2, eventInfo;
|
||||||
if (this._states.jobStatus(this.options.id === "RUNNING")) {
|
if (this._states.jobStatus(this.options.id === "RUNNING")) {
|
||||||
this._states.next(this.options.id);
|
this._states.next(this.options.id);
|
||||||
@@ -25869,9 +25869,9 @@ var require_light = __commonJS({
|
|||||||
this._assertStatus("EXECUTING");
|
this._assertStatus("EXECUTING");
|
||||||
eventInfo = { args: this.args, options: this.options, retryCount: this.retryCount };
|
eventInfo = { args: this.args, options: this.options, retryCount: this.retryCount };
|
||||||
error2 = new BottleneckError$1(`This job timed out after ${this.options.expiration} ms.`);
|
error2 = new BottleneckError$1(`This job timed out after ${this.options.expiration} ms.`);
|
||||||
return this._onFailure(error2, eventInfo, clearGlobalState, run2, free);
|
return this._onFailure(error2, eventInfo, clearGlobalState, run, free);
|
||||||
}
|
}
|
||||||
async _onFailure(error2, eventInfo, clearGlobalState, run2, free) {
|
async _onFailure(error2, eventInfo, clearGlobalState, run, free) {
|
||||||
var retry2, retryAfter;
|
var retry2, retryAfter;
|
||||||
if (clearGlobalState()) {
|
if (clearGlobalState()) {
|
||||||
retry2 = await this.Events.trigger("failed", error2, eventInfo);
|
retry2 = await this.Events.trigger("failed", error2, eventInfo);
|
||||||
@@ -25879,7 +25879,7 @@ var require_light = __commonJS({
|
|||||||
retryAfter = ~~retry2;
|
retryAfter = ~~retry2;
|
||||||
this.Events.trigger("retry", `Retrying ${this.options.id} after ${retryAfter} ms`, eventInfo);
|
this.Events.trigger("retry", `Retrying ${this.options.id} after ${retryAfter} ms`, eventInfo);
|
||||||
this.retryCount++;
|
this.retryCount++;
|
||||||
return run2(retryAfter);
|
return run(retryAfter);
|
||||||
} else {
|
} else {
|
||||||
this.doDone(eventInfo);
|
this.doDone(eventInfo);
|
||||||
await free(this.options, eventInfo);
|
await free(this.options, eventInfo);
|
||||||
@@ -26517,17 +26517,17 @@ var require_light = __commonJS({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_run(index, job, wait) {
|
_run(index, job, wait) {
|
||||||
var clearGlobalState, free, run2;
|
var clearGlobalState, free, run;
|
||||||
job.doRun();
|
job.doRun();
|
||||||
clearGlobalState = this._clearGlobalState.bind(this, index);
|
clearGlobalState = this._clearGlobalState.bind(this, index);
|
||||||
run2 = this._run.bind(this, index, job);
|
run = this._run.bind(this, index, job);
|
||||||
free = this._free.bind(this, index, job);
|
free = this._free.bind(this, index, job);
|
||||||
return this._scheduled[index] = {
|
return this._scheduled[index] = {
|
||||||
timeout: setTimeout(() => {
|
timeout: setTimeout(() => {
|
||||||
return job.doExecute(this._limiter, clearGlobalState, run2, free);
|
return job.doExecute(this._limiter, clearGlobalState, run, free);
|
||||||
}, wait),
|
}, wait),
|
||||||
expiration: job.options.expiration != null ? setTimeout(function() {
|
expiration: job.options.expiration != null ? setTimeout(function() {
|
||||||
return job.doExpire(clearGlobalState, run2, free);
|
return job.doExpire(clearGlobalState, run, free);
|
||||||
}, wait + job.options.expiration) : void 0,
|
}, wait + job.options.expiration) : void 0,
|
||||||
job
|
job
|
||||||
};
|
};
|
||||||
@@ -28843,6 +28843,178 @@ var require_semver2 = __commonJS({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// utils/log.ts
|
||||||
|
var core = __toESM(require_core(), 1);
|
||||||
|
var import_table = __toESM(require_src(), 1);
|
||||||
|
|
||||||
|
// utils/globals.ts
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
var isCloudflareSandbox = !!process.env.CLOUDFLARE_APPLICATION_ID && !!process.env.SANDBOX_VERSION;
|
||||||
|
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
|
var isInsideDocker = existsSync("/.dockerenv");
|
||||||
|
|
||||||
|
// utils/log.ts
|
||||||
|
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || core.isDebug();
|
||||||
|
function formatArgs(args2) {
|
||||||
|
return args2.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}
|
||||||
|
${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
}).join(" ");
|
||||||
|
}
|
||||||
|
function startGroup2(name) {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.startGroup(name);
|
||||||
|
} else {
|
||||||
|
console.group(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function endGroup2() {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.endGroup();
|
||||||
|
} else {
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function group(name, fn2) {
|
||||||
|
startGroup2(name);
|
||||||
|
fn2();
|
||||||
|
endGroup2();
|
||||||
|
}
|
||||||
|
function boxString(text, options) {
|
||||||
|
const { title, maxWidth = 80, indent: indent2 = "", padding = 1 } = options || {};
|
||||||
|
const lines = text.trim().split("\n");
|
||||||
|
const wrappedLines = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.length <= maxWidth - padding * 2) {
|
||||||
|
wrappedLines.push(line);
|
||||||
|
} else {
|
||||||
|
const words = line.split(" ");
|
||||||
|
let currentLine = "";
|
||||||
|
for (const word of words) {
|
||||||
|
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
||||||
|
if (testLine.length <= maxWidth - padding * 2) {
|
||||||
|
currentLine = testLine;
|
||||||
|
} else {
|
||||||
|
if (currentLine) {
|
||||||
|
wrappedLines.push(currentLine);
|
||||||
|
currentLine = "";
|
||||||
|
}
|
||||||
|
const maxLineLength2 = maxWidth - padding * 2;
|
||||||
|
let remainingWord = word;
|
||||||
|
while (remainingWord.length > maxLineLength2) {
|
||||||
|
wrappedLines.push(remainingWord.substring(0, maxLineLength2));
|
||||||
|
remainingWord = remainingWord.substring(maxLineLength2);
|
||||||
|
}
|
||||||
|
currentLine = remainingWord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentLine) {
|
||||||
|
wrappedLines.push(currentLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
|
||||||
|
const contentBoxWidth = maxLineLength + padding * 2;
|
||||||
|
const titleLineLength = title ? ` ${title} `.length : 0;
|
||||||
|
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
|
||||||
|
let result = "";
|
||||||
|
if (title) {
|
||||||
|
const titleLine = ` ${title} `;
|
||||||
|
const titlePadding = Math.max(0, boxWidth - titleLine.length);
|
||||||
|
result += `${indent2}\u250C${titleLine}${"\u2500".repeat(titlePadding)}\u2510
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
if (!title) {
|
||||||
|
result += `${indent2}\u250C${"\u2500".repeat(boxWidth)}\u2510
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
for (const line of wrappedLines) {
|
||||||
|
const paddedLine = line.padEnd(maxLineLength);
|
||||||
|
result += `${indent2}\u2502${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}\u2502
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
result += `${indent2}\u2514${"\u2500".repeat(boxWidth)}\u2518`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function box(text, options) {
|
||||||
|
const boxContent = boxString(text, options);
|
||||||
|
core.info(boxContent);
|
||||||
|
}
|
||||||
|
function printTable(rows, options) {
|
||||||
|
const { title } = options || {};
|
||||||
|
const tableData = rows.map(
|
||||||
|
(row) => row.map((cell) => {
|
||||||
|
if (typeof cell === "string") {
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
return cell.data;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const formatted = (0, import_table.table)(tableData);
|
||||||
|
if (title) {
|
||||||
|
core.info(`
|
||||||
|
${title}`);
|
||||||
|
}
|
||||||
|
core.info(`
|
||||||
|
${formatted}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
function separator(length = 50) {
|
||||||
|
const separatorText = "\u2500".repeat(length);
|
||||||
|
core.info(separatorText);
|
||||||
|
}
|
||||||
|
function ts() {
|
||||||
|
return isDebugEnabled() ? `[${(/* @__PURE__ */ new Date()).toISOString()}] ` : "";
|
||||||
|
}
|
||||||
|
var log = {
|
||||||
|
/** Print info message */
|
||||||
|
info: (...args2) => {
|
||||||
|
core.info(`${ts()}${formatArgs(args2)}`);
|
||||||
|
},
|
||||||
|
/** Print warning message */
|
||||||
|
warning: (...args2) => {
|
||||||
|
core.warning(`${ts()}${formatArgs(args2)}`);
|
||||||
|
},
|
||||||
|
/** Print error message */
|
||||||
|
error: (...args2) => {
|
||||||
|
core.error(`${ts()}${formatArgs(args2)}`);
|
||||||
|
},
|
||||||
|
/** Print success message */
|
||||||
|
success: (...args2) => {
|
||||||
|
core.info(`${ts()}\xBB ${formatArgs(args2)}`);
|
||||||
|
},
|
||||||
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
|
debug: (...args2) => {
|
||||||
|
if (isDebugEnabled()) {
|
||||||
|
core.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${formatArgs(args2)}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** Print a formatted box with text */
|
||||||
|
box,
|
||||||
|
/** Print a formatted table using the table package */
|
||||||
|
table: printTable,
|
||||||
|
/** Print a separator line */
|
||||||
|
separator,
|
||||||
|
/** Start a collapsed group (GitHub Actions) or regular group (local) */
|
||||||
|
startGroup: startGroup2,
|
||||||
|
/** End a collapsed group */
|
||||||
|
endGroup: endGroup2,
|
||||||
|
/** Run a callback within a collapsed group */
|
||||||
|
group,
|
||||||
|
/** Log tool call information to console with formatted output */
|
||||||
|
toolCall: ({ toolName, input }) => {
|
||||||
|
const inputFormatted = formatJsonValue(input);
|
||||||
|
const output = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
||||||
|
log.info(output.trimEnd());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function formatJsonValue(value2) {
|
||||||
|
const compact = JSON.stringify(value2);
|
||||||
|
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value2, null, 2) : compact;
|
||||||
|
}
|
||||||
|
|
||||||
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
||||||
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
||||||
var spliterate = (arr, predicate) => {
|
var spliterate = (arr, predicate) => {
|
||||||
@@ -37299,178 +37471,6 @@ var schema = ark.schema;
|
|||||||
var define2 = ark.define;
|
var define2 = ark.define;
|
||||||
var declare = ark.declare;
|
var declare = ark.declare;
|
||||||
|
|
||||||
// utils/log.ts
|
|
||||||
var core = __toESM(require_core(), 1);
|
|
||||||
var import_table = __toESM(require_src(), 1);
|
|
||||||
|
|
||||||
// utils/globals.ts
|
|
||||||
import { existsSync } from "node:fs";
|
|
||||||
var isCloudflareSandbox = !!process.env.CLOUDFLARE_APPLICATION_ID && !!process.env.SANDBOX_VERSION;
|
|
||||||
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
|
||||||
var isInsideDocker = existsSync("/.dockerenv");
|
|
||||||
|
|
||||||
// utils/log.ts
|
|
||||||
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || core.isDebug();
|
|
||||||
function formatArgs(args2) {
|
|
||||||
return args2.map((arg) => {
|
|
||||||
if (typeof arg === "string") return arg;
|
|
||||||
if (arg instanceof Error) return `${arg.message}
|
|
||||||
${arg.stack}`;
|
|
||||||
return JSON.stringify(arg);
|
|
||||||
}).join(" ");
|
|
||||||
}
|
|
||||||
function startGroup2(name) {
|
|
||||||
if (isGitHubActions) {
|
|
||||||
core.startGroup(name);
|
|
||||||
} else {
|
|
||||||
console.group(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function endGroup2() {
|
|
||||||
if (isGitHubActions) {
|
|
||||||
core.endGroup();
|
|
||||||
} else {
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function group(name, fn2) {
|
|
||||||
startGroup2(name);
|
|
||||||
fn2();
|
|
||||||
endGroup2();
|
|
||||||
}
|
|
||||||
function boxString(text, options) {
|
|
||||||
const { title, maxWidth = 80, indent: indent2 = "", padding = 1 } = options || {};
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
const wrappedLines = [];
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.length <= maxWidth - padding * 2) {
|
|
||||||
wrappedLines.push(line);
|
|
||||||
} else {
|
|
||||||
const words = line.split(" ");
|
|
||||||
let currentLine = "";
|
|
||||||
for (const word of words) {
|
|
||||||
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
||||||
if (testLine.length <= maxWidth - padding * 2) {
|
|
||||||
currentLine = testLine;
|
|
||||||
} else {
|
|
||||||
if (currentLine) {
|
|
||||||
wrappedLines.push(currentLine);
|
|
||||||
currentLine = "";
|
|
||||||
}
|
|
||||||
const maxLineLength2 = maxWidth - padding * 2;
|
|
||||||
let remainingWord = word;
|
|
||||||
while (remainingWord.length > maxLineLength2) {
|
|
||||||
wrappedLines.push(remainingWord.substring(0, maxLineLength2));
|
|
||||||
remainingWord = remainingWord.substring(maxLineLength2);
|
|
||||||
}
|
|
||||||
currentLine = remainingWord;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (currentLine) {
|
|
||||||
wrappedLines.push(currentLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
|
|
||||||
const contentBoxWidth = maxLineLength + padding * 2;
|
|
||||||
const titleLineLength = title ? ` ${title} `.length : 0;
|
|
||||||
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
|
|
||||||
let result = "";
|
|
||||||
if (title) {
|
|
||||||
const titleLine = ` ${title} `;
|
|
||||||
const titlePadding = Math.max(0, boxWidth - titleLine.length);
|
|
||||||
result += `${indent2}\u250C${titleLine}${"\u2500".repeat(titlePadding)}\u2510
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (!title) {
|
|
||||||
result += `${indent2}\u250C${"\u2500".repeat(boxWidth)}\u2510
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
for (const line of wrappedLines) {
|
|
||||||
const paddedLine = line.padEnd(maxLineLength);
|
|
||||||
result += `${indent2}\u2502${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}\u2502
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
result += `${indent2}\u2514${"\u2500".repeat(boxWidth)}\u2518`;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
function box(text, options) {
|
|
||||||
const boxContent = boxString(text, options);
|
|
||||||
core.info(boxContent);
|
|
||||||
}
|
|
||||||
function printTable(rows, options) {
|
|
||||||
const { title } = options || {};
|
|
||||||
const tableData = rows.map(
|
|
||||||
(row) => row.map((cell) => {
|
|
||||||
if (typeof cell === "string") {
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
return cell.data;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const formatted = (0, import_table.table)(tableData);
|
|
||||||
if (title) {
|
|
||||||
core.info(`
|
|
||||||
${title}`);
|
|
||||||
}
|
|
||||||
core.info(`
|
|
||||||
${formatted}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
function separator(length = 50) {
|
|
||||||
const separatorText = "\u2500".repeat(length);
|
|
||||||
core.info(separatorText);
|
|
||||||
}
|
|
||||||
function ts() {
|
|
||||||
return isDebugEnabled() ? `[${(/* @__PURE__ */ new Date()).toISOString()}] ` : "";
|
|
||||||
}
|
|
||||||
var log = {
|
|
||||||
/** Print info message */
|
|
||||||
info: (...args2) => {
|
|
||||||
core.info(`${ts()}${formatArgs(args2)}`);
|
|
||||||
},
|
|
||||||
/** Print warning message */
|
|
||||||
warning: (...args2) => {
|
|
||||||
core.warning(`${ts()}${formatArgs(args2)}`);
|
|
||||||
},
|
|
||||||
/** Print error message */
|
|
||||||
error: (...args2) => {
|
|
||||||
core.error(`${ts()}${formatArgs(args2)}`);
|
|
||||||
},
|
|
||||||
/** Print success message */
|
|
||||||
success: (...args2) => {
|
|
||||||
core.info(`${ts()}\xBB ${formatArgs(args2)}`);
|
|
||||||
},
|
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
|
||||||
debug: (...args2) => {
|
|
||||||
if (isDebugEnabled()) {
|
|
||||||
core.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${formatArgs(args2)}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/** Print a formatted box with text */
|
|
||||||
box,
|
|
||||||
/** Print a formatted table using the table package */
|
|
||||||
table: printTable,
|
|
||||||
/** Print a separator line */
|
|
||||||
separator,
|
|
||||||
/** Start a collapsed group (GitHub Actions) or regular group (local) */
|
|
||||||
startGroup: startGroup2,
|
|
||||||
/** End a collapsed group */
|
|
||||||
endGroup: endGroup2,
|
|
||||||
/** Run a callback within a collapsed group */
|
|
||||||
group,
|
|
||||||
/** Log tool call information to console with formatted output */
|
|
||||||
toolCall: ({ toolName, input }) => {
|
|
||||||
const inputFormatted = formatJsonValue(input);
|
|
||||||
const output = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
||||||
log.info(output.trimEnd());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function formatJsonValue(value2) {
|
|
||||||
const compact = JSON.stringify(value2);
|
|
||||||
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value2, null, 2) : compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
// utils/buildPullfrogFooter.ts
|
// utils/buildPullfrogFooter.ts
|
||||||
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
||||||
var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
||||||
@@ -41413,7 +41413,7 @@ function resolvePromptInput() {
|
|||||||
return jsonPayload;
|
return jsonPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
// post.ts
|
// utils/postCleanup.ts
|
||||||
var SHOULD_CHECK_REASON = true;
|
var SHOULD_CHECK_REASON = true;
|
||||||
async function validateStuckProgressComment(promptInput, octokit, owner, repo) {
|
async function validateStuckProgressComment(promptInput, octokit, owner, repo) {
|
||||||
if (!promptInput?.progressCommentId) {
|
if (!promptInput?.progressCommentId) {
|
||||||
@@ -41441,6 +41441,7 @@ async function validateStuckProgressComment(promptInput, octokit, owner, repo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getIsCancelled(params) {
|
async function getIsCancelled(params) {
|
||||||
|
if (!params.runIdStr) return false;
|
||||||
try {
|
try {
|
||||||
const { data: jobs } = await params.octokit.rest.actions.listJobsForWorkflowRun({
|
const { data: jobs } = await params.octokit.rest.actions.listJobsForWorkflowRun({
|
||||||
owner: params.repoContext.owner,
|
owner: params.repoContext.owner,
|
||||||
@@ -41471,7 +41472,6 @@ async function getIsCancelled(params) {
|
|||||||
async function runPostCleanup() {
|
async function runPostCleanup() {
|
||||||
log.info("\xBB [post] starting post cleanup");
|
log.info("\xBB [post] starting post cleanup");
|
||||||
const runIdStr = process.env.GITHUB_RUN_ID;
|
const runIdStr = process.env.GITHUB_RUN_ID;
|
||||||
if (!runIdStr) return log.info("\xBB [post] no GITHUB_RUN_ID available, skipping cleanup");
|
|
||||||
let promptInput = null;
|
let promptInput = null;
|
||||||
try {
|
try {
|
||||||
const resolved = resolvePromptInput();
|
const resolved = resolvePromptInput();
|
||||||
@@ -41511,16 +41511,15 @@ async function runPostCleanup() {
|
|||||||
log.error(`[post] failed to update comment: ${errorMessage}`);
|
log.error(`[post] failed to update comment: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function run() {
|
|
||||||
try {
|
// post.ts
|
||||||
await runPostCleanup();
|
|
||||||
} catch (error2) {
|
|
||||||
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
||||||
log.error(`[post] unexpected error: ${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug(`[post] script started at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
log.debug(`[post] script started at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
||||||
await run();
|
try {
|
||||||
|
await runPostCleanup();
|
||||||
|
} catch (error2) {
|
||||||
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
||||||
|
log.error(`[post] unexpected error: ${message}`);
|
||||||
|
}
|
||||||
/*! Bundled license information:
|
/*! Bundled license information:
|
||||||
|
|
||||||
undici/lib/fetch/body.js:
|
undici/lib/fetch/body.js:
|
||||||
|
|||||||
@@ -6,180 +6,14 @@
|
|||||||
* Searches for Pullfrog comment via GitHub API and updates if stuck on "Leaping into action".
|
* Searches for Pullfrog comment via GitHub API and updates if stuck on "Leaping into action".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LEAPING_INTO_ACTION_PREFIX } from "./mcp/comment.ts";
|
|
||||||
import { log } from "./utils/cli.ts";
|
import { log } from "./utils/cli.ts";
|
||||||
import { buildErrorCommentBody } from "./utils/exitHandler.ts";
|
import { runPostCleanup } from "./utils/postCleanup.ts";
|
||||||
import { createOctokit, parseRepoContext } from "./utils/github.ts";
|
|
||||||
import { type ResolvedPromptInput, resolvePromptInput } from "./utils/payload.ts";
|
|
||||||
import { getJobToken } from "./utils/token.ts";
|
|
||||||
|
|
||||||
type JsonPromptInput = Extract<ResolvedPromptInput, object>; // not string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls whether the script should check the reason for the workflow termination.
|
|
||||||
* It can be either canceled or failed.
|
|
||||||
* YAML file cannot supply it (not in ENV), so an extra request is required to check it.
|
|
||||||
* */
|
|
||||||
const SHOULD_CHECK_REASON = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that the progress comment is stuck on "Leaping into action"
|
|
||||||
* Fetches the comment by ID and checks if it starts with LEAPING_INTO_ACTION_PREFIX
|
|
||||||
* Returns the comment ID if stuck, null otherwise
|
|
||||||
*/
|
|
||||||
async function validateStuckProgressComment(
|
|
||||||
promptInput: JsonPromptInput | null,
|
|
||||||
octokit: ReturnType<typeof createOctokit>,
|
|
||||||
owner: string,
|
|
||||||
repo: string
|
|
||||||
): Promise<number | null> {
|
|
||||||
if (!promptInput?.progressCommentId) {
|
|
||||||
log.info("[post] no progressCommentId in prompt input, skipping cleanup");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commentId = parseInt(promptInput.progressCommentId, 10);
|
|
||||||
log.info(`[post] validating progressCommentId from prompt input: ${commentId}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data: comment } = await octokit.rest.issues.getComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// check if comment is stuck on "Leaping into action"
|
|
||||||
if (comment.body?.startsWith(LEAPING_INTO_ACTION_PREFIX)) {
|
|
||||||
log.info(`[post] comment ${commentId} is stuck on "Leaping into action"`);
|
|
||||||
return commentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`[post] comment ${commentId} is not stuck (already updated or different content)`);
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
log.error(`[post] failed to get comment ${commentId}: ${errorMessage}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect if the workflow or its steps is cancelled.
|
|
||||||
* While the job is still in_progress, the individual steps may have their conclusions set.
|
|
||||||
*/
|
|
||||||
async function getIsCancelled(params: {
|
|
||||||
repoContext: ReturnType<typeof parseRepoContext>;
|
|
||||||
octokit: ReturnType<typeof createOctokit>;
|
|
||||||
runIdStr: string;
|
|
||||||
}): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const { data: jobs } = await params.octokit.rest.actions.listJobsForWorkflowRun({
|
|
||||||
owner: params.repoContext.owner,
|
|
||||||
repo: params.repoContext.name,
|
|
||||||
run_id: Number.parseInt(params.runIdStr, 10),
|
|
||||||
});
|
|
||||||
|
|
||||||
// find current job by matching GITHUB_JOB env var
|
|
||||||
// Note: GITHUB_JOB is the job ID (yaml key), but job.name is the display name
|
|
||||||
// For matrix jobs, the name includes matrix values like "build (ubuntu-latest, node-18)"
|
|
||||||
// So we match jobs that START with the job ID
|
|
||||||
const currentJobName = process.env.GITHUB_JOB;
|
|
||||||
const currentJob = currentJobName
|
|
||||||
? jobs.jobs.find((j) => j.name === currentJobName || j.name.startsWith(`${currentJobName} (`))
|
|
||||||
: jobs.jobs[0]; // fallback to first job
|
|
||||||
|
|
||||||
if (!currentJob) {
|
|
||||||
log.warning("[post] could not find current job");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`[post] job status: ${currentJob.status}, conclusion: ${currentJob.conclusion}`);
|
|
||||||
if (currentJob.conclusion === "cancelled") return true; // whole job explicit cancellation
|
|
||||||
|
|
||||||
// but if it's still null, check steps for cancellation:
|
|
||||||
const cancelledStep = currentJob.steps?.find((step) => step.conclusion === "cancelled");
|
|
||||||
if (cancelledStep) {
|
|
||||||
log.info(`[post] found cancelled step: ${cancelledStep.name}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
log.info("[post] no cancellation found, assuming failure");
|
|
||||||
} catch (error) {
|
|
||||||
log.warning(
|
|
||||||
`[post] failed to get job status: ${error instanceof Error ? error.message : String(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false; // assuming failure
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runPostCleanup(): Promise<void> {
|
|
||||||
log.info("» [post] starting post cleanup");
|
|
||||||
|
|
||||||
const runIdStr = process.env.GITHUB_RUN_ID;
|
|
||||||
|
|
||||||
if (!runIdStr) return log.info("» [post] no GITHUB_RUN_ID available, skipping cleanup");
|
|
||||||
|
|
||||||
// resolve prompt input once and use it for both issue number and comment ID extraction
|
|
||||||
// only use the object form (JSON payload), not plain string prompts
|
|
||||||
let promptInput: JsonPromptInput | null = null;
|
|
||||||
try {
|
|
||||||
const resolved = resolvePromptInput();
|
|
||||||
if (typeof resolved !== "string") promptInput = resolved;
|
|
||||||
} catch (error) {
|
|
||||||
log.warning(
|
|
||||||
`[post] failed to resolve prompt input: ${error instanceof Error ? error.message : String(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get job token for API calls
|
|
||||||
const token = getJobToken();
|
|
||||||
const repoContext = parseRepoContext();
|
|
||||||
const octokit = createOctokit(token);
|
|
||||||
|
|
||||||
// validate that progressCommentId from prompt input is stuck on "Leaping into action"
|
|
||||||
const commentId = await validateStuckProgressComment(
|
|
||||||
promptInput,
|
|
||||||
octokit,
|
|
||||||
repoContext.owner,
|
|
||||||
repoContext.name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!commentId) return log.info("» [post] no stuck progress comment to update, skipping cleanup");
|
|
||||||
|
|
||||||
log.info(`» [post] validated stuck comment: ${commentId}, updating with error message`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = buildErrorCommentBody({
|
|
||||||
owner: repoContext.owner,
|
|
||||||
repo: repoContext.name,
|
|
||||||
runId: runIdStr,
|
|
||||||
isCancellation: SHOULD_CHECK_REASON
|
|
||||||
? await getIsCancelled({ octokit, repoContext, runIdStr })
|
|
||||||
: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
await octokit.rest.issues.updateComment({
|
|
||||||
owner: repoContext.owner,
|
|
||||||
repo: repoContext.name,
|
|
||||||
comment_id: commentId,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info("» [post] successfully updated progress comment");
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
log.error(`[post] failed to update comment: ${errorMessage}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await runPostCleanup();
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
log.error(`[post] unexpected error: ${message}`);
|
|
||||||
// don't fail the post script - best effort cleanup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug(`[post] script started at ${new Date().toISOString()}`);
|
log.debug(`[post] script started at ${new Date().toISOString()}`);
|
||||||
await run();
|
try {
|
||||||
|
await runPostCleanup();
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
log.error(`[post] unexpected error: ${message}`);
|
||||||
|
// don't fail the post script - best effort cleanup
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { LEAPING_INTO_ACTION_PREFIX } from "../mcp/comment.ts";
|
||||||
|
import { log } from "./cli.ts";
|
||||||
|
import { buildErrorCommentBody } from "./exitHandler.ts";
|
||||||
|
import { createOctokit, parseRepoContext } from "./github.ts";
|
||||||
|
import { type ResolvedPromptInput, resolvePromptInput } from "./payload.ts";
|
||||||
|
import { getJobToken } from "./token.ts";
|
||||||
|
|
||||||
|
type JsonPromptInput = Extract<ResolvedPromptInput, object>; // not string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls whether the script should check the reason for the workflow termination.
|
||||||
|
* It can be either canceled or failed.
|
||||||
|
* YAML file cannot supply it (not in ENV), so an extra request is required to check it.
|
||||||
|
* */
|
||||||
|
const SHOULD_CHECK_REASON = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the progress comment is stuck on "Leaping into action"
|
||||||
|
* Fetches the comment by ID and checks if it starts with LEAPING_INTO_ACTION_PREFIX
|
||||||
|
* Returns the comment ID if stuck, null otherwise
|
||||||
|
*/
|
||||||
|
async function validateStuckProgressComment(
|
||||||
|
promptInput: JsonPromptInput | null,
|
||||||
|
octokit: ReturnType<typeof createOctokit>,
|
||||||
|
owner: string,
|
||||||
|
repo: string
|
||||||
|
): Promise<number | null> {
|
||||||
|
if (!promptInput?.progressCommentId) {
|
||||||
|
log.info("[post] no progressCommentId in prompt input, skipping cleanup");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentId = parseInt(promptInput.progressCommentId, 10);
|
||||||
|
log.info(`[post] validating progressCommentId from prompt input: ${commentId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: comment } = await octokit.rest.issues.getComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: commentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if comment is stuck on "Leaping into action"
|
||||||
|
if (comment.body?.startsWith(LEAPING_INTO_ACTION_PREFIX)) {
|
||||||
|
log.info(`[post] comment ${commentId} is stuck on "Leaping into action"`);
|
||||||
|
return commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`[post] comment ${commentId} is not stuck (already updated or different content)`);
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
log.error(`[post] failed to get comment ${commentId}: ${errorMessage}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the workflow or its steps is cancelled.
|
||||||
|
* While the job is still in_progress, the individual steps may have their conclusions set.
|
||||||
|
*/
|
||||||
|
async function getIsCancelled(params: {
|
||||||
|
repoContext: ReturnType<typeof parseRepoContext>;
|
||||||
|
octokit: ReturnType<typeof createOctokit>;
|
||||||
|
runIdStr: string | undefined;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
if (!params.runIdStr) return false; // can't check without a run ID — assume failure
|
||||||
|
try {
|
||||||
|
const { data: jobs } = await params.octokit.rest.actions.listJobsForWorkflowRun({
|
||||||
|
owner: params.repoContext.owner,
|
||||||
|
repo: params.repoContext.name,
|
||||||
|
run_id: Number.parseInt(params.runIdStr, 10),
|
||||||
|
});
|
||||||
|
|
||||||
|
// find current job by matching GITHUB_JOB env var
|
||||||
|
// Note: GITHUB_JOB is the job ID (yaml key), but job.name is the display name
|
||||||
|
// For matrix jobs, the name includes matrix values like "build (ubuntu-latest, node-18)"
|
||||||
|
// So we match jobs that START with the job ID
|
||||||
|
const currentJobName = process.env.GITHUB_JOB;
|
||||||
|
const currentJob = currentJobName
|
||||||
|
? jobs.jobs.find((j) => j.name === currentJobName || j.name.startsWith(`${currentJobName} (`))
|
||||||
|
: jobs.jobs[0]; // fallback to first job
|
||||||
|
|
||||||
|
if (!currentJob) {
|
||||||
|
log.warning("[post] could not find current job");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`[post] job status: ${currentJob.status}, conclusion: ${currentJob.conclusion}`);
|
||||||
|
if (currentJob.conclusion === "cancelled") return true; // whole job explicit cancellation
|
||||||
|
|
||||||
|
// but if it's still null, check steps for cancellation:
|
||||||
|
const cancelledStep = currentJob.steps?.find((step) => step.conclusion === "cancelled");
|
||||||
|
if (cancelledStep) {
|
||||||
|
log.info(`[post] found cancelled step: ${cancelledStep.name}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log.info("[post] no cancellation found, assuming failure");
|
||||||
|
} catch (error) {
|
||||||
|
log.warning(
|
||||||
|
`[post] failed to get job status: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false; // assuming failure
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runPostCleanup(): Promise<void> {
|
||||||
|
log.info("» [post] starting post cleanup");
|
||||||
|
|
||||||
|
const runIdStr = process.env.GITHUB_RUN_ID;
|
||||||
|
|
||||||
|
// resolve prompt input once and use it for both issue number and comment ID extraction
|
||||||
|
// only use the object form (JSON payload), not plain string prompts
|
||||||
|
let promptInput: JsonPromptInput | null = null;
|
||||||
|
try {
|
||||||
|
const resolved = resolvePromptInput();
|
||||||
|
if (typeof resolved !== "string") promptInput = resolved;
|
||||||
|
} catch (error) {
|
||||||
|
log.warning(
|
||||||
|
`[post] failed to resolve prompt input: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get job token for API calls
|
||||||
|
const token = getJobToken();
|
||||||
|
const repoContext = parseRepoContext();
|
||||||
|
const octokit = createOctokit(token);
|
||||||
|
|
||||||
|
// validate that progressCommentId from prompt input is stuck on "Leaping into action"
|
||||||
|
const commentId = await validateStuckProgressComment(
|
||||||
|
promptInput,
|
||||||
|
octokit,
|
||||||
|
repoContext.owner,
|
||||||
|
repoContext.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!commentId) return log.info("» [post] no stuck progress comment to update, skipping cleanup");
|
||||||
|
|
||||||
|
log.info(`» [post] validated stuck comment: ${commentId}, updating with error message`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = buildErrorCommentBody({
|
||||||
|
owner: repoContext.owner,
|
||||||
|
repo: repoContext.name,
|
||||||
|
runId: runIdStr,
|
||||||
|
isCancellation: SHOULD_CHECK_REASON
|
||||||
|
? await getIsCancelled({ octokit, repoContext, runIdStr })
|
||||||
|
: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await octokit.rest.issues.updateComment({
|
||||||
|
owner: repoContext.owner,
|
||||||
|
repo: repoContext.name,
|
||||||
|
comment_id: commentId,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("» [post] successfully updated progress comment");
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
log.error(`[post] failed to update comment: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user