@cloud-cli/on 1.11.0 โ†’ 1.13.0

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 CHANGED
@@ -223,6 +223,8 @@ The GitHub status plugin publishes commit states when a workflow starts and fini
223
223
 
224
224
  Run details are available as HTML at `/runs/:id` and as sanitized JSON at `/api/runs/:id`. The HTML view refreshes reactively through job-specific SSE events while a run is active. Both representations omit raw webhook bodies, sensitive input fields, execution environment values, and internal rerun state.
225
225
 
226
+ Open `/workflows` to list workflows and manage secret values. Open `/workflows/new` to create a definition or `/workflows/:id` to edit one. These pages are protected by the administrator HTTP Basic credential; secret values are write-only.
227
+
226
228
  ---
227
229
 
228
230
  ## ๐Ÿ“ Deterministic Evaluation Rules
@@ -301,7 +303,9 @@ The Ingress Gateway listens for incoming HTTP requests and serves the live web U
301
303
 
302
304
  Install the unit files from `systemd/` and create root-owned environment files in `/etc/on/`: `server.env` for `RUNNER_DATABASE_URL`, `RUNNER_SERVER_URL`, `RUNNER_ADMIN_SECRET`, and `RUNNER_WORKER_SECRET`; `scheduler.env` for `RUNNER_DATABASE_URL`; and `worker.env` for `RUNNER_DATABASE_URL`, `RUNNER_SERVER_URL`, `RUNNER_WORKER_SECRET`, `RUNNER_TAGS`, and `RUNNER_TMP`. Workers receive only `RUNNER_WORKER_SECRET`; the admin secret stays on the HTTP server. The supplied units run as root because the systemd execution driver creates transient system services. Use `systemctl edit` for per-machine overrides.
303
305
 
304
- Place the server master key in `/etc/on/credentials/on-master-key` with permissions readable only by the `on` user. `runner-server.service` exposes it privately through systemd's credentials directory. Start the primary control plane with `systemctl enable --now runner.target`; enable `runner-worker.service` separately on worker machines. Use `systemctl edit runner-worker.service` for machine-specific labels and paths.
306
+ Place the server master key in `/etc/on/credentials/on-master-key` with root-only permissions. `runner-server.service` exposes it privately through systemd's credentials directory. Start the primary control plane with `systemctl enable --now runner.target`; enable `runner-worker.service` separately on worker machines. Use `systemctl edit runner-worker.service` for machine-specific labels and paths.
307
+
308
+ For package-managed remote workers, enable `runner-worker-update.timer`. It runs daily at 04:00 with up to 45 minutes of per-machine jitter, installs `@cloud-cli/on@latest` into `/opt/on`, then restarts the worker only when installation succeeds. `runner-worker.service` allows up to one hour for running jobs to drain during that restart. Do not enable this timer on a source-managed development worker; deploy that worker from its checked-out build instead.
305
309
 
306
310
  Workers now resolve the immutable revision when they claim a job. A workflow can safely use a checkout step followed by `if: files.exists('Dockerfile')`; `files` is constrained to the job's `workingDir`, so it cannot inspect files outside that workspace.
307
311
 
package/dist/on.js CHANGED
@@ -140,12 +140,16 @@ var se = class {
140
140
  await S.run("UPDATE jobs SET status = ?, finished_at = CURRENT_TIMESTAMP WHERE id = ?;", [t, e]);
141
141
  }
142
142
  async completeJob(e, t, n) {
143
- await S.run("UPDATE jobs\n SET status = ?, report = ?, updated_at = CURRENT_TIMESTAMP, finished_at = CURRENT_TIMESTAMP\n WHERE id = ?;", [
143
+ await S.run("UPDATE jobs\n SET status = CASE WHEN status = 'cancelled' THEN 'cancelled' ELSE ? END,\n report = ?, updated_at = CURRENT_TIMESTAMP, finished_at = CURRENT_TIMESTAMP\n WHERE id = ?;", [
144
144
  t,
145
145
  JSON.stringify(n),
146
146
  e
147
147
  ]);
148
148
  }
149
+ async cancelJob(e) {
150
+ let t = await this.getJob(e);
151
+ return t ? t.status !== "pending" && t.status !== "running" ? "not_active" : (await S.run("UPDATE jobs SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP, finished_at = CURRENT_TIMESTAMP\n WHERE id = ? AND status IN ('pending', 'running');", [e]), (await this.getJob(e))?.status === "cancelled" ? "cancelled" : "not_active") : "not_found";
152
+ }
149
153
  async restartJob(e) {
150
154
  let t = await this.getJob(e);
151
155
  if (t) return t.status === "running" && await S.run("UPDATE jobs SET status = 'cancelled', finished_at = CURRENT_TIMESTAMP WHERE id = ?;", [e]), (await S.get("INSERT INTO jobs (parentId, workflow_id, workflow_revision, required_tags, concurrency_key, payload) SELECT id, workflow_id, workflow_revision, required_tags, concurrency_key, payload FROM jobs WHERE id = ? RETURNING *", [e])).id;
@@ -3651,7 +3655,7 @@ var En = class {
3651
3655
  inputs: e
3652
3656
  };
3653
3657
  }
3654
- }, Dn = "<!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 &amp; 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";
3658
+ }, Dn = "<!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 &amp; Execution Traces</p>\n </div>\n <div class=\"flex items-center gap-3 text-xs font-mono\">\n <a href=\"/workflows\" class=\"text-indigo-400 hover:text-indigo-300 hover:underline\">Manage workflows</a>\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";
3655
3659
  //#endregion
3656
3660
  //#region src/html-state.ts
3657
3661
  function On(e) {
@@ -3732,7 +3736,7 @@ async function Mn(e, t, n, r) {
3732
3736
  }
3733
3737
  //#endregion
3734
3738
  //#region src/run.html?raw
3735
- var Nn = "<!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", Pn = /(?:^|[_-])auth(?:entication)?(?:$|[_-])|access[_-]?key|api[_-]?key|authorization|cookie|credential|passphrase|password|private[_-]?key|secret|session(?:id)?|signing[_-]?key|token/i;
3739
+ var Nn = "<!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 <template if=\"report.status === 'running'\">\n <button\n class=\"text-sm text-rose-300 px-4 py-1.5 border border-rose-500/50 hover:border-rose-400 rounded-full disabled:opacity-50\"\n on-click=\"cancelJob()\"\n attr-disabled=\"cancelling\"\n >\n {{ cancelling ? 'Cancelling...' : 'Cancel' }}\n </button>\n </template>\n <template if=\"report.status !== 'running'\">\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 </template>\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 const cancelling = ref(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 cancelJob = async () => {\n if (cancelling.value || report.value.status !== 'running') return;\n cancelling.value = true;\n try {\n const response = await fetch(`/api/jobs/${report.value.jobId}/cancel`, { method: 'POST' });\n if (!response.ok) throw new Error(`Cancel failed: ${response.status}`);\n await refreshRun();\n } catch (error) {\n console.error(error);\n } finally {\n cancelling.value = false;\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, cancelJob, cancelling };\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", Pn = /(?:^|[_-])auth(?:entication)?(?:$|[_-])|access[_-]?key|api[_-]?key|authorization|cookie|credential|passphrase|password|private[_-]?key|secret|session(?:id)?|signing[_-]?key|token/i;
3736
3740
  function Fn(e, t, n, r = []) {
3737
3741
  let i = JSON.parse(e.payload), a = e.report ? JSON.parse(e.report) : Ln(e, i, r), o = e.status;
3738
3742
  return {
@@ -8244,11 +8248,23 @@ var Ho = class {
8244
8248
  async names() {
8245
8249
  return (await S.all("SELECT name FROM secrets ORDER BY name")).map((e) => e.name);
8246
8250
  }
8251
+ async delete(e) {
8252
+ let t = await S.run("DELETE FROM secrets WHERE name = ?", [e]);
8253
+ return Number(t?.changes || 0) > 0;
8254
+ }
8247
8255
  decrypt(e) {
8248
8256
  let t = Buffer.from(e, "base64"), n = l.createDecipheriv("aes-256-gcm", this.encryptionKey(), t.subarray(0, 12));
8249
8257
  return n.setAuthTag(t.subarray(12, 28)), Buffer.concat([n.update(t.subarray(28)), n.final()]).toString("utf8");
8250
8258
  }
8251
- }, Uo = 50, Wo = 500, Go = class e {
8259
+ }, Uo = "<!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>Runner Workflow Management</title>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n <style>\n body[data-page=\"list\"] .editor-panel { display: none; }\n body[data-page=\"editor\"] .workflow-list, body[data-page=\"editor\"] .secrets-panel { display: none; }\n body[data-page=\"list\"] #workflow-layout { grid-template-columns: minmax(0, 1fr); }\n </style>\n</head>\n<body data-page=\"__WORKFLOW_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <main class=\"mx-auto max-w-7xl space-y-5\">\n <header class=\"flex flex-col justify-between gap-3 border-b border-gray-800 pb-4 sm:flex-row sm:items-end\">\n <div>\n <a href=\"/runs\" class=\"text-xs text-indigo-400 hover:underline\">Back to dashboard</a>\n <h1 class=\"mt-1 text-2xl font-bold text-white\">Workflow control room</h1>\n <p class=\"text-sm text-gray-400\">Draft, validate, and publish revisioned YAML definitions.</p>\n </div>\n <a href=\"/workflows/new\" class=\"rounded-lg border border-indigo-500/50 bg-indigo-500/10 px-4 py-2 text-sm font-medium text-indigo-200 hover:bg-indigo-500/20\">New workflow</a>\n </header>\n\n <p id=\"notice\" role=\"status\" class=\"hidden rounded-lg border px-3 py-2 text-sm\"></p>\n\n <section id=\"workflow-layout\" class=\"grid gap-5 lg:grid-cols-[17rem_1fr]\">\n <aside class=\"workflow-list rounded-xl border border-gray-800 bg-gray-900 p-3\">\n <h2 class=\"mb-2 px-1 text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflows</h2>\n <div id=\"workflow-list\" class=\"space-y-1\"></div>\n </aside>\n\n <section class=\"editor-panel rounded-xl border border-gray-800 bg-gray-900 p-4\">\n <div class=\"mb-3 flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between\">\n <label class=\"block flex-1 text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflow ID\n <input id=\"workflow-id\" class=\"mt-1 w-full rounded border border-gray-700 bg-gray-950 px-3 py-2 font-mono text-sm text-white outline-none focus:border-indigo-500\" placeholder=\"derived-from-workflow-name\">\n </label>\n <div class=\"flex flex-wrap gap-2\">\n <button id=\"validate\" class=\"rounded border border-gray-700 px-3 py-2 text-sm hover:border-gray-500\">Validate</button>\n <button id=\"save\" class=\"rounded border border-indigo-500 bg-indigo-500 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-400\">Save draft</button>\n <button id=\"publish\" class=\"rounded border border-emerald-500/60 px-3 py-2 text-sm text-emerald-300 hover:bg-emerald-500/10\">Publish</button>\n <button id=\"delete\" class=\"rounded border border-rose-500/60 px-3 py-2 text-sm text-rose-300 hover:bg-rose-500/10\">Delete</button>\n </div>\n </div>\n <textarea id=\"source-yaml\" spellcheck=\"false\" class=\"h-[34rem] w-full rounded-lg border border-gray-700 bg-gray-950 p-4 font-mono text-sm leading-6 text-gray-100 outline-none focus:border-indigo-500\" placeholder=\"name: Example&#10;on:&#10; github:&#10; events: [push]&#10;steps:&#10; - run: echo hello\"></textarea>\n </section>\n </section>\n\n <section class=\"secrets-panel rounded-xl border border-gray-800 bg-gray-900 p-4\">\n <div class=\"mb-3\">\n <h2 class=\"text-base font-semibold text-white\">Secrets</h2>\n <p class=\"text-sm text-gray-400\">Values are write-only and encrypted. Re-saving a name replaces its value.</p>\n </div>\n <div class=\"grid gap-3 md:grid-cols-[14rem_1fr_auto_auto]\">\n <input id=\"secret-name\" class=\"rounded border border-gray-700 bg-gray-950 px-3 py-2 font-mono text-sm uppercase outline-none focus:border-indigo-500\" placeholder=\"NPM_TOKEN\">\n <input id=\"secret-value\" type=\"password\" class=\"rounded border border-gray-700 bg-gray-950 px-3 py-2 font-mono text-sm outline-none focus:border-indigo-500\" placeholder=\"New secret value\">\n <button id=\"save-secret\" class=\"rounded border border-indigo-500 px-3 py-2 text-sm text-indigo-200 hover:bg-indigo-500/10\">Save value</button>\n <button id=\"delete-secret\" class=\"rounded border border-rose-500/60 px-3 py-2 text-sm text-rose-300 hover:bg-rose-500/10\">Delete</button>\n </div>\n <div id=\"secret-list\" class=\"mt-4 flex flex-wrap gap-2\"></div>\n </section>\n </main>\n\n <script>\n const notice = document.querySelector('#notice');\n const workflowList = document.querySelector('#workflow-list');\n const source = document.querySelector('#source-yaml');\n const workflowId = document.querySelector('#workflow-id');\n const secretList = document.querySelector('#secret-list');\n const secretName = document.querySelector('#secret-name');\n const secretValue = document.querySelector('#secret-value');\n const page = document.body.dataset.page;\n const initialId = __WORKFLOW_ID__;\n let workflows = [];\n let selectedId = '';\n\n const showNotice = (message, error = false) => {\n notice.textContent = message;\n notice.className = `rounded-lg border px-3 py-2 text-sm ${error ? 'border-rose-500/40 bg-rose-500/10 text-rose-200' : 'border-emerald-500/40 bg-emerald-500/10 text-emerald-200'}`;\n };\n const api = async (url, options = {}) => {\n const response = await fetch(url, { headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) }, ...options });\n const body = response.status === 204 ? null : await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`);\n return body;\n };\n const derivedId = () => source.value.match(/^\\s*(?:id:\\s*([^\\n]+)|name:\\s*([^\\n]+))/m)?.slice(1).find(Boolean)?.trim().toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/(^-|-$)/g, '') || '';\n const renderWorkflows = () => {\n workflowList.replaceChildren();\n for (const workflow of workflows) {\n const button = document.createElement('button');\n button.className = `block w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-800 ${workflow.id === selectedId ? 'bg-indigo-500/15 text-indigo-200' : 'text-gray-300'}`;\n button.textContent = `${workflow.name} ยท ${workflow.status}`;\n button.onclick = () => { window.location.href = `/workflows/${workflow.id}`; };\n workflowList.append(button);\n }\n };\n const loadWorkflows = async () => {\n workflows = (await api('/api/workflows')).workflows;\n renderWorkflows();\n };\n const openWorkflow = async (id) => {\n const workflow = await api(`/api/workflows/${id}`);\n selectedId = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n renderWorkflows();\n };\n const loadSecrets = async () => {\n const secrets = (await api('/api/secrets')).secrets;\n secretList.replaceChildren();\n for (const name of secrets) {\n const button = document.createElement('button');\n button.className = 'rounded border border-gray-700 px-2 py-1 font-mono text-xs text-gray-300 hover:border-indigo-500';\n button.textContent = name;\n button.onclick = () => { secretName.value = name; secretValue.focus(); };\n secretList.append(button);\n }\n };\n document.querySelector('#validate').onclick = async () => {\n try { const result = await api('/api/workflows/validate', { method: 'POST', body: JSON.stringify({ sourceYaml: source.value }) }); showNotice(`Valid: ${result.workflows.length} workflow definition(s).`); } catch (error) { showNotice(error.message, true); }\n };\n document.querySelector('#save').onclick = async () => {\n const id = workflowId.value.trim() || derivedId();\n if (!id) return showNotice('Set an ID or add a workflow name first.', true);\n try { const workflow = await api(`/api/workflows/${id}`, { method: 'PUT', body: JSON.stringify({ sourceYaml: source.value }) }); selectedId = workflow.id; workflowId.value = workflow.id; showNotice(`Saved ${workflow.id} as draft revision ${workflow.revision}.`); if (!initialId) history.replaceState(null, '', `/workflows/${workflow.id}`); } catch (error) { showNotice(error.message, true); }\n };\n document.querySelector('#publish').onclick = async () => {\n const id = workflowId.value.trim() || selectedId;\n if (!id) return showNotice('Save a workflow before publishing it.', true);\n try { const workflow = await api(`/api/workflows/${id}/publish`, { method: 'POST' }); selectedId = workflow.id; showNotice(`Published ${workflow.id} revision ${workflow.revision}.`); } catch (error) { showNotice(error.message, true); }\n };\n document.querySelector('#delete').onclick = async () => {\n const id = workflowId.value.trim() || selectedId;\n if (!id || !confirm(`Delete ${id} and all of its revisions?`)) return;\n try { await api(`/api/workflows/${id}`, { method: 'DELETE' }); window.location.href = '/workflows'; } catch (error) { showNotice(error.message, true); }\n };\n document.querySelector('#save-secret').onclick = async () => {\n try { await api(`/api/secrets/${secretName.value.trim()}`, { method: 'PUT', body: JSON.stringify({ value: secretValue.value }) }); secretValue.value = ''; await loadSecrets(); showNotice(`Saved ${secretName.value.trim()}.`); } catch (error) { showNotice(error.message, true); }\n };\n document.querySelector('#delete-secret').onclick = async () => {\n const name = secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n try { await api(`/api/secrets/${name}`, { method: 'DELETE' }); secretName.value = ''; secretValue.value = ''; await loadSecrets(); showNotice(`Deleted ${name}.`); } catch (error) { showNotice(error.message, true); }\n };\n const loadPage = page === 'editor'\n ? (initialId ? openWorkflow(initialId) : Promise.resolve())\n : Promise.all([loadWorkflows(), loadSecrets()]);\n loadPage.catch((error) => showNotice(error.message, true));\n <\/script>\n</body>\n</html>\n";
8260
+ //#endregion
8261
+ //#region src/workflows-ui.ts
8262
+ function Wo(e, t = "") {
8263
+ return Uo.replace("__WORKFLOW_PAGE__", e).replace("__WORKFLOW_ID__", JSON.stringify(t));
8264
+ }
8265
+ //#endregion
8266
+ //#region src/server.ts
8267
+ var Go = 50, Ko = 500, qo = class e {
8252
8268
  server;
8253
8269
  preprocessors = /* @__PURE__ */ new Map();
8254
8270
  workflows = new Bo();
@@ -8272,21 +8288,37 @@ var Ho = class {
8272
8288
  async handleRequest(e, t) {
8273
8289
  let n = new u(e.url || "/", `${e.headers["x-forwarded-proto"] || "http"}://${e.headers["x-forwarded-host"] || e.headers.host}`);
8274
8290
  if (e.method === "GET" && (n.pathname === "/runs" || n.pathname === "/")) return this.renderDashboard(t);
8291
+ if (e.method === "GET" && n.pathname === "/workflows") return this.requireAdmin(e, t) ? (t.writeHead(200, {
8292
+ "Content-Type": "text/html; charset=utf-8",
8293
+ "Cache-Control": "no-store"
8294
+ }), t.end(Wo("list"))) : void 0;
8295
+ let r = n.pathname.match(/^\/workflows\/(new|[a-z0-9-]+)$/);
8296
+ if (e.method === "GET" && r) {
8297
+ if (!this.requireAdmin(e, t)) return;
8298
+ let n = r[1] === "new" ? "" : r[1];
8299
+ return t.writeHead(200, {
8300
+ "Content-Type": "text/html; charset=utf-8",
8301
+ "Cache-Control": "no-store"
8302
+ }), t.end(Wo("editor", n));
8303
+ }
8275
8304
  if (e.method === "GET" && n.pathname === "/api/jobs") {
8276
- 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 ? Uo : Number(i);
8277
- 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 > Wo ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: `limit must be an integer from 1 to ${Wo}` }))) : this.renderDashboardJobs(t, s, a, o);
8305
+ 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 ? Go : Number(i);
8306
+ 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 > Ko ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: `limit must be an integer from 1 to ${Ko}` }))) : this.renderDashboardJobs(t, s, a, o);
8278
8307
  }
8279
8308
  if (e.method === "GET" && n.pathname === "/api/events") return this.events.subscribe(e, t);
8280
8309
  if (n.pathname === "/api/workflows/validate" && e.method === "POST") return this.handleWorkflowValidation(e, t);
8281
8310
  if (n.pathname === "/api/secrets" && e.method === "GET") return this.handleSecretList(e, t);
8282
- let r = n.pathname.match(/^\/api\/secrets\/([A-Z][A-Z0-9_]*)$/);
8283
- if (r && e.method === "PUT") return this.handleSecretSave(e, t, r[1]);
8284
- let i = n.pathname.match(/^\/api\/jobs\/(\d+)\/secrets$/);
8285
- if (i && e.method === "GET") return this.handleJobSecrets(e, t, i[1]);
8311
+ let i = n.pathname.match(/^\/api\/secrets\/([A-Z][A-Z0-9_]*)$/);
8312
+ if (i && e.method === "PUT") return this.handleSecretSave(e, t, i[1]);
8313
+ if (i && e.method === "DELETE") return this.handleSecretDelete(e, t, i[1]);
8314
+ let a = n.pathname.match(/^\/api\/jobs\/(\d+)\/secrets$/);
8315
+ if (a && e.method === "GET") return this.handleJobSecrets(e, t, a[1]);
8316
+ let o = n.pathname.match(/^\/api\/jobs\/(\d+)\/cancel$/);
8317
+ if (o && e.method === "POST") return this.handleCancelJob(e, t, o[1]);
8286
8318
  if (n.pathname === "/api/workflows" && e.method === "GET") return this.handleWorkflowList(e, t);
8287
- let a = n.pathname.match(/^\/api\/workflows\/([a-z0-9-]+)(\/publish)?$/);
8288
- if (a) {
8289
- let [, n, r] = a;
8319
+ let s = n.pathname.match(/^\/api\/workflows\/([a-z0-9-]+)(\/publish)?$/);
8320
+ if (s) {
8321
+ let [, n, r] = s;
8290
8322
  if (e.method === "GET" && !r) return this.handleWorkflowGet(e, t, n);
8291
8323
  if (e.method === "PUT" && !r) return this.handleWorkflowSave(e, t, n);
8292
8324
  if (e.method === "DELETE" && !r) return this.handleWorkflowDelete(e, t, n);
@@ -8498,6 +8530,12 @@ var Ho = class {
8498
8530
  t.writeHead(422, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: e.message }));
8499
8531
  }
8500
8532
  }
8533
+ async handleSecretDelete(e, t, n) {
8534
+ if (this.requireAdmin(e, t)) {
8535
+ if (!await this.secretRepository.delete(n)) return t.writeHead(404, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Secret not found" }));
8536
+ t.writeHead(204).end();
8537
+ }
8538
+ }
8501
8539
  async handleJobSecrets(e, t, n) {
8502
8540
  if (!this.isWorker(e)) return t.writeHead(403, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Unauthorized" }));
8503
8541
  let r = await this.queue.getJob(n);
@@ -8525,7 +8563,7 @@ var Ho = class {
8525
8563
  this.events.publish("jobs.changed", r ? { jobId: r } : {}), t.writeHead(202).end();
8526
8564
  }
8527
8565
  async renderDashboard(e) {
8528
- let t = await this.queue.listJobs(51), n = kn(t.slice(0, Uo)), r = await this.redactText(An(n, t.length > Uo));
8566
+ let t = await this.queue.listJobs(51), n = kn(t.slice(0, Go)), r = await this.redactText(An(n, t.length > Go));
8529
8567
  e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(r);
8530
8568
  }
8531
8569
  async renderDashboardJobs(e, t, n, r) {
@@ -8560,6 +8598,16 @@ var Ho = class {
8560
8598
  }
8561
8599
  n.writeHead(404, { "Content-Type": "application/json" }), n.end(JSON.stringify({ error: "Job not found" }));
8562
8600
  }
8601
+ async handleCancelJob(e, t, n) {
8602
+ if (!this.requireAdmin(e, t)) return;
8603
+ let r = await this.queue.cancelJob(n);
8604
+ if (r === "not_found") return t.writeHead(404, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Job not found" }));
8605
+ if (r === "not_active") return t.writeHead(409, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Job is no longer running" }));
8606
+ this.events.publish("jobs.changed", { jobId: Number(n) }), t.writeHead(200, { "Content-Type": "application/json" }), t.end(JSON.stringify({
8607
+ id: Number(n),
8608
+ status: "cancelled"
8609
+ }));
8610
+ }
8563
8611
  async currentSecrets() {
8564
8612
  try {
8565
8613
  return await this.secretRepository.getAll();
@@ -8589,7 +8637,7 @@ var Ho = class {
8589
8637
  });
8590
8638
  });
8591
8639
  }
8592
- }, Ko = s(d), qo = class {
8640
+ }, Jo = s(d), Yo = class {
8593
8641
  name = "systemd";
8594
8642
  async isSupported() {
8595
8643
  try {
@@ -8712,7 +8760,7 @@ var Ho = class {
8712
8760
  cancel: async () => {
8713
8761
  d = !0;
8714
8762
  try {
8715
- t.image && await Ko(`docker stop -t 2 ${o}`).catch(() => {}), await Ko(`systemctl stop ${o}.service`).catch(() => {});
8763
+ t.image && await Jo(`docker stop -t 2 ${o}`).catch(() => {}), await Jo(`systemctl stop ${o}.service`).catch(() => {});
8716
8764
  } catch {}
8717
8765
  },
8718
8766
  logFilePath: a
@@ -8722,7 +8770,7 @@ var Ho = class {
8722
8770
  let t = await p(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;
8723
8771
  return t.slice(n, r);
8724
8772
  }
8725
- }, Jo = class {
8773
+ }, Xo = class {
8726
8774
  name = "standard-process";
8727
8775
  async isSupported() {
8728
8776
  return !0;
@@ -8837,13 +8885,13 @@ var Ho = class {
8837
8885
  };
8838
8886
  //#endregion
8839
8887
  //#region src/drivers/index.ts
8840
- async function Yo() {
8841
- let e = new qo();
8842
- return await e.isSupported() ? (console.log("โšก Selected Execution Driver: Systemd (cgroups enabled)"), e) : (console.log("๐Ÿ“ฆ Selected Execution Driver: Standard Process (Fallback)"), new Jo());
8888
+ async function Zo() {
8889
+ let e = new Yo();
8890
+ return await e.isSupported() ? (console.log("โšก Selected Execution Driver: Systemd (cgroups enabled)"), e) : (console.log("๐Ÿ“ฆ Selected Execution Driver: Standard Process (Fallback)"), new Xo());
8843
8891
  }
8844
8892
  //#endregion
8845
8893
  //#region src/signals.ts
8846
- async function Xo(e) {
8894
+ async function Qo(e) {
8847
8895
  let t = !1;
8848
8896
  async function n(n) {
8849
8897
  if (!t) {
@@ -8851,7 +8899,7 @@ async function Xo(e) {
8851
8899
  console.error("โš ๏ธ Graceful shutdown timed out after 10s. Forcing exit!"), process.exit(1);
8852
8900
  }, 1e4).unref();
8853
8901
  try {
8854
- rs(), 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);
8902
+ as(), 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);
8855
8903
  } catch (e) {
8856
8904
  console.error("โŒ Error during graceful shutdown:", e.message), process.exit(1);
8857
8905
  }
@@ -8861,7 +8909,7 @@ async function Xo(e) {
8861
8909
  }
8862
8910
  //#endregion
8863
8911
  //#region src/plugins/manager.ts
8864
- var Zo = class {
8912
+ var $o = class {
8865
8913
  plugins;
8866
8914
  constructor(e = []) {
8867
8915
  this.plugins = e;
@@ -8883,38 +8931,38 @@ var Zo = class {
8883
8931
  console.error(`[Plugin Error] ${n.name}.onWorkflowFinish:`, e);
8884
8932
  }
8885
8933
  }
8886
- }, Qo = !!process.env.DEBUG, $o = { isStopping: !1 }, es = /* @__PURE__ */ new Set(), ts = null, ns = null;
8887
- function rs() {
8888
- $o.isStopping = !0, ts?.abort(), ns?.();
8934
+ }, es = !!process.env.DEBUG, ts = { isStopping: !1 }, ns = /* @__PURE__ */ new Set(), rs = null, is = null;
8935
+ function as() {
8936
+ ts.isStopping = !0, rs?.abort(), is?.();
8889
8937
  }
8890
- function is(e, t, n, r) {
8891
- $o.isStopping = !1;
8892
- let i = [as(e, t, n, r)];
8893
- return Xo(i), i;
8938
+ function os(e, t, n, r) {
8939
+ ts.isStopping = !1;
8940
+ let i = [ss(e, t, n, r)];
8941
+ return Qo(i), i;
8894
8942
  }
8895
- async function as(e, t, n, r) {
8896
- let i = await Yo(), a = new Bo(), o = /* @__PURE__ */ new Set(), s = 0, c = null, l = () => {
8943
+ async function ss(e, t, n, r) {
8944
+ let i = await Zo(), a = new Bo(), o = /* @__PURE__ */ new Set(), s = 0, c = null, l = () => {
8897
8945
  s++, c?.();
8898
8946
  };
8899
- ns = l, ts = new AbortController();
8900
- let u = os(r, ts.signal, l);
8901
- for (console.log(`๐Ÿš€ Worker scheduler started. Driver: ${i.name}. Concurrency: ${e}. Tags: ${r.tags.join(", ") || "(none)"}`); !$o.isStopping;) {
8947
+ is = l, rs = new AbortController();
8948
+ let u = cs(r, rs.signal, l);
8949
+ for (console.log(`๐Ÿš€ Worker scheduler started. Driver: ${i.name}. Concurrency: ${e}. Tags: ${r.tags.join(", ") || "(none)"}`); !ts.isStopping;) {
8902
8950
  let n = s;
8903
8951
  try {
8904
- for (; o.size < e && !$o.isStopping;) {
8952
+ for (; o.size < e && !ts.isStopping;) {
8905
8953
  let e = await t.claimNextJob(r.tags);
8906
8954
  if (!e) break;
8907
- if ($o.isStopping) {
8955
+ if (ts.isStopping) {
8908
8956
  await t.releaseJob(e.id);
8909
8957
  break;
8910
8958
  }
8911
8959
  let n = process.env.WORKER_NAME || "cli", s;
8912
8960
  s = (async () => {
8913
- ls(r, e.id);
8961
+ ds(r, e.id);
8914
8962
  let o = await a.getRevision(e.workflow_id, e.workflow_revision);
8915
8963
  if (!o) throw await t.finishJob(e.id, "failed"), Error(`Workflow ${e.workflow_id} revision ${e.workflow_revision} was not found`);
8916
8964
  let s = new ce();
8917
- s.replace(await us(r, n, e.id)), await ds({
8965
+ s.replace(await fs(r, n, e.id)), await ps({
8918
8966
  workerId: n,
8919
8967
  job: e,
8920
8968
  queue: t,
@@ -8924,17 +8972,17 @@ async function as(e, t, n, r) {
8924
8972
  workflow: o
8925
8973
  });
8926
8974
  })().catch((e) => console.error(`[${n}] โš ๏ธ Worker execution error:`, e)).finally(() => {
8927
- o.delete(s), ls(r, e.id), l();
8975
+ o.delete(s), ds(r, e.id), l();
8928
8976
  }), o.add(s);
8929
8977
  }
8930
8978
  } catch (e) {
8931
8979
  console.error("โš ๏ธ Worker scheduler claim error:", e), setTimeout(l, 5e3).unref();
8932
8980
  }
8933
- !$o.isStopping && s === n && (await ss(n, () => s, (e) => c = e), c = null);
8981
+ !ts.isStopping && s === n && (await ls(n, () => s, (e) => c = e), c = null);
8934
8982
  }
8935
- ts.abort(), await Promise.allSettled(o), await u, ns = null, console.log("๐Ÿ›‘ Worker scheduler stopped cleanly.");
8983
+ rs.abort(), await Promise.allSettled(o), await u, is = null, console.log("๐Ÿ›‘ Worker scheduler stopped cleanly.");
8936
8984
  }
8937
- async function os(e, t, n) {
8985
+ async function cs(e, t, n) {
8938
8986
  let r = 1e3;
8939
8987
  for (; !t.aborted;) {
8940
8988
  try {
@@ -8945,10 +8993,10 @@ async function os(e, t, n) {
8945
8993
  if (t.aborted || e?.name === "AbortError") break;
8946
8994
  console.error(`โš ๏ธ Worker event stream disconnected: ${e.message}`);
8947
8995
  }
8948
- await cs(r, t), r = Math.min(r * 2, 3e4);
8996
+ await us(r, t), r = Math.min(r * 2, 3e4);
8949
8997
  }
8950
8998
  }
8951
- function ss(e, t, n) {
8999
+ function ls(e, t, n) {
8952
9000
  return new Promise((r) => {
8953
9001
  if (t() !== e) return r();
8954
9002
  let i = setTimeout(r, 6e4);
@@ -8957,7 +9005,7 @@ function ss(e, t, n) {
8957
9005
  });
8958
9006
  });
8959
9007
  }
8960
- function cs(e, t) {
9008
+ function us(e, t) {
8961
9009
  return new Promise((n) => {
8962
9010
  if (t.aborted) return n();
8963
9011
  let r = () => {
@@ -8968,7 +9016,7 @@ function cs(e, t) {
8968
9016
  t.addEventListener("abort", r, { once: !0 });
8969
9017
  });
8970
9018
  }
8971
- async function ls(e, t) {
9019
+ async function ds(e, t) {
8972
9020
  if (e.workerToken) try {
8973
9021
  let n = await fetch(new URL("/api/events", e.serverUrl), {
8974
9022
  method: "POST",
@@ -8979,12 +9027,12 @@ async function ls(e, t) {
8979
9027
  body: JSON.stringify({ jobId: t }),
8980
9028
  signal: AbortSignal.timeout(5e3)
8981
9029
  });
8982
- !n.ok && Qo && console.error(`Failed to publish job status event: HTTP ${n.status}`);
9030
+ !n.ok && es && console.error(`Failed to publish job status event: HTTP ${n.status}`);
8983
9031
  } catch (e) {
8984
- Qo && console.error("Failed to publish job status event:", e);
9032
+ es && console.error("Failed to publish job status event:", e);
8985
9033
  }
8986
9034
  }
8987
- async function us(e, t, n) {
9035
+ async function fs(e, t, n) {
8988
9036
  if (!e.workerToken) return {};
8989
9037
  try {
8990
9038
  let r = await fetch(new URL(`/api/jobs/${n}/secrets`, e.serverUrl), {
@@ -9000,7 +9048,7 @@ async function us(e, t, n) {
9000
9048
  throw console.error(`Unable to retrieve secrets for job ${n}:`, e), e;
9001
9049
  }
9002
9050
  }
9003
- async function ds(e) {
9051
+ async function ps(e) {
9004
9052
  let { workerId: t, job: n, config: r, secrets: a, queue: o } = e;
9005
9053
  console.log(`\n[${t}] ๐Ÿ“ฆ Claimed Job #${n.id} (Workflow: ${n.workflow_id})`);
9006
9054
  let s = typeof n.payload == "string" ? JSON.parse(n.payload) : n.payload;
@@ -9020,27 +9068,27 @@ async function ds(e) {
9020
9068
  durationMs: 0,
9021
9069
  outputs: {},
9022
9070
  logContent: ""
9023
- })), g = vs(n, "running", u, l, m.env, h, s);
9024
- await o.saveReport(n.id, g), ls(r, n.id);
9025
- let _ = new Zo(r.plugins), v = {
9071
+ })), g = bs(n, "running", u, l, m.env, h, s);
9072
+ await o.saveReport(n.id, g), ds(r, n.id);
9073
+ let _ = new $o(r.plugins), v = {
9026
9074
  jobId: String(n.id),
9027
9075
  workflowName: n.workflow_id,
9028
9076
  inputs: l,
9029
9077
  runUrl: new URL(`/runs/${n.id}`, r.serverUrl).toString()
9030
9078
  };
9031
9079
  await _.triggerWorkflowStart(v);
9032
- let { cancelled: y, failed: ee } = await fs({
9080
+ let { cancelled: y, failed: ee } = await ms({
9033
9081
  payload: s,
9034
9082
  steps: c,
9035
9083
  executionContext: m,
9036
9084
  ...e
9037
9085
  }, g), b = y ? "cancelled" : ee ? "failed" : "success";
9038
- g.status = b, g.durationMs = Date.now() - u, g.finishedAt = (/* @__PURE__ */ new Date()).toISOString(), await o.completeJob(n.id, b, g), ls(r, n.id), console.log(`[${t}] โœ… Job #${n.id} completed as: ${b}`), await _.triggerWorkflowFinish(v, b);
9086
+ g.status = b, g.durationMs = Date.now() - u, g.finishedAt = (/* @__PURE__ */ new Date()).toISOString(), await o.completeJob(n.id, b, g), ds(r, n.id), console.log(`[${t}] โœ… Job #${n.id} completed as: ${b}`), await _.triggerWorkflowFinish(v, b);
9039
9087
  }
9040
- async function fs(e, t) {
9088
+ async function ms(e, t) {
9041
9089
  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;
9042
9090
  try {
9043
- e.workflow?.env && Object.assign(l.env, await hs(e.workflow.env, l));
9091
+ e.workflow?.env && Object.assign(l.env, await _s(e.workflow.env, l));
9044
9092
  for (let e = 0; e < c.length; e++) {
9045
9093
  let s = c[e], m = (/* @__PURE__ */ new Date()).toISOString();
9046
9094
  u[e] = {
@@ -9053,8 +9101,8 @@ async function fs(e, t) {
9053
9101
  logContent: "",
9054
9102
  startedAt: m,
9055
9103
  finishedAt: void 0
9056
- }, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ls(a, o.id);
9057
- let h = await ps({
9104
+ }, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ds(a, o.id);
9105
+ let h = await hs({
9058
9106
  workerId: r,
9059
9107
  jobId: o.id,
9060
9108
  step: s,
@@ -9081,7 +9129,7 @@ async function fs(e, t) {
9081
9129
  exitCode: 1,
9082
9130
  error: "Step failed before execution started",
9083
9131
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
9084
- }, p = e + 1, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ls(a, o.id), h.skipped) break;
9132
+ }, p = e + 1, t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ds(a, o.id), h.skipped) break;
9085
9133
  if (h.cancelled) {
9086
9134
  f = !0, d = !0;
9087
9135
  break;
@@ -9094,22 +9142,22 @@ async function fs(e, t) {
9094
9142
  } catch (e) {
9095
9143
  console.log("๐Ÿ›‘ Step failed", e), d = !0;
9096
9144
  }
9097
- return p < c.length && (_s(c, p, u), t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ls(a, o.id)), {
9145
+ return p < c.length && (ys(c, p, u), t.durationMs = Date.now() - Date.parse(t.startedAt), await i.saveReport(o.id, t), ds(a, o.id)), {
9098
9146
  failed: d,
9099
9147
  cancelled: f,
9100
9148
  stepReports: u
9101
9149
  };
9102
9150
  }
9103
- async function ps(e) {
9151
+ async function hs(e) {
9104
9152
  let { step: t, stepIndex: n, executionContext: r } = e;
9105
- if (t.id ||= `step-${n}`, t.name ||= t.id, t.if && !await Cn.evaluateConditions(t.if, r)) return Qo && console.log(`โฉ Skipped step ${t.id} based on condition: ${t.if}`, r), {
9153
+ if (t.id ||= `step-${n}`, t.name ||= t.id, t.if && !await Cn.evaluateConditions(t.if, r)) return es && console.log(`โฉ Skipped step ${t.id} based on condition: ${t.if}`, r), {
9106
9154
  failed: !1,
9107
9155
  cancelled: !1,
9108
9156
  skipped: !0,
9109
9157
  report: null
9110
9158
  };
9111
9159
  try {
9112
- let n = await hs(t.env, r), i = {
9160
+ let n = await _s(t.env, r), i = {
9113
9161
  jobId: String(e.jobId),
9114
9162
  step: t,
9115
9163
  command: t.run,
@@ -9123,15 +9171,15 @@ async function ps(e) {
9123
9171
  WORKING_DIR: r.workingDir
9124
9172
  }
9125
9173
  };
9126
- return t.eval ? ms({
9174
+ return t.eval ? gs({
9127
9175
  ...e,
9128
9176
  stepContext: i
9129
- }) : gs({
9177
+ }) : vs({
9130
9178
  ...e,
9131
9179
  stepContext: i
9132
9180
  });
9133
9181
  } catch (e) {
9134
- return Qo && console.error(`โฉ Failed to run step ${t.id}`, r, e), {
9182
+ return es && console.error(`โฉ Failed to run step ${t.id}`, r, e), {
9135
9183
  failed: !0,
9136
9184
  cancelled: !1,
9137
9185
  skipped: !1,
@@ -9139,7 +9187,7 @@ async function ps(e) {
9139
9187
  };
9140
9188
  }
9141
9189
  }
9142
- async function ms(e) {
9190
+ async function gs(e) {
9143
9191
  let { queue: t, stepContext: n, executionContext: r } = e, { jobId: i, step: a } = n, o = Date.now(), s = a.id, c = a.name;
9144
9192
  try {
9145
9193
  let e = await Cn.evaluateExpression(a.eval, r);
@@ -9185,12 +9233,12 @@ async function ms(e) {
9185
9233
  };
9186
9234
  }
9187
9235
  }
9188
- async function hs(e, t) {
9236
+ async function _s(e, t) {
9189
9237
  let n = {};
9190
9238
  if (e) for (let [r, i] of Object.entries(e)) n[r] = String(await Cn.evaluateValue(i, t));
9191
9239
  return n;
9192
9240
  }
9193
- async function gs(t) {
9241
+ async function vs(t) {
9194
9242
  let { workerId: n, jobId: r, stepContext: i, executionContext: a, driver: o, queue: s } = t, { step: c } = i, l = c.id, u = c.name, d;
9195
9243
  try {
9196
9244
  d = o.execute(i);
@@ -9205,11 +9253,11 @@ async function gs(t) {
9205
9253
  logFilePath: ""
9206
9254
  };
9207
9255
  }
9208
- es.add(d);
9256
+ ns.add(d);
9209
9257
  let f = !1, p = setInterval(async () => {
9210
9258
  await s.isCancelled(r) && (console.log(`[${n}] ๐Ÿ›‘ Job #${r} was cancelled! Halting execution.`), f = !0, clearInterval(p), await d.cancel());
9211
9259
  }, 3e3), m = await d.done;
9212
- if (clearInterval(p), es.delete(d), d.logFilePath && e.existsSync(d.logFilePath)) try {
9260
+ if (clearInterval(p), ns.delete(d), d.logFilePath && e.existsSync(d.logFilePath)) try {
9213
9261
  let e = await o.readLog(d.logFilePath);
9214
9262
  await s.saveStepLog(r, l, e);
9215
9263
  } catch (e) {
@@ -9236,7 +9284,7 @@ async function gs(t) {
9236
9284
  }
9237
9285
  };
9238
9286
  }
9239
- function _s(e, t, n) {
9287
+ function ys(e, t, n) {
9240
9288
  for (let r = t; r < e.length; r++) {
9241
9289
  let t = e[r], i = t.id || `step-${r}`;
9242
9290
  n[r] = {
@@ -9250,7 +9298,7 @@ function _s(e, t, n) {
9250
9298
  };
9251
9299
  }
9252
9300
  }
9253
- function vs(e, t, n, r, i, a, o) {
9301
+ function bs(e, t, n, r, i, a, o) {
9254
9302
  return {
9255
9303
  jobId: String(e.id),
9256
9304
  parentId: String(e.parentId || ""),
@@ -9271,7 +9319,7 @@ function vs(e, t, n, r, i, a, o) {
9271
9319
  }
9272
9320
  //#endregion
9273
9321
  //#region src/scheduler.ts
9274
- function ys(e, t, n, r) {
9322
+ function xs(e, t, n, r) {
9275
9323
  return e.split(",").some((e) => {
9276
9324
  let [i, a] = e.split("/"), o = a ? Number(a) : 1;
9277
9325
  if (!Number.isInteger(o) || o < 1) return !1;
@@ -9279,7 +9327,7 @@ function ys(e, t, n, r) {
9279
9327
  return Number.isInteger(l) && Number.isInteger(u) && t >= l && t <= u && (t - l) % o === 0;
9280
9328
  });
9281
9329
  }
9282
- function bs(e, t, n = "UTC") {
9330
+ function Ss(e, t, n = "UTC") {
9283
9331
  let r = e.trim().split(/\s+/);
9284
9332
  if (r.length !== 5) return !1;
9285
9333
  let i = new Intl.DateTimeFormat("en-US", {
@@ -9301,16 +9349,16 @@ function bs(e, t, n = "UTC") {
9301
9349
  "Thu",
9302
9350
  "Fri",
9303
9351
  "Sat"
9304
- ].indexOf(i.weekday), o = ys(r[2], Number(i.day), 1, 31), s = ys(r[4].replace(/7/g, "0"), a, 0, 6);
9305
- return ys(r[0], Number(i.minute), 0, 59) && ys(r[1], Number(i.hour), 0, 23) && ys(r[3], Number(i.month), 1, 12) && (r[2] === "*" || r[4] === "*" ? o && s : o || s);
9352
+ ].indexOf(i.weekday), o = xs(r[2], Number(i.day), 1, 31), s = xs(r[4].replace(/7/g, "0"), a, 0, 6);
9353
+ return xs(r[0], Number(i.minute), 0, 59) && xs(r[1], Number(i.hour), 0, 23) && xs(r[3], Number(i.month), 1, 12) && (r[2] === "*" || r[4] === "*" ? o && s : o || s);
9306
9354
  }
9307
- function xs(e) {
9355
+ function Cs(e) {
9308
9356
  if (!e) return 0;
9309
9357
  let t = e.match(/^([+-]?)(\d+)(m|h)$/);
9310
9358
  if (!t) throw Error(`Invalid solar offset: ${e}`);
9311
9359
  return (t[1] === "-" ? -1 : 1) * Number(t[2]) * (t[3] === "h" ? 36e5 : 6e4);
9312
9360
  }
9313
- function Ss(e, t) {
9361
+ function ws(e, t) {
9314
9362
  let n = t.latitude, r = t.longitude, i = Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate()), a = Math.floor((i - Date.UTC(e.getUTCFullYear(), 0, 0)) / 864e5), o = r / 15, s = a + ((t.event === "sunrise" ? 6 : 18) - o) / 24, c = .9856 * s - 3.289, l = c + 1.916 * Math.sin(c * Math.PI / 180) + .02 * Math.sin(2 * c * Math.PI / 180) + 282.634;
9315
9363
  l = (l + 360) % 360;
9316
9364
  let u = Math.atan(.91764 * Math.tan(l * Math.PI / 180)) * 180 / Math.PI;
@@ -9318,9 +9366,9 @@ function Ss(e, t) {
9318
9366
  let d = .39782 * Math.sin(l * Math.PI / 180), f = Math.cos(Math.asin(d)), p = (Math.cos(90.833 * Math.PI / 180) - d * Math.sin(n * Math.PI / 180)) / (f * Math.cos(n * Math.PI / 180));
9319
9367
  if (p > 1 || p < -1) return null;
9320
9368
  let m = (t.event === "sunrise" ? 360 - Math.acos(p) * 180 / Math.PI : Math.acos(p) * 180 / Math.PI) / 15;
9321
- return m = (m + u - .06571 * s - 6.622 - o + 24) % 24, new Date(i + m * 36e5 + xs(t.offset));
9369
+ return m = (m + u - .06571 * s - 6.622 - o + 24) % 24, new Date(i + m * 36e5 + Cs(t.offset));
9322
9370
  }
9323
- var Cs = class {
9371
+ var Ts = class {
9324
9372
  queue;
9325
9373
  workflows;
9326
9374
  config;
@@ -9337,9 +9385,9 @@ var Cs = class {
9337
9385
  async tick(e = /* @__PURE__ */ new Date()) {
9338
9386
  let t = /* @__PURE__ */ new Date(Math.floor(e.getTime() / 6e4) * 6e4);
9339
9387
  for (let { definition: e, revision: n } of await this.workflows.published()) {
9340
- for (let [r, i] of (e.schedule || []).entries()) bs(i.cron, t, i.timezone || "UTC") && await this.enqueue(e, n, i, `cron-${r}`, t);
9388
+ for (let [r, i] of (e.schedule || []).entries()) Ss(i.cron, t, i.timezone || "UTC") && await this.enqueue(e, n, i, `cron-${r}`, t);
9341
9389
  for (let [r, i] of (e.solar || []).entries()) {
9342
- let a = Ss(t, i);
9390
+ let a = ws(t, i);
9343
9391
  a && Math.floor(a.getTime() / 6e4) === t.getTime() / 6e4 && await this.enqueue(e, n, i, `solar-${r}`, t);
9344
9392
  }
9345
9393
  }
@@ -9355,7 +9403,7 @@ var Cs = class {
9355
9403
  } } };
9356
9404
  await this.queue.enqueue(e.id, t, o, e.tags), console.log(`Scheduled ${e.id} via ${a}`);
9357
9405
  }
9358
- }, ws = class {
9406
+ }, Es = class {
9359
9407
  name = "github-commit-status";
9360
9408
  token;
9361
9409
  apiUrl;
@@ -9397,13 +9445,13 @@ var Cs = class {
9397
9445
  };
9398
9446
  //#endregion
9399
9447
  //#region src/index.ts
9400
- async function Ts() {
9448
+ async function Ds() {
9401
9449
  let { config: e, command: t } = await oe();
9402
9450
  e || process.exit(1);
9403
9451
  let n = new ce(), r = new se(process.env.WORKER_NAME || "cli");
9404
9452
  switch (t) {
9405
9453
  case "start-server":
9406
- console.log("๐ŸŒ Starting Ingress Gateway..."), await r.init(), await Go.withPort({
9454
+ console.log("๐ŸŒ Starting Ingress Gateway..."), await r.init(), await qo.withPort({
9407
9455
  config: e,
9408
9456
  queue: r,
9409
9457
  secrets: n,
@@ -9412,13 +9460,13 @@ async function Ts() {
9412
9460
  });
9413
9461
  break;
9414
9462
  case "start-workers":
9415
- console.log(`โš™๏ธ Starting worker scheduler with ${e.workers} concurrent slot(s)...`), await r.init(), is(e.workers, r, n, e);
9463
+ console.log(`โš™๏ธ Starting worker scheduler with ${e.workers} concurrent slot(s)...`), await r.init(), os(e.workers, r, n, e);
9416
9464
  break;
9417
9465
  case "start-scheduler": {
9418
9466
  console.log("Starting workflow scheduler..."), await r.init();
9419
9467
  let t = new Bo();
9420
9468
  await t.init();
9421
- let n = new Cs(r, t, e);
9469
+ let n = new Ts(r, t, e);
9422
9470
  n.start();
9423
9471
  let i = () => {
9424
9472
  n.stop(), process.exit(0);
@@ -9429,6 +9477,6 @@ async function Ts() {
9429
9477
  default: console.error(`โŒ Unknown command: '${t}'`), ae(), process.exit(1);
9430
9478
  }
9431
9479
  }
9432
- Ts().catch(console.error);
9480
+ Ds().catch(console.error);
9433
9481
  //#endregion
9434
- export { ws as GitHubStatusPlugin };
9482
+ export { Es as GitHubStatusPlugin };
package/dist/queue.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare class QueueManager {
19
19
  */
20
20
  finishJob(jobId: string | number, status: JobStatus): Promise<void>;
21
21
  completeJob(jobId: string | number, status: JobStatus, report: WorkflowExecutionReport): Promise<void>;
22
+ cancelJob(jobId: string | number): Promise<'cancelled' | 'not_found' | 'not_active'>;
22
23
  restartJob(jobId: string | number): Promise<any>;
23
24
  /**
24
25
  * Checks if the current job has been marked for cancellation by another event
@@ -1 +1 @@
1
- {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvF,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE9B,IAAI;IAKV;;;OAGG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,cAAc,CAAC,EAAE,MAAM;IAoBrI;;;OAGG;IACG,YAAY,CAAC,UAAU,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA6BlE,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS;IAInD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAStG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAiBvC;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrD,cAAc;IAMd,YAAY;IAsClB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIlD;;OAEG;IACG,QAAQ,CAAC,KAAK,SAAK,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAiB/E;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxF,2DAA2D;IACrD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5F;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAS1E"}
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvF,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE9B,IAAI;IAKV;;;OAGG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,cAAc,CAAC,EAAE,MAAM;IAoBrI;;;OAGG;IACG,YAAY,CAAC,UAAU,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA6BlE,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS;IAInD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;IAapF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAiBvC;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrD,cAAc;IAMd,YAAY;IAsClB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIlD;;OAEG;IACG,QAAQ,CAAC,KAAK,SAAK,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAiB/E;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxF,2DAA2D;IACrD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5F;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAS1E"}
@@ -5,6 +5,7 @@ export declare class SecretRepository {
5
5
  set(name: string, value: string): Promise<void>;
6
6
  getAll(): Promise<Record<string, string>>;
7
7
  names(): Promise<string[]>;
8
+ delete(name: string): Promise<boolean>;
8
9
  private decrypt;
9
10
  }
10
11
  //# sourceMappingURL=secret-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-repository.d.ts","sourceRoot":"","sources":["../src/secret-repository.ts"],"names":[],"mappings":"AAWA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,GAAG,CAAC,CAAS;IAEf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,aAAa;IAIf,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAKzC,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhC,OAAO,CAAC,OAAO;CAMhB"}
1
+ {"version":3,"file":"secret-repository.d.ts","sourceRoot":"","sources":["../src/secret-repository.ts"],"names":[],"mappings":"AAWA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,GAAG,CAAC,CAAS;IAEf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,aAAa;IAIf,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAKzC,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAK1B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5C,OAAO,CAAC,OAAO;CAMhB"}
package/dist/server.d.ts CHANGED
@@ -36,6 +36,7 @@ export declare class WebhookServer {
36
36
  private handleWorkflowPublish;
37
37
  private handleSecretList;
38
38
  private handleSecretSave;
39
+ private handleSecretDelete;
39
40
  private handleJobSecrets;
40
41
  /**
41
42
  * Handles Zero-Downtime Secret Reload
@@ -52,6 +53,7 @@ export declare class WebhookServer {
52
53
  */
53
54
  private renderRunDetails;
54
55
  private handleRestartJob;
56
+ private handleCancelJob;
55
57
  private currentSecrets;
56
58
  private redact;
57
59
  private redactText;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,mBAAmB,EACnB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AASpB,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,eAAe,CAAgB;WAE1B,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;gBAK1D,OAAO,EAAE,oBAAoB;IAYzC,oBAAoB,CAAC,YAAY,EAAE,mBAAmB;YAIxC,aAAa;IAgG3B;;OAEG;YACW,aAAa;YA0Bb,WAAW;YAwBX,UAAU;YAuBV,cAAc;IAuC5B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,YAAY;YAON,QAAQ;YAYR,wBAAwB;YAcxB,kBAAkB;YAOlB,iBAAiB;YAWjB,kBAAkB;YAiBlB,oBAAoB;YASpB,qBAAqB;YAWrB,gBAAgB;YAMhB,gBAAgB;YAgBhB,gBAAgB;IAc9B;;OAEG;YACW,kBAAkB;YAclB,iBAAiB;IAuB/B;;OAEG;YACW,eAAe;YAQf,mBAAmB;IAWjC;;OAEG;YACW,gBAAgB;YAuBhB,gBAAgB;YAehB,cAAc;IAS5B,OAAO,CAAC,MAAM;YAMA,UAAU;IAIxB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAStC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,mBAAmB,EACnB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAUpB,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,eAAe,CAAgB;WAE1B,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;gBAK1D,OAAO,EAAE,oBAAoB;IAYzC,oBAAoB,CAAC,YAAY,EAAE,mBAAmB;YAIxC,aAAa;IAiH3B;;OAEG;YACW,aAAa;YA0Bb,WAAW;YAwBX,UAAU;YAuBV,cAAc;IAuC5B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,YAAY;YAON,QAAQ;YAYR,wBAAwB;YAcxB,kBAAkB;YAOlB,iBAAiB;YAWjB,kBAAkB;YAiBlB,oBAAoB;YASpB,qBAAqB;YAWrB,gBAAgB;YAMhB,gBAAgB;YAgBhB,kBAAkB;YASlB,gBAAgB;IAc9B;;OAEG;YACW,kBAAkB;YAclB,iBAAiB;IAuB/B;;OAEG;YACW,eAAe;YAQf,mBAAmB;IAWjC;;OAEG;YACW,gBAAgB;YAuBhB,gBAAgB;YAehB,eAAe;YAkBf,cAAc;IAS5B,OAAO,CAAC,MAAM;YAMA,UAAU;IAIxB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAStC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
@@ -0,0 +1,2 @@
1
+ export declare function generateWorkflowManagementHtml(page: 'list' | 'editor', workflowId?: string): string;
2
+ //# sourceMappingURL=workflows-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows-ui.d.ts","sourceRoot":"","sources":["../src/workflows-ui.ts"],"names":[],"mappings":"AAEA,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,UAAU,SAAK,GAAG,MAAM,CAI/F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-cli/on",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "CLI entry point for the on plugin ecosystem.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",