@cloud-cli/on 1.8.1 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/events.d.ts +12 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/on.js +378 -183
- package/dist/parser/yaml-loader.d.ts.map +1 -1
- package/dist/plugins/github-status.plugin.d.ts +13 -3
- package/dist/plugins/github-status.plugin.d.ts.map +1 -1
- package/dist/plugins/manager.d.ts +3 -2
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/queue.d.ts +3 -2
- package/dist/queue.d.ts.map +1 -1
- package/dist/run-view.d.ts +16 -0
- package/dist/run-view.d.ts.map +1 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/types.d.ts +13 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +3 -2
- package/dist/worker.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/reporters/html.reporter.d.ts +0 -11
- package/dist/reporters/html.reporter.d.ts.map +0 -1
- package/dist/reporters/json-file.reporter.d.ts +0 -10
- package/dist/reporters/json-file.reporter.d.ts.map +0 -1
- package/dist/reporters/slack.reporter.d.ts +0 -16
- package/dist/reporters/slack.reporter.d.ts.map +0 -1
package/dist/on.js
CHANGED
|
@@ -63,20 +63,22 @@ async function le(e) {
|
|
|
63
63
|
return t(o.workflows) ? (ce(o.database), o) : (console.warn(`⚠️ Warning: Workflows directory '${o.workflows}' not found.`), null);
|
|
64
64
|
}
|
|
65
65
|
function ue(e, t) {
|
|
66
|
-
let n = process.env;
|
|
66
|
+
let n = process.env, r = Number(e.port || t.port || n.PORT || 11235), i = e.tags ?? (n.RUNNER_TAGS ? n.RUNNER_TAGS.split(",") : []);
|
|
67
67
|
return {
|
|
68
|
-
port:
|
|
68
|
+
port: r,
|
|
69
69
|
adminToken: e.adminToken ?? n.RUNNER_ADMIN_SECRET ?? "",
|
|
70
70
|
database: e.database ?? t.database ?? n.RUNNER_DATABASE_URL ?? "",
|
|
71
71
|
workflows: e.workflows ?? t.workflows ?? n.RUNNER_WORKFLOWS ?? "on/",
|
|
72
72
|
workers: Number(e.workers ?? t.workers ?? n.RUNNER_WORKERS ?? 5),
|
|
73
|
+
serverUrl: e.serverUrl ?? n.RUNNER_SERVER_URL ?? `http://127.0.0.1:${r}`,
|
|
74
|
+
tags: i.map((e) => e.trim()).filter(Boolean),
|
|
73
75
|
storagePath: e.storagePath ?? n.RUNNER_TMP ?? "/tmp/workspaces",
|
|
74
76
|
env: e.env ?? {},
|
|
75
|
-
|
|
77
|
+
plugins: e.plugins ?? []
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
function de() {
|
|
79
|
-
console.log("\n🏃 Runner CLI 🏃\n\nUsage:\n npx -y @cloud-cli/on <command> [options]\n pnpm dlx -y @cloud-cli/on <command> [options]\n\nCommands:\n start Runs both Webhook Ingress Server and Workers (Default)\n start-server Runs Webhook Ingress Server only (API Gateway mode)\n start-workers Runs
|
|
81
|
+
console.log("\n🏃 Runner CLI 🏃\n\nUsage:\n npx -y @cloud-cli/on <command> [options]\n pnpm dlx -y @cloud-cli/on <command> [options]\n\nCommands:\n start Runs both Webhook Ingress Server and Workers (Default)\n start-server Runs Webhook Ingress Server only (API Gateway mode)\n start-workers Runs event-driven workers (Scalable Worker mode)\n validate Parses and validates workflow YAML files without running\n\nOptions:\n -c, --config Path to runner.config.mjs (default: ./runner.config.mjs, env: RUNNER_CONFIG_PATH)\n -d, --database SQLite Database URL (env: RUNNER_DATABASE_URL)\n -w, --workflows Path to where your workflows are defined (default: on/, env: RUNNER_WORKFLOWS_PATH)\n -p, --port Port for Webhook Ingress Server (default: 11235, env: PORT)\n -k, --workers Maximum concurrent jobs (default: 5, env: RUNNER_WORKERS)\n Worker tags (comma-separated env: RUNNER_TAGS)\n Webhook server URL (env: RUNNER_SERVER_URL)\n -h, --help Show this help message\n ");
|
|
80
82
|
}
|
|
81
83
|
async function fe() {
|
|
82
84
|
let { values: e, positionals: t } = l({
|
|
@@ -4462,8 +4464,11 @@ var pi = class {
|
|
|
4462
4464
|
JSON.stringify(t)
|
|
4463
4465
|
]);
|
|
4464
4466
|
}
|
|
4465
|
-
async claimNextJob() {
|
|
4466
|
-
return await S.get("\n UPDATE jobs\n SET\n status = 'running',\n worker_id = ?,\n started_at = CURRENT_TIMESTAMP\n WHERE id = (\n SELECT id FROM jobs\n WHERE status = 'pending'\n ORDER BY created_at ASC\n LIMIT 1\n )\n RETURNING *;\n ", [this.workerId]) || null;
|
|
4467
|
+
async claimNextJob(e = []) {
|
|
4468
|
+
return await S.get("\n UPDATE jobs\n SET\n status = 'running',\n worker_id = ?,\n started_at = CURRENT_TIMESTAMP\n WHERE id = (\n SELECT id FROM jobs\n WHERE status = 'pending'\n AND NOT EXISTS (\n SELECT 1\n FROM json_each(COALESCE(json_extract(jobs.payload, '$.tags'), '[]')) AS required_tag\n WHERE required_tag.value NOT IN (SELECT value FROM json_each(?))\n )\n ORDER BY created_at ASC\n LIMIT 1\n )\n RETURNING *;\n ", [this.workerId, JSON.stringify(e)]) || null;
|
|
4469
|
+
}
|
|
4470
|
+
async releaseJob(e) {
|
|
4471
|
+
await S.run("UPDATE jobs SET status = 'pending', worker_id = NULL, started_at = NULL WHERE id = ? AND status = 'running';", [e]);
|
|
4467
4472
|
}
|
|
4468
4473
|
async finishJob(e, t) {
|
|
4469
4474
|
await S.run("UPDATE jobs SET status = ?, finished_at = CURRENT_TIMESTAMP WHERE id = ?;", [t, e]);
|
|
@@ -7949,7 +7954,7 @@ var Mo = class {
|
|
|
7949
7954
|
owner: c,
|
|
7950
7955
|
repo: l,
|
|
7951
7956
|
clone_url: n.repository?.clone_url,
|
|
7952
|
-
commit_sha: n.after || n.head_commit?.id,
|
|
7957
|
+
commit_sha: n.after || n.head_commit?.id || n.pull_request?.head?.sha,
|
|
7953
7958
|
author: n.pusher?.name || n.sender?.login,
|
|
7954
7959
|
action: n.action,
|
|
7955
7960
|
raw: n,
|
|
@@ -7977,29 +7982,7 @@ var Mo = class {
|
|
|
7977
7982
|
inputs: e
|
|
7978
7983
|
};
|
|
7979
7984
|
}
|
|
7980
|
-
}
|
|
7981
|
-
//#endregion
|
|
7982
|
-
//#region src/html-state.ts
|
|
7983
|
-
function No(e) {
|
|
7984
|
-
return JSON.stringify(e).replace(/[<>&\u2028\u2029]/g, (e) => `\\u${e.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
7985
|
-
}
|
|
7986
|
-
//#endregion
|
|
7987
|
-
//#region src/reporters/html.reporter.html?raw
|
|
7988
|
-
var Po = "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Workflow Run</title>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"@li3/\": \"https://cdn.li3.dev/@li3/\",\n \"ansi_up\": \"https://esm.sh/ansi_up@6.0.6\"\n }\n }\n <\/script>\n</head>\n<body class=\"bg-gray-950 text-gray-100 min-h-screen p-4 md:p-6 font-sans\">\n <template app>\n <div class=\"max-w-5xl mx-auto space-y-4\">\n <header class=\"flex flex-col md:flex-row md:items-center justify-between border-b border-gray-800 pb-6 gap-4\">\n <div class=\"min-w-0\">\n <a href=\"/runs\" class=\"text-xs text-indigo-400 hover:underline mb-1 inline-block\">Back to Dashboard</a>\n <h1 class=\"text-2xl font-bold text-white flex flex-wrap items-center gap-3\">\n {{ report.workflowName }}\n <span class=\"text-sm font-mono text-gray-500\">#{{ report.jobId }}</span>\n <template if=\"report.parentId\">\n <a attr-href=\"'/runs/' + report.parentId\" class=\"text-sm font-mono text-indigo-400 hover:underline\">\n from #{{ report.parentId }}\n </a>\n </template>\n </h1>\n <p class=\"text-xs text-gray-400 mt-1\">Started {{ report.startedAt }} · {{ timing }}</p>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <span\n class=\"px-4 py-1.5 rounded-full text-sm font-semibold border\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400.border-emerald-500/20': report.status === 'success',\n 'bg-rose-500/10.text-rose-400.border-rose-500/20': report.status === 'failed',\n 'bg-indigo-500/10.text-indigo-400.border-indigo-500/20': report.status === 'running',\n 'bg-amber-500/10.text-amber-400.border-amber-500/20': !['success', 'failed', 'running'].includes(report.status)\n }\"\n >\n {{ upper(report.status) }}\n </span>\n <button\n class=\"text-sm text-white px-4 py-1.5 border border-gray-600 hover:border-gray-400 rounded-full\"\n on-click=\"restartJob()\"\n >\n Restart\n </button>\n </div>\n </header>\n\n <section class=\"rounded overflow-hidden border-b border-gray-800\">\n <h2 class=\"sr-only\">Execution Steps</h2>\n <template for=\"[step, index] of report.steps\">\n <details class=\"border border-b-0 border-gray-800 bg-gray-900/50 text-sm\">\n <summary class=\"flex flex-col md:flex-row md:items-center justify-between gap-2 p-3 bg-gray-800/40 border-b border-gray-800 cursor-pointer\">\n <div class=\"flex flex-wrap items-center gap-3 min-w-0\">\n <span class=\"font-mono text-gray-500\">\n #{{ index + 1 }}<template if=\"step.exitCode !== undefined && step.exitCode !== 0\"> ({{ step.exitCode }})</template>\n </span>\n <h3 class=\"font-semibold text-gray-200 break-words\">{{ step.name }}</h3>\n <span\n class=\"px-2.5 py-0.5 rounded-full font-medium\"\n bind-class=\"{\n 'text-emerald-400.bg-emerald-500/10': step.status === 'success',\n 'text-rose-400.bg-rose-500/10': step.status === 'failed',\n 'text-indigo-400.bg-indigo-500/10': step.status === 'running',\n 'text-gray-400.bg-gray-500/10': !['success', 'failed', 'running'].includes(step.status)\n }\"\n >\n {{ upper(step.status) }}\n </span>\n </div>\n <div class=\"font-mono text-gray-400 shrink-0\">{{ step.durationMs }}ms</div>\n </summary>\n <div class=\"p-4 bg-gray-950 font-mono text-gray-300 leading-relaxed overflow-auto w-full\">\n <pre class=\"whitespace-pre-wrap\" bind-innerhtml=\"stepLog(step)\"></pre>\n </div>\n </details>\n </template>\n </section>\n\n <details class=\"bg-gray-900 border border-gray-800 rounded-xl p-4 overflow-auto max-h-[400px]\">\n <summary class=\"cursor-pointer\">\n <h2 class=\"inline text-xs font-semibold text-gray-400 uppercase tracking-wider\">Trigger Inputs</h2>\n </summary>\n <pre class=\"font-mono text-xs text-gray-200 bg-gray-950 p-3 mt-3 rounded-lg overflow-x-auto\">{{ inputsJson }}</pre>\n </details>\n </div>\n\n <script setup>\n import { computed, onInit, ref } from '@li3/web';\n import { AnsiUp } from 'ansi_up';\n\n export default function setup() {\n const report = ref({});\n const ansiUp = new AnsiUp();\n ansiUp.use_classes = false;\n\n const active = computed(() => ['pending', 'running'].includes(report.value.status));\n const inputsJson = computed(() => JSON.stringify(report.value.inputs || {}, null, 2));\n const timing = computed(() => {\n if (report.value.status === 'pending') return 'Waiting for a worker';\n if (report.value.status === 'running') return `Running for ${report.value.durationMs}ms`;\n return `Finished in ${report.value.durationMs}ms`;\n });\n\n const upper = (value) => String(value || '').toUpperCase();\n const stepLog = (step) => {\n if (step.status === 'skipped') return '';\n if (step.status === 'running') return '<span class=\"text-indigo-400\">Step is running. Logs will appear after it finishes.</span>';\n if (step.status === 'pending') return '<span class=\"text-gray-500\">Waiting to run.</span>';\n if (step.logContent) return ansiUp.ansi_to_html(step.logContent);\n return '<span class=\"text-gray-500\">(No terminal log output recorded for this step)</span>';\n };\n const restartJob = async () => {\n const response = await fetch(`/restart/${report.value.jobId}`, { method: 'POST' });\n if (response.ok) {\n const { id } = await response.json();\n location.href = `/runs/${id}`;\n }\n };\n\n onInit(() => {\n document.title = `Run #${report.value.jobId} - ${report.value.workflowName}`;\n if (active.value) setTimeout(() => location.reload(), 3000);\n });\n\n return { report, inputsJson, timing, upper, stepLog, restartJob };\n }\n <\/script>\n <script state>__REPORT_STATE__<\/script>\n </template>\n\n <script type=\"module\">import '@li3/web';<\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n</body>\n</html>\n", Fo = "__REPORT_STATE__", Io = class {
|
|
7989
|
-
name = "html";
|
|
7990
|
-
outputDir;
|
|
7991
|
-
constructor(e) {
|
|
7992
|
-
this.outputDir = e.outputDir;
|
|
7993
|
-
}
|
|
7994
|
-
async report(t) {
|
|
7995
|
-
e.mkdirSync(this.outputDir, { recursive: !0 });
|
|
7996
|
-
let n = this.generateHtml(t), r = a.join(this.outputDir, `run-${t.jobId}.html`);
|
|
7997
|
-
e.writeFileSync(r, n, "utf-8"), console.log(`📊 HTML Execution Report generated: ${r}`);
|
|
7998
|
-
}
|
|
7999
|
-
generateHtml(e) {
|
|
8000
|
-
return Po.replace(Fo, () => No({ report: e }));
|
|
8001
|
-
}
|
|
8002
|
-
}, Lo = class e {
|
|
7985
|
+
}, No = class e {
|
|
8003
7986
|
static async from(t) {
|
|
8004
7987
|
let n = o(t) ? c("/", t) : s(process.cwd(), c("/", t)), r = (await _(n, { withFileTypes: !0 })).filter((e) => e.isFile() && (e.name.endsWith(".yml") || e.name.endsWith(".yaml"))).map((e) => s(n, e.name)), i = [], a = new di(t);
|
|
8005
7988
|
for (let t of r) i.push(...await e.loadFile(t, a));
|
|
@@ -8024,7 +8007,8 @@ var Po = "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta ch
|
|
|
8024
8007
|
},
|
|
8025
8008
|
concurrency: t.concurrency,
|
|
8026
8009
|
steps: t.steps,
|
|
8027
|
-
env: t.env
|
|
8010
|
+
env: t.env,
|
|
8011
|
+
tags: Array.isArray(t.tags) ? [...new Set(t.tags.filter((e) => typeof e == "string").map((e) => e.trim()).filter(Boolean))] : void 0
|
|
8028
8012
|
});
|
|
8029
8013
|
}
|
|
8030
8014
|
} catch (t) {
|
|
@@ -8032,10 +8016,15 @@ var Po = "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta ch
|
|
|
8032
8016
|
}
|
|
8033
8017
|
return n;
|
|
8034
8018
|
}
|
|
8035
|
-
},
|
|
8019
|
+
}, Po = "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Workflow Engine Dashboard</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n</head>\n<body class=\"bg-gray-950 text-gray-100 min-h-screen p-4 md:p-6 font-sans\">\n <template app>\n <main class=\"max-w-6xl mx-auto space-y-6\">\n <header class=\"flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-gray-800 pb-4\">\n <div>\n <h1 class=\"text-2xl font-bold text-white\">Runner Engine Status</h1>\n <p class=\"text-xs text-gray-400\">Live Job Queue & Execution Traces</p>\n </div>\n <div class=\"flex items-center gap-3 text-xs font-mono\">\n <span class=\"text-gray-500\">Updated {{ lastUpdated }}</span>\n <span\n class=\"px-3 py-1 rounded-full border\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400.border-emerald-500/20': !refreshError,\n 'bg-rose-500/10.text-rose-400.border-rose-500/20': refreshError\n }\"\n >\n {{ refreshError ? 'Refresh failed' : 'System operational' }}\n </span>\n </div>\n </header>\n\n <section class=\"bg-gray-900 border border-gray-800 rounded-xl overflow-hidden hidden md:block\">\n <table class=\"w-full text-left text-sm\">\n <thead class=\"bg-gray-800/50 text-gray-400 text-xs uppercase font-mono border-b border-gray-800\">\n <tr>\n <th class=\"py-3 px-4\">Job ID</th>\n <th class=\"py-3 px-4\">Workflow</th>\n <th class=\"py-3 px-4\">Status</th>\n <th class=\"py-3 px-4 hidden xl:table-cell\">Worker</th>\n <th class=\"py-3 px-4 hidden lg:table-cell\">Created At</th>\n <th class=\"py-3 px-4 text-right\">Action</th>\n </tr>\n </thead>\n <tbody>\n <template for=\"job of jobs\">\n <tr class=\"border-b border-gray-800 hover:bg-gray-800/40 transition\">\n <td class=\"py-3 px-4 font-mono text-indigo-400\">\n <a attr-href=\"'/runs/' + job.id\" class=\"hover:underline\">#{{ job.id }}</a>\n </td>\n <td class=\"py-3 px-4 font-medium text-white\">{{ job.workflowId }}</td>\n <td class=\"py-3 px-4\">\n <span\n class=\"px-2.5 py-0.5 rounded-full text-xs font-semibold\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400': job.status === 'success',\n 'bg-rose-500/10.text-rose-400': job.status === 'failed',\n 'bg-indigo-500/10.text-indigo-400.animate-pulse': job.status === 'running',\n 'bg-gray-500/10.text-gray-400': !['success', 'failed', 'running'].includes(job.status)\n }\"\n >\n {{ upper(job.status) }}\n </span>\n </td>\n <td class=\"py-3 px-4 hidden xl:table-cell text-xs font-mono text-gray-400\">{{ job.workerId || '-' }}</td>\n <td class=\"py-3 px-4 hidden lg:table-cell text-xs text-gray-400\">{{ job.createdAt }}</td>\n <td class=\"py-3 px-4 text-right whitespace-nowrap\">\n <a attr-href=\"'/runs/' + job.id\" class=\"text-xs bg-gray-800 hover:bg-gray-700 text-gray-200 px-3 py-1 rounded border border-gray-700\">View Trace</a>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n <template if=\"!jobs.length\">\n <div class=\"p-8 text-center text-gray-500\">No jobs recorded yet.</div>\n </template>\n </section>\n\n <section class=\"md:hidden bg-gray-900 border border-gray-800 rounded-xl divide-y divide-gray-800 overflow-hidden\">\n <template for=\"job of jobs\">\n <article class=\"p-4 bg-gray-900/60\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0\"\n bind-class=\"{\n 'bg-emerald-400': job.status === 'success',\n 'bg-rose-400': job.status === 'failed',\n 'bg-indigo-400.animate-pulse': job.status === 'running',\n 'bg-gray-500': !['success', 'failed', 'running'].includes(job.status)\n }\"\n ></span>\n <h2 class=\"font-medium text-white truncate\">{{ job.workflowId }}</h2>\n </div>\n <p class=\"mt-1 pl-[18px] text-xs font-mono text-gray-400\">#{{ job.id }} · {{ upper(job.status) }}</p>\n </div>\n <a attr-href=\"'/runs/' + job.id\" class=\"shrink-0 text-xs bg-gray-800 hover:bg-gray-700 text-gray-300 px-2.5 py-1 rounded border border-gray-700\">Trace</a>\n </div>\n </article>\n </template>\n <template if=\"!jobs.length\">\n <div class=\"p-8 text-center text-gray-500\">No jobs recorded yet.</div>\n </template>\n </section>\n\n <template if=\"hasMore\">\n <div class=\"flex justify-center\">\n <button\n class=\"px-4 py-2 text-sm text-gray-200 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg\"\n on-click=\"loadMore()\"\n >\n {{ loadingMore ? 'Loading...' : 'Load more' }}\n </button>\n </div>\n </template>\n </main>\n\n <script setup>\n import { onDestroy, onInit, ref } from '@li3/web';\n\n export default function setup() {\n const jobs = ref([]);\n const lastUpdated = ref('now');\n const refreshError = ref(false);\n const hasMore = ref(false);\n const loadingMore = ref(false);\n let refreshTimer;\n let eventSource;\n let refreshing = false;\n let refreshPending = false;\n\n const upper = (value) => String(value || '').toUpperCase();\n const terminalStatuses = new Set(['success', 'failed', 'cancelled']);\n const refreshJobs = async () => {\n if (refreshing) {\n refreshPending = true;\n return;\n }\n refreshing = true;\n\n try {\n const activeIds = jobs.value\n .filter((job) => !terminalStatuses.has(job.status))\n .map((job) => Number(job.id));\n const knownIds = jobs.value.map((job) => Number(job.id));\n const afterId = activeIds.length\n ? Math.max(0, Math.min(...activeIds) - 1)\n : knownIds.length\n ? Math.max(...knownIds)\n : null;\n const url = afterId === null ? '/api/jobs?limit=500' : `/api/jobs?afterId=${afterId}&limit=500`;\n const response = await fetch(url, { headers: { accept: 'application/json' } });\n if (!response.ok) throw new Error(`Dashboard refresh failed: ${response.status}`);\n\n const data = await response.json();\n const merged = new Map(jobs.value.map((job) => [job.id, job]));\n for (const job of data.jobs) merged.set(job.id, job);\n jobs.value = Array.from(merged.values()).sort((a, b) => Number(b.id) - Number(a.id));\n lastUpdated.value = new Date().toLocaleTimeString();\n refreshError.value = false;\n } catch (error) {\n console.error(error);\n refreshError.value = true;\n } finally {\n refreshing = false;\n if (refreshPending) {\n refreshPending = false;\n void refreshJobs();\n }\n }\n };\n\n const loadMore = async () => {\n if (loadingMore.value || !hasMore.value || !jobs.value.length) return;\n loadingMore.value = true;\n\n try {\n const beforeId = Math.min(...jobs.value.map((job) => Number(job.id)));\n const response = await fetch(`/api/jobs?beforeId=${beforeId}`, {\n headers: { accept: 'application/json' },\n });\n if (!response.ok) throw new Error(`Loading older jobs failed: ${response.status}`);\n\n const data = await response.json();\n const merged = new Map(jobs.value.map((job) => [job.id, job]));\n for (const job of data.jobs) merged.set(job.id, job);\n jobs.value = Array.from(merged.values()).sort((a, b) => Number(b.id) - Number(a.id));\n hasMore.value = data.hasMore;\n } catch (error) {\n console.error(error);\n refreshError.value = true;\n } finally {\n loadingMore.value = false;\n }\n };\n\n onInit(() => {\n eventSource = new EventSource('/api/events');\n eventSource.addEventListener('jobs.available', refreshJobs);\n eventSource.addEventListener('jobs.changed', refreshJobs);\n eventSource.onerror = () => {\n refreshError.value = true;\n };\n refreshTimer = setInterval(refreshJobs, 60000);\n });\n onDestroy(() => {\n eventSource?.close();\n clearInterval(refreshTimer);\n });\n\n return { jobs, lastUpdated, refreshError, hasMore, loadingMore, loadMore, upper };\n }\n <\/script>\n <script state>__DASHBOARD_STATE__<\/script>\n </template>\n\n <script type=\"module\">import '@li3/web';<\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n</body>\n</html>\n";
|
|
8020
|
+
//#endregion
|
|
8021
|
+
//#region src/html-state.ts
|
|
8022
|
+
function Fo(e) {
|
|
8023
|
+
return JSON.stringify(e).replace(/[<>&\u2028\u2029]/g, (e) => `\\u${e.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
8024
|
+
}
|
|
8036
8025
|
//#endregion
|
|
8037
8026
|
//#region src/dashboard.ts
|
|
8038
|
-
function
|
|
8027
|
+
function Io(e) {
|
|
8039
8028
|
return e.map((e) => ({
|
|
8040
8029
|
id: e.id,
|
|
8041
8030
|
workflowId: e.workflow_id,
|
|
@@ -8045,27 +8034,145 @@ function zo(e) {
|
|
|
8045
8034
|
updatedAt: e.updated_at
|
|
8046
8035
|
}));
|
|
8047
8036
|
}
|
|
8048
|
-
function
|
|
8049
|
-
return
|
|
8037
|
+
function Lo(e, t = !1) {
|
|
8038
|
+
return Po.replace("__DASHBOARD_STATE__", () => Fo({
|
|
8050
8039
|
jobs: e,
|
|
8051
8040
|
hasMore: t
|
|
8052
8041
|
}));
|
|
8053
8042
|
}
|
|
8054
8043
|
//#endregion
|
|
8044
|
+
//#region src/events.ts
|
|
8045
|
+
var Ro = class {
|
|
8046
|
+
clients = /* @__PURE__ */ new Map();
|
|
8047
|
+
nextId = 1;
|
|
8048
|
+
maxClients = 1e3;
|
|
8049
|
+
subscribe(e, t) {
|
|
8050
|
+
if (this.clients.size >= this.maxClients) {
|
|
8051
|
+
t.writeHead(503, {
|
|
8052
|
+
"Content-Type": "application/json",
|
|
8053
|
+
"Retry-After": "5"
|
|
8054
|
+
}), t.end(JSON.stringify({ error: "Too many event stream clients" }));
|
|
8055
|
+
return;
|
|
8056
|
+
}
|
|
8057
|
+
t.writeHead(200, {
|
|
8058
|
+
"Cache-Control": "no-cache, no-transform",
|
|
8059
|
+
Connection: "keep-alive",
|
|
8060
|
+
"Content-Type": "text/event-stream",
|
|
8061
|
+
"X-Accel-Buffering": "no"
|
|
8062
|
+
}), t.write("retry: 2000\n\n");
|
|
8063
|
+
let n = setInterval(() => t.write(": heartbeat\n\n"), 3e4);
|
|
8064
|
+
n.unref(), this.clients.set(t, n), e.once("close", () => {
|
|
8065
|
+
clearInterval(n), this.clients.delete(t);
|
|
8066
|
+
});
|
|
8067
|
+
}
|
|
8068
|
+
publish(e, t = {}) {
|
|
8069
|
+
let n = `id: ${this.nextId++}\nevent: ${e}\ndata: ${JSON.stringify(t)}\n\n`;
|
|
8070
|
+
for (let [e, t] of this.clients) e.write(n) || (clearInterval(t), this.clients.delete(e), e.end());
|
|
8071
|
+
}
|
|
8072
|
+
close() {
|
|
8073
|
+
for (let [e, t] of this.clients) clearInterval(t), e.end();
|
|
8074
|
+
this.clients.clear();
|
|
8075
|
+
}
|
|
8076
|
+
};
|
|
8077
|
+
async function zo(e, t, n, r) {
|
|
8078
|
+
let i = await fetch(new URL("/api/events", e), {
|
|
8079
|
+
headers: { Accept: "text/event-stream" },
|
|
8080
|
+
signal: t
|
|
8081
|
+
});
|
|
8082
|
+
if (!i.ok || !i.body) throw Error(`Event stream failed with HTTP ${i.status}`);
|
|
8083
|
+
r?.();
|
|
8084
|
+
let a = new TextDecoder(), o = "";
|
|
8085
|
+
for await (let e of i.body) {
|
|
8086
|
+
o += a.decode(e, { stream: !0 }).replaceAll("\r\n", "\n");
|
|
8087
|
+
let t = o.indexOf("\n\n");
|
|
8088
|
+
for (; t !== -1;) {
|
|
8089
|
+
let e = o.slice(0, t);
|
|
8090
|
+
o = o.slice(t + 2), t = o.indexOf("\n\n");
|
|
8091
|
+
let r = e.match(/^event: (.+)$/m)?.[1];
|
|
8092
|
+
if (!r) continue;
|
|
8093
|
+
let i = e.match(/^data: (.*)$/m)?.[1];
|
|
8094
|
+
n(r, i ? JSON.parse(i) : {});
|
|
8095
|
+
}
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
//#endregion
|
|
8099
|
+
//#region src/run.html?raw
|
|
8100
|
+
var Bo = "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Workflow Run</title>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"@li3/\": \"https://cdn.li3.dev/@li3/\",\n \"ansi_up\": \"https://esm.sh/ansi_up@6.0.6\"\n }\n }\n <\/script>\n</head>\n<body class=\"bg-gray-950 text-gray-100 min-h-screen p-4 md:p-6 font-sans\">\n <template app>\n <div class=\"max-w-5xl mx-auto space-y-4\">\n <header class=\"flex flex-col md:flex-row md:items-center justify-between border-b border-gray-800 pb-6 gap-4\">\n <div class=\"min-w-0\">\n <a href=\"/runs\" class=\"text-xs text-indigo-400 hover:underline mb-1 inline-block\">Back to Dashboard</a>\n <h1 class=\"text-2xl font-bold text-white flex flex-wrap items-center gap-3\">\n {{ report.workflowName }}\n <span class=\"text-sm font-mono text-gray-500\">#{{ report.jobId }}</span>\n <template if=\"report.parentId\">\n <a attr-href=\"'/runs/' + report.parentId\" class=\"text-sm font-mono text-indigo-400 hover:underline\">\n from #{{ report.parentId }}\n </a>\n </template>\n </h1>\n <p class=\"text-xs text-gray-400 mt-1\">Started {{ report.startedAt }} · {{ timing }}</p>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <span\n class=\"px-4 py-1.5 rounded-full text-sm font-semibold border\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400.border-emerald-500/20': report.status === 'success',\n 'bg-rose-500/10.text-rose-400.border-rose-500/20': report.status === 'failed',\n 'bg-indigo-500/10.text-indigo-400.border-indigo-500/20': report.status === 'running',\n 'bg-amber-500/10.text-amber-400.border-amber-500/20': !['success', 'failed', 'running'].includes(report.status)\n }\"\n >\n {{ upper(report.status) }}\n </span>\n <button\n class=\"text-sm text-white px-4 py-1.5 border border-gray-600 hover:border-gray-400 rounded-full\"\n on-click=\"restartJob()\"\n >\n Restart\n </button>\n </div>\n </header>\n\n <section class=\"rounded overflow-hidden border-b border-gray-800\">\n <h2 class=\"sr-only\">Execution Steps</h2>\n <template for=\"[step, index] of report.steps\">\n <details class=\"border border-b-0 border-gray-800 bg-gray-900/50 text-sm\">\n <summary class=\"flex flex-col md:flex-row md:items-center justify-between gap-2 p-3 bg-gray-800/40 border-b border-gray-800 cursor-pointer\">\n <div class=\"flex flex-wrap items-center gap-3 min-w-0\">\n <span class=\"font-mono text-gray-500\">\n #{{ index + 1 }}<template if=\"step.exitCode !== undefined && step.exitCode !== 0\"> ({{ step.exitCode }})</template>\n </span>\n <h3 class=\"font-semibold text-gray-200 break-words\">{{ step.name }}</h3>\n <span\n class=\"px-2.5 py-0.5 rounded-full font-medium\"\n bind-class=\"{\n 'text-emerald-400.bg-emerald-500/10': step.status === 'success',\n 'text-rose-400.bg-rose-500/10': step.status === 'failed',\n 'text-indigo-400.bg-indigo-500/10': step.status === 'running',\n 'text-gray-400.bg-gray-500/10': !['success', 'failed', 'running'].includes(step.status)\n }\"\n >\n {{ upper(step.status) }}\n </span>\n </div>\n <div class=\"font-mono text-gray-400 shrink-0\">{{ step.durationMs }}ms</div>\n </summary>\n <div class=\"p-4 bg-gray-950 font-mono text-gray-300 leading-relaxed overflow-auto w-full\">\n <pre class=\"whitespace-pre-wrap\" bind-innerhtml=\"stepLog(step)\"></pre>\n </div>\n </details>\n </template>\n </section>\n\n <details class=\"bg-gray-900 border border-gray-800 rounded-xl p-4 overflow-auto max-h-[400px]\">\n <summary class=\"cursor-pointer\">\n <h2 class=\"inline text-xs font-semibold text-gray-400 uppercase tracking-wider\">Trigger Inputs</h2>\n </summary>\n <pre class=\"font-mono text-xs text-gray-200 bg-gray-950 p-3 mt-3 rounded-lg overflow-x-auto\">{{ inputsJson }}</pre>\n </details>\n </div>\n\n <script setup>\n import { computed, onDestroy, onInit, ref } from '@li3/web';\n import { AnsiUp } from 'ansi_up';\n\n export default function setup() {\n const report = ref({});\n const now = ref(Date.now());\n const ansiUp = new AnsiUp();\n ansiUp.use_classes = false;\n let eventSource;\n let refreshTimer;\n let clockTimer;\n let refreshing = false;\n let refreshPending = false;\n\n const active = computed(() => ['pending', 'running'].includes(report.value.status));\n const inputsJson = computed(() => JSON.stringify(report.value.inputs || {}, null, 2));\n const timing = computed(() => {\n if (report.value.status === 'pending') return 'Waiting for a worker';\n if (report.value.status === 'running') {\n return `Running for ${Math.max(0, now.value - Date.parse(report.value.startedAt))}ms`;\n }\n return `Finished in ${report.value.durationMs}ms`;\n });\n\n const upper = (value) => String(value || '').toUpperCase();\n const stepLog = (step) => {\n if (step.status === 'skipped') return '';\n if (step.status === 'running') return '<span class=\"text-indigo-400\">Step is running. Logs will appear after it finishes.</span>';\n if (step.status === 'pending') return '<span class=\"text-gray-500\">Waiting to run.</span>';\n if (step.logContent) return ansiUp.ansi_to_html(step.logContent);\n return '<span class=\"text-gray-500\">(No terminal log output recorded for this step)</span>';\n };\n const restartJob = async () => {\n const response = await fetch(`/restart/${report.value.jobId}`, { method: 'POST' });\n if (response.ok) {\n const { id } = await response.json();\n location.href = `/runs/${id}`;\n }\n };\n const refreshRun = async () => {\n if (refreshing) {\n refreshPending = true;\n return;\n }\n refreshing = true;\n\n try {\n const response = await fetch(`/api/runs/${report.value.jobId}`, {\n headers: { accept: 'application/json' },\n });\n if (!response.ok) throw new Error(`Run refresh failed: ${response.status}`);\n report.value = await response.json();\n } catch (error) {\n console.error(error);\n } finally {\n refreshing = false;\n if (refreshPending) {\n refreshPending = false;\n void refreshRun();\n }\n }\n };\n const handleJobChange = (event) => {\n const data = JSON.parse(event.data || '{}');\n if (!data.jobId || String(data.jobId) === String(report.value.jobId)) void refreshRun();\n };\n\n onInit(() => {\n document.title = `Run #${report.value.jobId} - ${report.value.workflowName}`;\n eventSource = new EventSource('/api/events');\n eventSource.addEventListener('jobs.changed', handleJobChange);\n refreshTimer = setInterval(() => {\n if (active.value) void refreshRun();\n }, 60000);\n clockTimer = setInterval(() => {\n now.value = Date.now();\n }, 1000);\n });\n onDestroy(() => {\n eventSource?.close();\n clearInterval(refreshTimer);\n clearInterval(clockTimer);\n });\n\n return { report, inputsJson, timing, upper, stepLog, restartJob };\n }\n <\/script>\n <script state>__REPORT_STATE__<\/script>\n </template>\n\n <script type=\"module\">import '@li3/web';<\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n</body>\n</html>\n", Vo = /(?:^|[_-])auth(?:entication)?(?:$|[_-])|access[_-]?key|api[_-]?key|authorization|cookie|credential|passphrase|password|private[_-]?key|secret|session(?:id)?|signing[_-]?key|token/i;
|
|
8101
|
+
function Ho(e, t, n) {
|
|
8102
|
+
let r = JSON.parse(e.payload), i = e.report ? JSON.parse(e.report) : Wo(e, r), a = e.status;
|
|
8103
|
+
return {
|
|
8104
|
+
jobId: String(e.id),
|
|
8105
|
+
parentId: String(i.parentId || e.parentId || ""),
|
|
8106
|
+
workflowName: n(i.workflowName),
|
|
8107
|
+
status: a,
|
|
8108
|
+
durationMs: a === "running" ? Math.max(0, Date.now() - Date.parse(i.startedAt)) : i.durationMs,
|
|
8109
|
+
startedAt: i.startedAt,
|
|
8110
|
+
finishedAt: i.finishedAt,
|
|
8111
|
+
inputs: Go(i.inputs || {}, n),
|
|
8112
|
+
steps: (i.steps || []).map((e) => {
|
|
8113
|
+
let r = Object.hasOwn(t, e.id) && typeof t[e.id] == "string" ? t[e.id] : "";
|
|
8114
|
+
return {
|
|
8115
|
+
id: e.id,
|
|
8116
|
+
name: n(e.name),
|
|
8117
|
+
status: e.status,
|
|
8118
|
+
durationMs: e.durationMs,
|
|
8119
|
+
exitCode: e.exitCode,
|
|
8120
|
+
startedAt: e.startedAt,
|
|
8121
|
+
finishedAt: e.finishedAt,
|
|
8122
|
+
error: e.error ? n(e.error) : void 0,
|
|
8123
|
+
outputs: Go(e.outputs || {}, n),
|
|
8124
|
+
logContent: e.status === "running" || e.status === "pending" ? "" : n(r || e.logContent || "")
|
|
8125
|
+
};
|
|
8126
|
+
}),
|
|
8127
|
+
artifacts: (i.artifacts || []).map(n)
|
|
8128
|
+
};
|
|
8129
|
+
}
|
|
8130
|
+
function Uo(e) {
|
|
8131
|
+
return Bo.replace("__REPORT_STATE__", () => Fo({ report: e }));
|
|
8132
|
+
}
|
|
8133
|
+
function Wo(e, t) {
|
|
8134
|
+
let n = e.started_at || e.created_at;
|
|
8135
|
+
return {
|
|
8136
|
+
jobId: String(e.id),
|
|
8137
|
+
parentId: String(e.parentId || ""),
|
|
8138
|
+
workflowName: e.workflow_id,
|
|
8139
|
+
status: e.status,
|
|
8140
|
+
durationMs: e.status === "running" ? Math.max(0, Date.now() - Date.parse(n)) : 0,
|
|
8141
|
+
startedAt: n,
|
|
8142
|
+
inputs: t.inputs || {},
|
|
8143
|
+
environment: {},
|
|
8144
|
+
steps: (t.steps || []).map((e, t) => ({
|
|
8145
|
+
id: e.id || `step-${t}`,
|
|
8146
|
+
name: e.name || e.id || `step-${t}`,
|
|
8147
|
+
status: "pending",
|
|
8148
|
+
durationMs: 0,
|
|
8149
|
+
outputs: {},
|
|
8150
|
+
logContent: ""
|
|
8151
|
+
})),
|
|
8152
|
+
artifacts: [],
|
|
8153
|
+
rerunToken: ""
|
|
8154
|
+
};
|
|
8155
|
+
}
|
|
8156
|
+
function Go(e, t) {
|
|
8157
|
+
return typeof e == "string" ? t(e) : Array.isArray(e) ? e.map((e) => Go(e, t)) : !e || typeof e != "object" ? e : Object.fromEntries(Object.entries(e).filter(([e]) => e.toLowerCase() !== "raw" && !Vo.test(e)).map(([e, n]) => [e, Go(n, t)]));
|
|
8158
|
+
}
|
|
8159
|
+
//#endregion
|
|
8055
8160
|
//#region src/server.ts
|
|
8056
|
-
var
|
|
8161
|
+
var Ko = 50, qo = 500, Jo = class e {
|
|
8057
8162
|
server;
|
|
8058
8163
|
preprocessors = /* @__PURE__ */ new Map();
|
|
8059
8164
|
workflows = [];
|
|
8060
8165
|
queue;
|
|
8061
8166
|
secrets;
|
|
8062
8167
|
adminToken;
|
|
8168
|
+
events = new Ro();
|
|
8169
|
+
workflowsLoaded;
|
|
8063
8170
|
static async withPort(t) {
|
|
8064
8171
|
let { port: n, ...r } = t;
|
|
8065
8172
|
return new e(r).listen(n);
|
|
8066
8173
|
}
|
|
8067
8174
|
constructor(e) {
|
|
8068
|
-
this.queue = e.queue, this.secrets = e.secrets, this.adminToken = e.adminToken,
|
|
8175
|
+
this.queue = e.queue, this.secrets = e.secrets, this.adminToken = e.adminToken, this.workflowsLoaded = No.from(e.config.workflows).then((t) => {
|
|
8069
8176
|
this.workflows = t, console.log(`✅ Loaded ${t.length} workflow(s) from ${e.config.workflows}`);
|
|
8070
8177
|
}), this.registerPreprocessor(new Mo()), this.server = f.createServer((e, t) => this.handleRequest(e, t));
|
|
8071
8178
|
}
|
|
@@ -8076,12 +8183,18 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8076
8183
|
let n = new p(e.url || "/", `${e.headers["x-forwarded-proto"] || "http"}://${e.headers["x-forwarded-host"] || e.headers.host}`);
|
|
8077
8184
|
if (e.method === "GET" && (n.pathname === "/runs" || n.pathname === "/")) return this.renderDashboard(t);
|
|
8078
8185
|
if (e.method === "GET" && n.pathname === "/api/jobs") {
|
|
8079
|
-
let e = n.searchParams.get("afterId"), r = n.searchParams.get("beforeId"), i = n.searchParams.get("limit"), a = e === null ? void 0 : Number(e), o = r === null ? void 0 : Number(r), s = i === null ?
|
|
8080
|
-
return a !== void 0 && (!Number.isSafeInteger(a) || a < 0) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "afterId must be a non-negative integer" }))) : o !== void 0 && (!Number.isSafeInteger(o) || o < 1) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "beforeId must be a positive integer" }))) : !Number.isSafeInteger(s) || s < 1 || s >
|
|
8186
|
+
let e = n.searchParams.get("afterId"), r = n.searchParams.get("beforeId"), i = n.searchParams.get("limit"), a = e === null ? void 0 : Number(e), o = r === null ? void 0 : Number(r), s = i === null ? Ko : Number(i);
|
|
8187
|
+
return a !== void 0 && (!Number.isSafeInteger(a) || a < 0) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "afterId must be a non-negative integer" }))) : o !== void 0 && (!Number.isSafeInteger(o) || o < 1) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "beforeId must be a positive integer" }))) : !Number.isSafeInteger(s) || s < 1 || s > qo ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: `limit must be an integer from 1 to ${qo}` }))) : this.renderDashboardJobs(t, s, a, o);
|
|
8081
8188
|
}
|
|
8189
|
+
if (e.method === "GET" && n.pathname === "/api/events") return this.events.subscribe(e, t);
|
|
8190
|
+
if (e.method === "POST" && n.pathname === "/api/events") return this.handleWorkerEvent(e, t);
|
|
8082
8191
|
if (e.method === "GET" && n.pathname.startsWith("/runs/")) {
|
|
8083
8192
|
let e = n.pathname.replace("/runs/", "");
|
|
8084
|
-
return this.renderRunDetails(e, t);
|
|
8193
|
+
return this.renderRunDetails(e, t, "html");
|
|
8194
|
+
}
|
|
8195
|
+
if (e.method === "GET" && n.pathname.startsWith("/api/runs/")) {
|
|
8196
|
+
let e = n.pathname.replace("/api/runs/", "");
|
|
8197
|
+
return this.renderRunDetails(e, t, "json");
|
|
8085
8198
|
}
|
|
8086
8199
|
if (e.method === "POST" && n.pathname.startsWith("/restart/")) {
|
|
8087
8200
|
let e = n.pathname.replace("/restart/", "");
|
|
@@ -8103,7 +8216,7 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8103
8216
|
n.writeHead(401, { "Content-Type": "application/json" }), n.end(JSON.stringify({ error: "Invalid HMAC signature or authentication failed" }));
|
|
8104
8217
|
return;
|
|
8105
8218
|
}
|
|
8106
|
-
this.matchWorkflows(e, o), n.writeHead(202, { "Content-Type": "application/json" }), n.end(JSON.stringify({ message: "OK" }));
|
|
8219
|
+
await this.workflowsLoaded, await this.matchWorkflows(e, o), n.writeHead(202, { "Content-Type": "application/json" }), n.end(JSON.stringify({ message: "OK" }));
|
|
8107
8220
|
} catch (e) {
|
|
8108
8221
|
console.error("❌ Webhook Ingress Error:", e), n.writeHead(500, { "Content-Type": "application/json" }), n.end(JSON.stringify({
|
|
8109
8222
|
error: "Internal Ingress Error",
|
|
@@ -8169,21 +8282,35 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8169
8282
|
workflowId: n.id,
|
|
8170
8283
|
env: n.env,
|
|
8171
8284
|
steps: n.steps,
|
|
8172
|
-
inputs: t
|
|
8285
|
+
inputs: t,
|
|
8286
|
+
tags: n.tags
|
|
8173
8287
|
};
|
|
8174
|
-
await this.queue.enqueue(n.id, a, i);
|
|
8288
|
+
await this.queue.enqueue(n.id, a, i), this.events.publish("jobs.available", { tags: n.tags || [] });
|
|
8175
8289
|
}
|
|
8176
8290
|
}
|
|
8177
8291
|
async handleSecretReload(e, t) {
|
|
8178
8292
|
if (e.headers.authorization !== `Bearer ${this.adminToken}`) return t.writeHead(403, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8179
8293
|
this.secrets.reload(), console.log("🔄 SecretStore reloaded successfully without downtime!"), t.writeHead(200, { "Content-Type": "application/json" }), t.end(JSON.stringify({ message: "Secrets reloaded successfully" }));
|
|
8180
8294
|
}
|
|
8295
|
+
async handleWorkerEvent(e, t) {
|
|
8296
|
+
if (!this.adminToken || e.headers.authorization !== `Bearer ${this.adminToken}`) return t.writeHead(403, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Unauthorized" }));
|
|
8297
|
+
let { rawBuffer: n } = await this.readRequest(e, t);
|
|
8298
|
+
if (!n || t.headersSent) return;
|
|
8299
|
+
let r;
|
|
8300
|
+
try {
|
|
8301
|
+
let e = n.length ? JSON.parse(n.toString("utf8")) : {}, t = Number(e.jobId);
|
|
8302
|
+
Number.isSafeInteger(t) && t > 0 && (r = t);
|
|
8303
|
+
} catch {
|
|
8304
|
+
return t.writeHead(400, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Invalid event payload" }));
|
|
8305
|
+
}
|
|
8306
|
+
this.events.publish("jobs.changed", r ? { jobId: r } : {}), t.writeHead(202).end();
|
|
8307
|
+
}
|
|
8181
8308
|
async renderDashboard(e) {
|
|
8182
|
-
let t = await this.queue.listJobs(51), n =
|
|
8309
|
+
let t = await this.queue.listJobs(51), n = Io(t.slice(0, Ko)), r = this.secrets.redactText(Lo(n, t.length > Ko));
|
|
8183
8310
|
e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(r);
|
|
8184
8311
|
}
|
|
8185
8312
|
async renderDashboardJobs(e, t, n, r) {
|
|
8186
|
-
let i = await this.queue.listJobs(t + 1, n, r), a =
|
|
8313
|
+
let i = await this.queue.listJobs(t + 1, n, r), a = Io(i.slice(0, t)), o = this.secrets.redactText(JSON.stringify({
|
|
8187
8314
|
jobs: a,
|
|
8188
8315
|
hasMore: i.length > t
|
|
8189
8316
|
}));
|
|
@@ -8192,49 +8319,23 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8192
8319
|
"Content-Type": "application/json; charset=utf-8"
|
|
8193
8320
|
}), e.end(o);
|
|
8194
8321
|
}
|
|
8195
|
-
async renderRunDetails(e, t) {
|
|
8196
|
-
let
|
|
8197
|
-
if (!
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(
|
|
8207
|
-
}
|
|
8208
|
-
buildPendingReport(e) {
|
|
8209
|
-
let t = JSON.parse(e.payload), n = e.started_at || e.created_at;
|
|
8210
|
-
return {
|
|
8211
|
-
jobId: String(e.id),
|
|
8212
|
-
parentId: String(e.parentId || ""),
|
|
8213
|
-
workflowName: e.workflow_id,
|
|
8214
|
-
status: e.status,
|
|
8215
|
-
durationMs: e.status === "running" ? Math.max(0, Date.now() - Date.parse(n)) : 0,
|
|
8216
|
-
startedAt: n,
|
|
8217
|
-
inputs: t.inputs || {},
|
|
8218
|
-
environment: t.env || {},
|
|
8219
|
-
steps: (t.steps || []).map((e, t) => ({
|
|
8220
|
-
id: e.id || `step-${t}`,
|
|
8221
|
-
name: e.name || e.id || `step-${t}`,
|
|
8222
|
-
status: "pending",
|
|
8223
|
-
durationMs: 0,
|
|
8224
|
-
outputs: {},
|
|
8225
|
-
logContent: ""
|
|
8226
|
-
})),
|
|
8227
|
-
artifacts: [],
|
|
8228
|
-
rerunToken: JSON.stringify({
|
|
8229
|
-
jobId: e.id,
|
|
8230
|
-
payload: t
|
|
8231
|
-
})
|
|
8232
|
-
};
|
|
8322
|
+
async renderRunDetails(e, t, n) {
|
|
8323
|
+
let r = await this.queue.getJob(e);
|
|
8324
|
+
if (!r) {
|
|
8325
|
+
let e = n === "json" ? "application/json; charset=utf-8" : "text/html; charset=utf-8";
|
|
8326
|
+
return t.writeHead(404, { "Content-Type": e }), t.end(n === "json" ? JSON.stringify({ error: "Run not found" }) : "<h1>404 - Run Not Found</h1>");
|
|
8327
|
+
}
|
|
8328
|
+
let i = Ho(r, await this.queue.getJobLogs(e), (e) => this.secrets.redactText(e));
|
|
8329
|
+
if (n === "json") return t.writeHead(200, {
|
|
8330
|
+
"Cache-Control": "no-store",
|
|
8331
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
8332
|
+
}), t.end(JSON.stringify(i));
|
|
8333
|
+
t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(Uo(i));
|
|
8233
8334
|
}
|
|
8234
8335
|
async handleRestartJob(e, t) {
|
|
8235
8336
|
let n = await this.queue.restartJob(e);
|
|
8236
8337
|
if (n) {
|
|
8237
|
-
t.writeHead(201, { "Content-Type": "application/json" }), t.end(JSON.stringify({ id: n }));
|
|
8338
|
+
this.events.publish("jobs.available"), t.writeHead(201, { "Content-Type": "application/json" }), t.end(JSON.stringify({ id: n }));
|
|
8238
8339
|
return;
|
|
8239
8340
|
}
|
|
8240
8341
|
t.writeHead(404, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Job not found" }));
|
|
@@ -8247,13 +8348,13 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8247
8348
|
});
|
|
8248
8349
|
}
|
|
8249
8350
|
async stop() {
|
|
8250
|
-
return new Promise((e) => {
|
|
8351
|
+
return this.events.close(), new Promise((e) => {
|
|
8251
8352
|
this.server.close(() => {
|
|
8252
8353
|
console.log("🌐 Webhook Ingress Server stopped listening."), e();
|
|
8253
8354
|
});
|
|
8254
8355
|
});
|
|
8255
8356
|
}
|
|
8256
|
-
},
|
|
8357
|
+
}, Yo = d(v), Xo = class {
|
|
8257
8358
|
name = "systemd";
|
|
8258
8359
|
async isSupported() {
|
|
8259
8360
|
try {
|
|
@@ -8376,7 +8477,7 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8376
8477
|
cancel: async () => {
|
|
8377
8478
|
d = !0;
|
|
8378
8479
|
try {
|
|
8379
|
-
t.image && await
|
|
8480
|
+
t.image && await Yo(`docker stop -t 2 ${o}`).catch(() => {}), await Yo(`systemctl stop ${o}.service`).catch(() => {});
|
|
8380
8481
|
} catch {}
|
|
8381
8482
|
},
|
|
8382
8483
|
logFilePath: i
|
|
@@ -8386,7 +8487,7 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8386
8487
|
let t = await g(e, "utf-8"), n = t.includes("Running as unit: ") ? t.indexOf("\n") + 1 : 0, r = t.includes("Finished with result: ") ? t.lastIndexOf("Finished with result: ") : t.length;
|
|
8387
8488
|
return t.slice(n, r);
|
|
8388
8489
|
}
|
|
8389
|
-
},
|
|
8490
|
+
}, Zo = class {
|
|
8390
8491
|
name = "standard-process";
|
|
8391
8492
|
async isSupported() {
|
|
8392
8493
|
return !0;
|
|
@@ -8501,13 +8602,13 @@ var Vo = 50, Ho = 500, Uo = class e {
|
|
|
8501
8602
|
};
|
|
8502
8603
|
//#endregion
|
|
8503
8604
|
//#region src/drivers/index.ts
|
|
8504
|
-
async function
|
|
8505
|
-
let e = new
|
|
8506
|
-
return await e.isSupported() ? (console.log("⚡ Selected Execution Driver: Systemd (cgroups enabled)"), e) : (console.log("📦 Selected Execution Driver: Standard Process (Fallback)"), new
|
|
8605
|
+
async function Qo() {
|
|
8606
|
+
let e = new Xo();
|
|
8607
|
+
return await e.isSupported() ? (console.log("⚡ Selected Execution Driver: Systemd (cgroups enabled)"), e) : (console.log("📦 Selected Execution Driver: Standard Process (Fallback)"), new Zo());
|
|
8507
8608
|
}
|
|
8508
8609
|
//#endregion
|
|
8509
8610
|
//#region src/signals.ts
|
|
8510
|
-
async function
|
|
8611
|
+
async function $o(e) {
|
|
8511
8612
|
let t = !1;
|
|
8512
8613
|
async function n(n) {
|
|
8513
8614
|
if (!t) {
|
|
@@ -8515,7 +8616,7 @@ async function Jo(e) {
|
|
|
8515
8616
|
console.error("⚠️ Graceful shutdown timed out after 10s. Forcing exit!"), process.exit(1);
|
|
8516
8617
|
}, 1e4).unref();
|
|
8517
8618
|
try {
|
|
8518
|
-
|
|
8619
|
+
os(), e.length > 0 && (console.log("⚙️ Waiting for active worker jobs to drain..."), await Promise.allSettled(e)), console.log("✨ Engine stopped cleanly. Goodbye!"), process.exit(0);
|
|
8519
8620
|
} catch (e) {
|
|
8520
8621
|
console.error("❌ Error during graceful shutdown:", e.message), process.exit(1);
|
|
8521
8622
|
}
|
|
@@ -8524,35 +8625,126 @@ async function Jo(e) {
|
|
|
8524
8625
|
process.on("SIGINT", () => n("SIGINT")), process.on("SIGTERM", () => n("SIGTERM"));
|
|
8525
8626
|
}
|
|
8526
8627
|
//#endregion
|
|
8527
|
-
//#region src/
|
|
8528
|
-
var
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
let
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8628
|
+
//#region src/plugins/manager.ts
|
|
8629
|
+
var es = class {
|
|
8630
|
+
plugins;
|
|
8631
|
+
constructor(e = []) {
|
|
8632
|
+
this.plugins = e;
|
|
8633
|
+
}
|
|
8634
|
+
register(e) {
|
|
8635
|
+
console.log(`🔌 Registered Plugin: ${e.name}`), this.plugins.push(e);
|
|
8636
|
+
}
|
|
8637
|
+
async triggerWorkflowStart(e) {
|
|
8638
|
+
for (let t of this.plugins) if (t.onWorkflowStart) try {
|
|
8639
|
+
await t.onWorkflowStart(e);
|
|
8640
|
+
} catch (e) {
|
|
8641
|
+
console.error(`[Plugin Error] ${t.name}.onWorkflowStart:`, e);
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
async triggerWorkflowFinish(e, t) {
|
|
8645
|
+
for (let n of this.plugins) if (n.onWorkflowFinish) try {
|
|
8646
|
+
await n.onWorkflowFinish(e, t);
|
|
8647
|
+
} catch (e) {
|
|
8648
|
+
console.error(`[Plugin Error] ${n.name}.onWorkflowFinish:`, e);
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
}, ts = !!process.env.DEBUG, ns = { isStopping: !1 }, rs = /* @__PURE__ */ new Set(), is = null, as = null;
|
|
8652
|
+
function os() {
|
|
8653
|
+
ns.isStopping = !0, is?.abort(), as?.();
|
|
8654
|
+
}
|
|
8655
|
+
function ss(e, t, n, r) {
|
|
8656
|
+
ns.isStopping = !1;
|
|
8657
|
+
let i = [cs(e, t, n, r)];
|
|
8658
|
+
return $o(i), i;
|
|
8659
|
+
}
|
|
8660
|
+
async function cs(e, t, n, r) {
|
|
8661
|
+
let i = await Qo(), a = /* @__PURE__ */ new Set(), o = 0, s = null, c = () => {
|
|
8662
|
+
o++, s?.();
|
|
8663
|
+
};
|
|
8664
|
+
as = c, is = new AbortController();
|
|
8665
|
+
let l = ls(r, is.signal, c);
|
|
8666
|
+
for (console.log(`🚀 Worker scheduler started. Driver: ${i.name}. Concurrency: ${e}. Tags: ${r.tags.join(", ") || "(none)"}`); !ns.isStopping;) {
|
|
8667
|
+
let l = o;
|
|
8668
|
+
try {
|
|
8669
|
+
for (; a.size < e && !ns.isStopping;) {
|
|
8670
|
+
let e = await t.claimNextJob(r.tags);
|
|
8671
|
+
if (!e) break;
|
|
8672
|
+
if (ns.isStopping) {
|
|
8673
|
+
await t.releaseJob(e.id);
|
|
8674
|
+
break;
|
|
8675
|
+
}
|
|
8676
|
+
let o = `worker-${e.id}`, s;
|
|
8677
|
+
s = (async () => {
|
|
8678
|
+
fs(r, e.id), await ps({
|
|
8679
|
+
workerId: o,
|
|
8680
|
+
job: e,
|
|
8681
|
+
queue: t,
|
|
8682
|
+
secrets: n,
|
|
8683
|
+
config: r,
|
|
8684
|
+
driver: i
|
|
8685
|
+
});
|
|
8686
|
+
})().catch((e) => console.error(`[${o}] ⚠️ Worker execution error:`, e)).finally(() => {
|
|
8687
|
+
a.delete(s), fs(r, e.id), c();
|
|
8688
|
+
}), a.add(s);
|
|
8689
|
+
}
|
|
8690
|
+
} catch (e) {
|
|
8691
|
+
console.error("⚠️ Worker scheduler claim error:", e), setTimeout(c, 5e3).unref();
|
|
8541
8692
|
}
|
|
8542
|
-
await
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8693
|
+
!ns.isStopping && o === l && (await us(l, () => o, (e) => s = e), s = null);
|
|
8694
|
+
}
|
|
8695
|
+
is.abort(), await Promise.allSettled(a), await l, as = null, console.log("🛑 Worker scheduler stopped cleanly.");
|
|
8696
|
+
}
|
|
8697
|
+
async function ls(e, t, n) {
|
|
8698
|
+
let r = 1e3;
|
|
8699
|
+
for (; !t.aborted;) {
|
|
8700
|
+
try {
|
|
8701
|
+
await zo(e.serverUrl, t, (t, r) => {
|
|
8702
|
+
t === "jobs.available" && (Array.isArray(r.tags) ? r.tags.filter((e) => typeof e == "string") : []).every((t) => e.tags.includes(t)) && n();
|
|
8703
|
+
}, n), r = 1e3;
|
|
8704
|
+
} catch (e) {
|
|
8705
|
+
if (t.aborted || e?.name === "AbortError") break;
|
|
8706
|
+
console.error(`⚠️ Worker event stream disconnected: ${e.message}`);
|
|
8707
|
+
}
|
|
8708
|
+
await ds(r, t), r = Math.min(r * 2, 3e4);
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
function us(e, t, n) {
|
|
8712
|
+
return new Promise((r) => {
|
|
8713
|
+
if (t() !== e) return r();
|
|
8714
|
+
let i = setTimeout(r, 6e4);
|
|
8715
|
+
n(() => {
|
|
8716
|
+
clearTimeout(i), r();
|
|
8549
8717
|
});
|
|
8550
|
-
}
|
|
8551
|
-
|
|
8718
|
+
});
|
|
8719
|
+
}
|
|
8720
|
+
function ds(e, t) {
|
|
8721
|
+
return new Promise((n) => {
|
|
8722
|
+
if (t.aborted) return n();
|
|
8723
|
+
let r = () => {
|
|
8724
|
+
clearTimeout(i), n();
|
|
8725
|
+
}, i = setTimeout(() => {
|
|
8726
|
+
t.removeEventListener("abort", r), n();
|
|
8727
|
+
}, e);
|
|
8728
|
+
t.addEventListener("abort", r, { once: !0 });
|
|
8729
|
+
});
|
|
8730
|
+
}
|
|
8731
|
+
async function fs(e, t) {
|
|
8732
|
+
if (e.adminToken) try {
|
|
8733
|
+
let n = await fetch(new URL("/api/events", e.serverUrl), {
|
|
8734
|
+
method: "POST",
|
|
8735
|
+
headers: {
|
|
8736
|
+
Authorization: `Bearer ${e.adminToken}`,
|
|
8737
|
+
"Content-Type": "application/json"
|
|
8738
|
+
},
|
|
8739
|
+
body: JSON.stringify({ jobId: t }),
|
|
8740
|
+
signal: AbortSignal.timeout(5e3)
|
|
8741
|
+
});
|
|
8742
|
+
!n.ok && ts && console.error(`Failed to publish job status event: HTTP ${n.status}`);
|
|
8743
|
+
} catch (e) {
|
|
8744
|
+
ts && console.error("Failed to publish job status event:", e);
|
|
8552
8745
|
}
|
|
8553
|
-
console.log(`[${e}] 🛑 Worker loop stopped cleanly.`);
|
|
8554
8746
|
}
|
|
8555
|
-
async function
|
|
8747
|
+
async function ps(e) {
|
|
8556
8748
|
let { workerId: t, job: n, config: r, secrets: i, queue: o } = e;
|
|
8557
8749
|
console.log(`\n[${t}] 📦 Claimed Job #${n.id} (Workflow: ${n.workflow_id})`);
|
|
8558
8750
|
let s = typeof n.payload == "string" ? JSON.parse(n.payload) : n.payload, c = s.steps || [], l = s.inputs || {}, u = Date.now(), d = a.join(r.storagePath, `job-${n.id}`), f = a.join(d, "logs"), p = a.join(d, "wd"), m = {
|
|
@@ -8569,20 +8761,27 @@ async function $o(e) {
|
|
|
8569
8761
|
durationMs: 0,
|
|
8570
8762
|
outputs: {},
|
|
8571
8763
|
logContent: ""
|
|
8572
|
-
})), g =
|
|
8573
|
-
await o.saveReport(n.id, g);
|
|
8574
|
-
let
|
|
8764
|
+
})), g = bs(n, "running", u, l, m.env, h, s);
|
|
8765
|
+
await o.saveReport(n.id, g), fs(r, n.id);
|
|
8766
|
+
let _ = new es(r.plugins), v = {
|
|
8767
|
+
jobId: String(n.id),
|
|
8768
|
+
workflowName: n.workflow_id,
|
|
8769
|
+
inputs: l,
|
|
8770
|
+
runUrl: new URL(`/runs/${n.id}`, r.serverUrl).toString()
|
|
8771
|
+
};
|
|
8772
|
+
await _.triggerWorkflowStart(v);
|
|
8773
|
+
let { cancelled: y, failed: ee } = await ms({
|
|
8575
8774
|
payload: s,
|
|
8576
8775
|
steps: c,
|
|
8577
8776
|
executionContext: m,
|
|
8578
8777
|
...e
|
|
8579
|
-
}, g),
|
|
8580
|
-
g.status =
|
|
8778
|
+
}, g), b = y ? "cancelled" : ee ? "failed" : "success";
|
|
8779
|
+
g.status = b, g.durationMs = Date.now() - u, g.finishedAt = (/* @__PURE__ */ new Date()).toISOString(), await o.completeJob(n.id, b, g), fs(r, n.id), console.log(`[${t}] ✅ Job #${n.id} completed as: ${b}`), await _.triggerWorkflowFinish(v, b);
|
|
8581
8780
|
}
|
|
8582
|
-
async function
|
|
8781
|
+
async function ms(e, t) {
|
|
8583
8782
|
let { driver: n, workerId: r, queue: i, config: a, job: o, payload: s, steps: c, executionContext: l } = e, u = t.steps, d = !1, f = !1, p = 0;
|
|
8584
8783
|
try {
|
|
8585
|
-
s.env && Object.assign(l.env, await
|
|
8784
|
+
s.env && Object.assign(l.env, await _s(s.env, l));
|
|
8586
8785
|
for (let e = 0; e < c.length; e++) {
|
|
8587
8786
|
let s = c[e], m = (/* @__PURE__ */ new Date()).toISOString();
|
|
8588
8787
|
u[e] = {
|
|
@@ -8595,8 +8794,8 @@ async function es(e, t) {
|
|
|
8595
8794
|
logContent: "",
|
|
8596
8795
|
startedAt: m,
|
|
8597
8796
|
finishedAt: void 0
|
|
8598
|
-
}, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t);
|
|
8599
|
-
let h = await
|
|
8797
|
+
}, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), fs(a, o.id);
|
|
8798
|
+
let h = await hs({
|
|
8600
8799
|
workerId: r,
|
|
8601
8800
|
jobId: o.id,
|
|
8602
8801
|
step: s,
|
|
@@ -8623,7 +8822,7 @@ async function es(e, t) {
|
|
|
8623
8822
|
exitCode: 1,
|
|
8624
8823
|
error: "Step failed before execution started",
|
|
8625
8824
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8626
|
-
}, p = e + 1, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), h.skipped) break;
|
|
8825
|
+
}, p = e + 1, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), fs(a, o.id), h.skipped) break;
|
|
8627
8826
|
if (h.cancelled) {
|
|
8628
8827
|
f = !0, d = !0;
|
|
8629
8828
|
break;
|
|
@@ -8636,22 +8835,22 @@ async function es(e, t) {
|
|
|
8636
8835
|
} catch (e) {
|
|
8637
8836
|
console.log("🛑 Step failed", e), d = !0;
|
|
8638
8837
|
}
|
|
8639
|
-
return p < c.length && (
|
|
8838
|
+
return p < c.length && (ys(c, p, u), t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), fs(a, o.id)), {
|
|
8640
8839
|
failed: d,
|
|
8641
8840
|
cancelled: f,
|
|
8642
8841
|
stepReports: u
|
|
8643
8842
|
};
|
|
8644
8843
|
}
|
|
8645
|
-
async function
|
|
8844
|
+
async function hs(e) {
|
|
8646
8845
|
let { step: t, stepIndex: n, executionContext: r } = e;
|
|
8647
|
-
if (t.id ||= `step-${n}`, t.name ||= t.id, t.if && !await ko.evaluateConditions(t.if, r)) return
|
|
8846
|
+
if (t.id ||= `step-${n}`, t.name ||= t.id, t.if && !await ko.evaluateConditions(t.if, r)) return ts && console.log(`⏩ Skipped step ${t.id} based on condition: ${t.if}`, r), {
|
|
8648
8847
|
failed: !1,
|
|
8649
8848
|
cancelled: !1,
|
|
8650
8849
|
skipped: !0,
|
|
8651
8850
|
report: null
|
|
8652
8851
|
};
|
|
8653
8852
|
try {
|
|
8654
|
-
let n = await
|
|
8853
|
+
let n = await _s(t.env, r), i = {
|
|
8655
8854
|
jobId: String(e.jobId),
|
|
8656
8855
|
step: t,
|
|
8657
8856
|
command: t.run,
|
|
@@ -8665,15 +8864,15 @@ async function ts(e) {
|
|
|
8665
8864
|
WORKING_DIR: r.workingDir
|
|
8666
8865
|
}
|
|
8667
8866
|
};
|
|
8668
|
-
return t.eval ?
|
|
8867
|
+
return t.eval ? gs({
|
|
8669
8868
|
...e,
|
|
8670
8869
|
stepContext: i
|
|
8671
|
-
}) :
|
|
8870
|
+
}) : vs({
|
|
8672
8871
|
...e,
|
|
8673
8872
|
stepContext: i
|
|
8674
8873
|
});
|
|
8675
8874
|
} catch (e) {
|
|
8676
|
-
return
|
|
8875
|
+
return ts && console.error(`⏩ Failed to run step ${t.id}`, r, e), {
|
|
8677
8876
|
failed: !0,
|
|
8678
8877
|
cancelled: !1,
|
|
8679
8878
|
skipped: !1,
|
|
@@ -8681,7 +8880,7 @@ async function ts(e) {
|
|
|
8681
8880
|
};
|
|
8682
8881
|
}
|
|
8683
8882
|
}
|
|
8684
|
-
async function
|
|
8883
|
+
async function gs(e) {
|
|
8685
8884
|
let { queue: t, stepContext: n, executionContext: r } = e, { jobId: i, step: a } = n, o = Date.now(), s = a.id, c = a.name;
|
|
8686
8885
|
try {
|
|
8687
8886
|
let e = await ko.evaluateExpression(a.eval, r);
|
|
@@ -8727,12 +8926,12 @@ async function ns(e) {
|
|
|
8727
8926
|
};
|
|
8728
8927
|
}
|
|
8729
8928
|
}
|
|
8730
|
-
async function
|
|
8929
|
+
async function _s(e, t) {
|
|
8731
8930
|
let n = {};
|
|
8732
8931
|
if (e) for (let [r, i] of Object.entries(e)) n[r] = String(await ko.evaluateValue(i, t));
|
|
8733
8932
|
return n;
|
|
8734
8933
|
}
|
|
8735
|
-
async function
|
|
8934
|
+
async function vs(t) {
|
|
8736
8935
|
let { workerId: n, jobId: r, stepContext: i, executionContext: a, driver: o, queue: s } = t, { step: c } = i, l = c.id, u = c.name, d;
|
|
8737
8936
|
try {
|
|
8738
8937
|
d = o.execute(i);
|
|
@@ -8747,10 +8946,11 @@ async function is(t) {
|
|
|
8747
8946
|
logFilePath: ""
|
|
8748
8947
|
};
|
|
8749
8948
|
}
|
|
8949
|
+
rs.add(d);
|
|
8750
8950
|
let f = !1, p = setInterval(async () => {
|
|
8751
8951
|
await s.isCancelled(r) && (console.log(`[${n}] 🛑 Job #${r} was cancelled! Halting execution.`), f = !0, clearInterval(p), await d.cancel());
|
|
8752
8952
|
}, 3e3), m = await d.done;
|
|
8753
|
-
if (clearInterval(p), d.logFilePath && e.existsSync(d.logFilePath)) try {
|
|
8953
|
+
if (clearInterval(p), rs.delete(d), d.logFilePath && e.existsSync(d.logFilePath)) try {
|
|
8754
8954
|
let e = await o.readLog(d.logFilePath);
|
|
8755
8955
|
await s.saveStepLog(r, l, e);
|
|
8756
8956
|
} catch (e) {
|
|
@@ -8777,7 +8977,7 @@ async function is(t) {
|
|
|
8777
8977
|
}
|
|
8778
8978
|
};
|
|
8779
8979
|
}
|
|
8780
|
-
function
|
|
8980
|
+
function ys(e, t, n) {
|
|
8781
8981
|
for (let r = t; r < e.length; r++) {
|
|
8782
8982
|
let t = e[r], i = t.id || `step-${r}`;
|
|
8783
8983
|
n[r] = {
|
|
@@ -8791,7 +8991,7 @@ function as(e, t, n) {
|
|
|
8791
8991
|
};
|
|
8792
8992
|
}
|
|
8793
8993
|
}
|
|
8794
|
-
function
|
|
8994
|
+
function bs(e, t, n, r, i, a, o) {
|
|
8795
8995
|
return {
|
|
8796
8996
|
jobId: String(e.id),
|
|
8797
8997
|
parentId: String(e.parentId || ""),
|
|
@@ -8810,56 +9010,51 @@ function os(e, t, n, r, i, a, o) {
|
|
|
8810
9010
|
})
|
|
8811
9011
|
};
|
|
8812
9012
|
}
|
|
8813
|
-
async function ss(e, t = [], n) {
|
|
8814
|
-
!Array.isArray(t) || t.length === 0 || (console.log(`[${e}] 📢 Dispatching execution report to ${t.length} reporter(s)...`), await Promise.allSettled(t.map(async (t) => {
|
|
8815
|
-
try {
|
|
8816
|
-
await t.report(n);
|
|
8817
|
-
} catch (n) {
|
|
8818
|
-
console.error(`[${e}] ⚠️ Reporter '${t.name || "unknown"}' failed:`, n.message);
|
|
8819
|
-
}
|
|
8820
|
-
})));
|
|
8821
|
-
}
|
|
8822
9013
|
//#endregion
|
|
8823
|
-
//#region src/
|
|
8824
|
-
var
|
|
8825
|
-
name = "
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
}
|
|
8830
|
-
async report(t) {
|
|
8831
|
-
e.mkdirSync(this.outputDir, { recursive: !0 });
|
|
8832
|
-
let n = a.join(this.outputDir, `run-${t.jobId}.json`);
|
|
8833
|
-
e.writeFileSync(n, JSON.stringify(t, null, 2), "utf-8"), console.log(`📊 Execution report saved to: ${n}`);
|
|
8834
|
-
}
|
|
8835
|
-
}, ls = class {
|
|
8836
|
-
name = "slack";
|
|
8837
|
-
webhookUrl = "";
|
|
8838
|
-
token = "";
|
|
8839
|
-
channel = "";
|
|
8840
|
-
notifyOn = ["failed"];
|
|
9014
|
+
//#region src/plugins/github-status.plugin.ts
|
|
9015
|
+
var xs = class {
|
|
9016
|
+
name = "github-commit-status";
|
|
9017
|
+
token;
|
|
9018
|
+
apiUrl;
|
|
9019
|
+
context;
|
|
8841
9020
|
constructor(e) {
|
|
8842
|
-
|
|
9021
|
+
this.token = e.token, this.apiUrl = e.apiUrl || "https://api.github.com/", this.context = e.context || "on";
|
|
9022
|
+
}
|
|
9023
|
+
async onWorkflowStart(e) {
|
|
9024
|
+
this.hasCoordinates(e) && await this.updateStatus(e, "pending", "Workflow build has started.");
|
|
8843
9025
|
}
|
|
8844
|
-
async
|
|
8845
|
-
if (!this.
|
|
8846
|
-
let
|
|
8847
|
-
await
|
|
9026
|
+
async onWorkflowFinish(e, t) {
|
|
9027
|
+
if (!this.hasCoordinates(e)) return;
|
|
9028
|
+
let n = t === "success" ? "success" : t === "failed" ? "failure" : "error", r = t === "success" ? "Workflow completed successfully." : `Workflow ${t}.`;
|
|
9029
|
+
await this.updateStatus(e, n, r);
|
|
9030
|
+
}
|
|
9031
|
+
async updateStatus(e, t, n) {
|
|
9032
|
+
let r = encodeURIComponent(String(e.inputs.owner)), i = encodeURIComponent(String(e.inputs.repo)), a = encodeURIComponent(String(e.inputs.commit_sha)), o = this.apiUrl.endsWith("/") ? this.apiUrl : `${this.apiUrl}/`, s = await fetch(new URL(`repos/${r}/${i}/statuses/${a}`, o), {
|
|
8848
9033
|
method: "POST",
|
|
8849
9034
|
headers: {
|
|
9035
|
+
Accept: "application/vnd.github+json",
|
|
8850
9036
|
Authorization: `Bearer ${this.token}`,
|
|
8851
|
-
"Content-Type": "application/json"
|
|
9037
|
+
"Content-Type": "application/json",
|
|
9038
|
+
"User-Agent": "@cloud-cli/on",
|
|
9039
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
8852
9040
|
},
|
|
9041
|
+
signal: AbortSignal.timeout(1e4),
|
|
8853
9042
|
body: JSON.stringify({
|
|
8854
|
-
|
|
8855
|
-
|
|
9043
|
+
state: t,
|
|
9044
|
+
target_url: e.runUrl,
|
|
9045
|
+
description: n.slice(0, 140),
|
|
9046
|
+
context: `${this.context}/${e.workflowName}`.slice(0, 100)
|
|
8856
9047
|
})
|
|
8857
9048
|
});
|
|
9049
|
+
if (!s.ok) throw Error(`GitHub status update failed with HTTP ${s.status}: ${await s.text()}`);
|
|
9050
|
+
}
|
|
9051
|
+
hasCoordinates(e) {
|
|
9052
|
+
return !!(this.token && e.inputs.owner && e.inputs.repo && e.inputs.commit_sha);
|
|
8858
9053
|
}
|
|
8859
9054
|
};
|
|
8860
9055
|
//#endregion
|
|
8861
9056
|
//#region src/index.ts
|
|
8862
|
-
function
|
|
9057
|
+
function Ss(e) {
|
|
8863
9058
|
console.log("🔍 Validating Workflows in:", e.workflows);
|
|
8864
9059
|
let t = new di(e.workflows), n = r(e.workflows).filter((e) => e.endsWith(".yml") || e.endsWith(".yaml"));
|
|
8865
9060
|
for (let e of n) {
|
|
@@ -8867,13 +9062,13 @@ function us(e) {
|
|
|
8867
9062
|
console.log(` ✅ ${e} -> Valid! (${n.length} job matrix variant(s) generated)`);
|
|
8868
9063
|
}
|
|
8869
9064
|
}
|
|
8870
|
-
async function
|
|
9065
|
+
async function Cs() {
|
|
8871
9066
|
let { config: e, command: t } = await fe();
|
|
8872
|
-
if (e || process.exit(1), t === "validate") return
|
|
9067
|
+
if (e || process.exit(1), t === "validate") return Ss(e);
|
|
8873
9068
|
let n = new mi("./.env"), r = new pi(process.env.WORKER_NAME || "cli");
|
|
8874
9069
|
switch (t) {
|
|
8875
9070
|
case "start-server":
|
|
8876
|
-
console.log("🌐 Starting Ingress Gateway..."), await r.init(), await
|
|
9071
|
+
console.log("🌐 Starting Ingress Gateway..."), await r.init(), await Jo.withPort({
|
|
8877
9072
|
config: e,
|
|
8878
9073
|
queue: r,
|
|
8879
9074
|
secrets: n,
|
|
@@ -8882,11 +9077,11 @@ async function ds() {
|
|
|
8882
9077
|
});
|
|
8883
9078
|
break;
|
|
8884
9079
|
case "start-workers":
|
|
8885
|
-
console.log(`⚙️ Starting ${e.workers}
|
|
9080
|
+
console.log(`⚙️ Starting worker scheduler with ${e.workers} concurrent slot(s)...`), await r.init(), ss(e.workers, r, n, e);
|
|
8886
9081
|
break;
|
|
8887
9082
|
default: console.error(`❌ Unknown command: '${t}'`), de(), process.exit(1);
|
|
8888
9083
|
}
|
|
8889
9084
|
}
|
|
8890
|
-
|
|
9085
|
+
Cs().catch(console.error);
|
|
8891
9086
|
//#endregion
|
|
8892
|
-
export {
|
|
9087
|
+
export { xs as GitHubStatusPlugin };
|