@cloud-cli/on 1.55.2 → 1.56.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/dist/api-key-repository.d.ts +2 -2
- package/dist/api-key-repository.d.ts.map +1 -1
- package/dist/on.js +143 -32
- package/dist/queue.d.ts +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const API_KEY_SCOPES: readonly ["workflows:read", "workflows:write", "logs:read", "artifacts:read", "runs:control"];
|
|
1
|
+
export declare const API_KEY_SCOPES: readonly ["workflows:read", "workflows:write", "logs:read", "artifacts:read", "runs:control", "runs:dispatch"];
|
|
2
2
|
export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
|
|
3
3
|
export declare class ApiKeyRepository {
|
|
4
4
|
init(): Promise<void>;
|
|
@@ -6,7 +6,7 @@ export declare class ApiKeyRepository {
|
|
|
6
6
|
issue(name: string, scopes: string[]): Promise<{
|
|
7
7
|
id: `${string}-${string}-${string}-${string}-${string}`;
|
|
8
8
|
name: string;
|
|
9
|
-
scopes: ("workflows:read" | "workflows:write" | "logs:read" | "artifacts:read" | "runs:control")[];
|
|
9
|
+
scopes: ("workflows:read" | "workflows:write" | "logs:read" | "artifacts:read" | "runs:control" | "runs:dispatch")[];
|
|
10
10
|
token: string;
|
|
11
11
|
}>;
|
|
12
12
|
scopesForToken(token: string): Promise<string[] | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-repository.d.ts","sourceRoot":"","sources":["../src/api-key-repository.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"api-key-repository.d.ts","sourceRoot":"","sources":["../src/api-key-repository.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,gHAAiH,CAAC;AAC7I,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,qBAAa,gBAAgB;IACrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYrB,IAAI;IAKJ,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;;;;;;IAUpC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAQvD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAI3C"}
|
package/dist/on.js
CHANGED
|
@@ -227,13 +227,15 @@ var ye = class extends l {
|
|
|
227
227
|
await this.createTables(), await this.clearStaleJobs();
|
|
228
228
|
}
|
|
229
229
|
async enqueue(e, t, n, r = [], i) {
|
|
230
|
-
|
|
230
|
+
i && await C.run("UPDATE jobs SET status = 'cancelled' WHERE concurrency_key = ? AND status IN ('pending', 'running');", [i]);
|
|
231
|
+
let a = await C.run("INSERT INTO jobs (workflow_id, workflow_revision, required_tags, concurrency_key, payload) VALUES (?, ?, ?, ?, ?);", [
|
|
231
232
|
e,
|
|
232
233
|
t,
|
|
233
234
|
JSON.stringify(r),
|
|
234
235
|
i || "",
|
|
235
236
|
JSON.stringify(n)
|
|
236
237
|
]);
|
|
238
|
+
return Number(a?.lastInsertRowid ?? a?.id ?? 0);
|
|
237
239
|
}
|
|
238
240
|
async claimNextJob(e = []) {
|
|
239
241
|
return await C.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(jobs.required_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;
|
|
@@ -5739,6 +5741,49 @@ var oa = {
|
|
|
5739
5741
|
content: { "application/json": { schema: { $ref: "#/components/schemas/JobList" } } }
|
|
5740
5742
|
} }
|
|
5741
5743
|
} },
|
|
5744
|
+
"/api/dispatch/{trigger}": { post: {
|
|
5745
|
+
tags: ["jobs"],
|
|
5746
|
+
summary: "Dispatch a workflow trigger with manual inputs",
|
|
5747
|
+
security: [{ bearerAuth: [] }, { basicAuth: [] }],
|
|
5748
|
+
parameters: [{
|
|
5749
|
+
name: "trigger",
|
|
5750
|
+
in: "path",
|
|
5751
|
+
required: !0,
|
|
5752
|
+
schema: { type: "string" }
|
|
5753
|
+
}],
|
|
5754
|
+
requestBody: {
|
|
5755
|
+
required: !0,
|
|
5756
|
+
content: { "application/json": { schema: { type: "object" } } }
|
|
5757
|
+
},
|
|
5758
|
+
responses: {
|
|
5759
|
+
202: {
|
|
5760
|
+
description: "Jobs queued",
|
|
5761
|
+
content: { "application/json": { schema: { type: "object" } } }
|
|
5762
|
+
},
|
|
5763
|
+
401: { $ref: "#/components/responses/Unauthorized" }
|
|
5764
|
+
}
|
|
5765
|
+
} },
|
|
5766
|
+
"/api/jobs/{jobId}/wait": { get: {
|
|
5767
|
+
tags: ["jobs"],
|
|
5768
|
+
summary: "Wait for a job to reach a terminal state",
|
|
5769
|
+
parameters: [{ $ref: "#/components/parameters/JobId" }, {
|
|
5770
|
+
name: "timeout",
|
|
5771
|
+
in: "query",
|
|
5772
|
+
schema: {
|
|
5773
|
+
type: "integer",
|
|
5774
|
+
minimum: 0,
|
|
5775
|
+
maximum: 12e4,
|
|
5776
|
+
default: 3e4
|
|
5777
|
+
}
|
|
5778
|
+
}],
|
|
5779
|
+
responses: {
|
|
5780
|
+
200: {
|
|
5781
|
+
description: "Latest job state",
|
|
5782
|
+
content: { "application/json": { schema: { type: "object" } } }
|
|
5783
|
+
},
|
|
5784
|
+
404: { $ref: "#/components/responses/NotFound" }
|
|
5785
|
+
}
|
|
5786
|
+
} },
|
|
5742
5787
|
"/api/jobs/{jobId}/cancel": { post: {
|
|
5743
5788
|
tags: ["jobs"],
|
|
5744
5789
|
summary: "Cancel an active job",
|
|
@@ -6262,7 +6307,8 @@ var oa = {
|
|
|
6262
6307
|
"workflows:write",
|
|
6263
6308
|
"logs:read",
|
|
6264
6309
|
"artifacts:read",
|
|
6265
|
-
"runs:control"
|
|
6310
|
+
"runs:control",
|
|
6311
|
+
"runs:dispatch"
|
|
6266
6312
|
]
|
|
6267
6313
|
}
|
|
6268
6314
|
}
|
|
@@ -6293,7 +6339,8 @@ var oa = {
|
|
|
6293
6339
|
"workflows:write",
|
|
6294
6340
|
"logs:read",
|
|
6295
6341
|
"artifacts:read",
|
|
6296
|
-
"runs:control"
|
|
6342
|
+
"runs:control",
|
|
6343
|
+
"runs:dispatch"
|
|
6297
6344
|
], ca = class {
|
|
6298
6345
|
async init() {
|
|
6299
6346
|
await C.exec("CREATE TABLE IF NOT EXISTS api_keys (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n key_hash TEXT NOT NULL UNIQUE,\n scopes TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_used_at DATETIME,\n revoked_at DATETIME\n )");
|
|
@@ -6328,7 +6375,7 @@ var oa = {
|
|
|
6328
6375
|
let t = await C.run("UPDATE api_keys SET revoked_at = CURRENT_TIMESTAMP WHERE id = ? AND revoked_at IS NULL", [e]);
|
|
6329
6376
|
return Number(t?.changes || 0) > 0;
|
|
6330
6377
|
}
|
|
6331
|
-
}, la = "<!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\" />\n <title>Runner settings</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <link rel=\"component\" href=\"/app-header.html\" />\n </head>\n <body data-page=\"__SETTINGS_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5 py-6\">\n <app-header title=\"Settings\" subtitle=\"Human-admin controls and automation credentials\" back=\"/runs\" back-label=\"Back to jobs\"></app-header>\n <nav class=\"flex flex-wrap gap-1 rounded-xl border border-gray-800 bg-gray-900/70 p-1\" aria-label=\"Settings navigation\">\n <a href=\"/settings/workflows\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Workflows</a>\n <a href=\"/settings/secrets\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Secrets</a>\n <a href=\"/settings/tokens\" bind-class=\"page === 'tokens' ? 'bg-gray-800.text-white' : ''\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">API tokens</a>\n <a href=\"/settings/notifications\" bind-class=\"page === 'notifications' ? 'bg-gray-800.text-white' : ''\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Notifications</a>\n </nav>\n\n <template if=\"page === 'tokens'\">\n </template>\n <template if=\"page === 'notifications'\">\n <section class=\"rounded-2xl border border-gray-800 bg-gray-900/80 p-5\" aria-labelledby=\"notifications-title\">\n <h2 id=\"notifications-title\" class=\"text-lg font-semibold text-white\">Notifications</h2>\n <p class=\"mt-1 text-sm text-gray-400\">Receive job completion notifications on this device.</p>\n <button type=\"button\" on-click=\"toggleNotifications()\" class=\"mt-5 inline-flex items-center gap-2 rounded-lg border border-indigo-500/60 px-4 py-2.5 text-sm font-medium text-indigo-200 outline-none hover:bg-indigo-500/10 focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <lucide-icon bind-icon=\"notificationsEnabled ? 'bell-off' : 'bell-ring'\" size=\"16\"></lucide-icon>\n {{ notificationsEnabled ? 'Disable notifications' : 'Enable notifications' }}\n </button>\n <p class=\"mt-3 text-sm text-gray-400\" role=\"status\">{{ notificationMessage }}</p>\n </section>\n </template>\n\n <template if=\"page === 'tokens'\">\n <section class=\"rounded-2xl border border-gray-800 bg-gray-900/80 p-5\" aria-labelledby=\"keys-title\">\n <div class=\"flex items-start justify-between gap-3\">\n <div>\n <h2 id=\"keys-title\" class=\"text-lg font-semibold text-white\">API keys</h2>\n <p class=\"mt-1 text-sm text-gray-400\">Issue narrowly scoped credentials for automation tools. Keys are shown only once.</p>\n </div>\n </div>\n <div class=\"mt-5 grid gap-3 rounded-xl border border-gray-800 bg-gray-950/50 p-4 md:grid-cols-[1fr_auto]\">\n <label class=\"sr-only\" for=\"key-name\">Key name</label>\n <input id=\"key-name\" bind-value=\"keyName\" on-input=\"setKeyName($event)\" placeholder=\"Documentation bot\" class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-sm outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\" />\n <button type=\"button\" on-click=\"issueKey()\" class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white outline-none hover:bg-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"><lucide-icon icon=\"key-round\" size=\"16\"></lucide-icon>Issue key</button>\n <fieldset class=\"md:col-span-2\">\n <legend class=\"mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500\">Scopes</legend>\n <div class=\"space-y-2\">\n <template for=\"scope of scopeOptions\">\n <label class=\"group flex cursor-pointer items-center gap-3 text-sm text-gray-300\">\n <input type=\"checkbox\" bind-checked=\"scope.selected\" on-change=\"toggleScope(scope)\" class=\"peer sr-only\" />\n <span aria-hidden=\"true\" class=\"flex h-5 w-5 shrink-0 items-center justify-center rounded-md border border-gray-600 bg-gray-900 text-transparent transition peer-checked:border-indigo-400 peer-checked:bg-indigo-500 peer-checked:text-white peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-400/70 peer-disabled:opacity-50\">\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 8 3 3 7-7\"></path></svg>\n </span>\n <span class=\"group-hover:text-white\">{{ scope.label }}</span>\n </label>\n </template>\n </div>\n </fieldset>\n </div>\n <template if=\"newToken\">\n <div class=\"mt-4 rounded-xl border border-amber-500/40 bg-amber-500/10 p-4\" role=\"alert\">\n <p class=\"text-sm font-semibold text-amber-200\">Copy this key now. It cannot be shown again.</p>\n <code class=\"mt-2 block select-all break-all rounded bg-gray-950 p-3 font-mono text-xs text-amber-100\">{{ newToken }}</code>\n </div>\n </template>\n <h3 class=\"mt-5 text-xs font-semibold uppercase tracking-wider text-gray-500\">Active tokens</h3>\n <ul class=\"mt-2 space-y-2\" aria-label=\"Active API keys\">\n <template for=\"key of keys\">\n <li class=\"flex items-center justify-between gap-3 rounded-xl border border-gray-800 bg-gray-950/60 px-4 py-3\">\n <div class=\"min-w-0\"><p class=\"truncate text-sm font-medium text-gray-200\">{{ key.name }}</p><p class=\"text-xs text-gray-500\">{{ key.scopes.join(', ') }} · created {{ key.created_at }}</p></div>\n <button type=\"button\" on-click=\"revokeKey(key)\" class=\"shrink-0 rounded-lg p-2 text-rose-300 outline-none hover:bg-rose-500/10 focus-visible:ring-2 focus-visible:ring-rose-400/60\" aria-label=\"Revoke {{ key.name }}\" title=\"Revoke key\"><lucide-icon icon=\"trash\" size=\"16\"></lucide-icon></button>\n </li>\n </template>\n </ul>\n </section>\n </template>\n <p class=\"text-sm text-rose-300\" role=\"alert\" class-hidden=\"!error\">{{ error }}</p>\n </main>\n <script setup>\n import { onInit, ref } from '@li3/web';\n export default function () {\n const page = document.body.dataset.page;\n const keys = ref([]);\n const keyName = ref('');\n const newToken = ref('');\n const error = ref('');\n const notificationsSupported = ref('Notification' in window && 'serviceWorker' in navigator);\n const notificationsEnabled = ref(notificationsSupported.value && Notification.permission === 'granted' && localStorage.getItem('runner-notifications') === 'enabled');\n const notificationMessage = ref('');\n const scopeOptions = ref([\n { name: 'workflows:read', label: 'Read workflows', selected: true },\n { name: 'workflows:write', label: 'Modify workflows', selected: false },\n { name: 'logs:read', label: 'Read logs', selected: false },\n { name: 'artifacts:read', label: 'Read artifacts', selected: false },\n { name: 'runs:control', label: 'Control runs', selected: false },\n ]);\n const api = async (url, options = {}) => { const response = await fetch(url, { headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) }, ...options }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`); return body; };\n const load = async () => { keys.value = (await api('/api/api-keys')).keys; };\n const issueKey = async () => { try { error.value = ''; const result = await api('/api/api-keys', { method: 'POST', body: JSON.stringify({ name: keyName.value, scopes: scopeOptions.value.filter((scope) => scope.selected).map((scope) => scope.name) }) }); newToken.value = result.token; keyName.value = ''; await load(); } catch (reason) { error.value = reason.message; } };\n const revokeKey = async (key) => { if (!confirm(`Revoke ${key.name}?`)) return; try { await api(`/api/api-keys/${key.id}`, { method: 'DELETE' }); await load(); } catch (reason) { error.value = reason.message; } };\n const setKeyName = (event) => { keyName.value = event.target.value; };\n const toggleScope = (scope) => { scope.selected = !scope.selected; };\n const toggleNotifications = async () => {\n if (notificationsEnabled.value) {\n notificationsEnabled.value = false;\n localStorage.removeItem('runner-notifications');\n notificationMessage.value = 'Notifications disabled on this device.';\n return;\n }\n if (!notificationsSupported.value) { notificationMessage.value = 'Notifications are not supported by this browser.'; return; }\n const permission = await Notification.requestPermission();\n notificationsEnabled.value = permission === 'granted';\n if (notificationsEnabled.value) { localStorage.setItem('runner-notifications', 'enabled'); notificationMessage.value = 'Notifications enabled.'; }\n else notificationMessage.value = 'Notification permission was not granted.';\n };\n onInit(() => { if (page === 'tokens') load().catch((reason) => { error.value = reason.message; }); });\n return { page, keys, keyName, newToken, error, scopeOptions, issueKey, revokeKey, setKeyName, toggleScope, notificationsEnabled, notificationMessage, toggleNotifications };\n }\n <\/script>\n </template>\n <script type=\"module\">import '@li3/web';<\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
|
|
6378
|
+
}, la = "<!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\" />\n <title>Runner settings</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <link rel=\"component\" href=\"/app-header.html\" />\n </head>\n <body data-page=\"__SETTINGS_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5 py-6\">\n <app-header title=\"Settings\" subtitle=\"Human-admin controls and automation credentials\" back=\"/runs\" back-label=\"Back to jobs\"></app-header>\n <nav class=\"flex flex-wrap gap-1 rounded-xl border border-gray-800 bg-gray-900/70 p-1\" aria-label=\"Settings navigation\">\n <a href=\"/settings/workflows\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Workflows</a>\n <a href=\"/settings/secrets\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Secrets</a>\n <a href=\"/settings/tokens\" bind-class=\"page === 'tokens' ? 'bg-gray-800.text-white' : ''\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">API tokens</a>\n <a href=\"/settings/notifications\" bind-class=\"page === 'notifications' ? 'bg-gray-800.text-white' : ''\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Notifications</a>\n </nav>\n\n <template if=\"page === 'tokens'\">\n </template>\n <template if=\"page === 'notifications'\">\n <section class=\"rounded-2xl border border-gray-800 bg-gray-900/80 p-5\" aria-labelledby=\"notifications-title\">\n <h2 id=\"notifications-title\" class=\"text-lg font-semibold text-white\">Notifications</h2>\n <p class=\"mt-1 text-sm text-gray-400\">Receive job completion notifications on this device.</p>\n <button type=\"button\" on-click=\"toggleNotifications()\" class=\"mt-5 inline-flex items-center gap-2 rounded-lg border border-indigo-500/60 px-4 py-2.5 text-sm font-medium text-indigo-200 outline-none hover:bg-indigo-500/10 focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <lucide-icon bind-icon=\"notificationsEnabled ? 'bell-off' : 'bell-ring'\" size=\"16\"></lucide-icon>\n {{ notificationsEnabled ? 'Disable notifications' : 'Enable notifications' }}\n </button>\n <p class=\"mt-3 text-sm text-gray-400\" role=\"status\">{{ notificationMessage }}</p>\n </section>\n </template>\n\n <template if=\"page === 'tokens'\">\n <section class=\"rounded-2xl border border-gray-800 bg-gray-900/80 p-5\" aria-labelledby=\"keys-title\">\n <div class=\"flex items-start justify-between gap-3\">\n <div>\n <h2 id=\"keys-title\" class=\"text-lg font-semibold text-white\">API keys</h2>\n <p class=\"mt-1 text-sm text-gray-400\">Issue narrowly scoped credentials for automation tools. Keys are shown only once.</p>\n </div>\n </div>\n <div class=\"mt-5 grid gap-3 rounded-xl border border-gray-800 bg-gray-950/50 p-4 md:grid-cols-[1fr_auto]\">\n <label class=\"sr-only\" for=\"key-name\">Key name</label>\n <input id=\"key-name\" bind-value=\"keyName\" on-input=\"setKeyName($event)\" placeholder=\"Documentation bot\" class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-sm outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\" />\n <button type=\"button\" on-click=\"issueKey()\" class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white outline-none hover:bg-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"><lucide-icon icon=\"key-round\" size=\"16\"></lucide-icon>Issue key</button>\n <fieldset class=\"md:col-span-2\">\n <legend class=\"mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500\">Scopes</legend>\n <div class=\"space-y-2\">\n <template for=\"scope of scopeOptions\">\n <label class=\"group flex cursor-pointer items-center gap-3 text-sm text-gray-300\">\n <input type=\"checkbox\" bind-checked=\"scope.selected\" on-change=\"toggleScope(scope)\" class=\"peer sr-only\" />\n <span aria-hidden=\"true\" class=\"flex h-5 w-5 shrink-0 items-center justify-center rounded-md border border-gray-600 bg-gray-900 text-transparent transition peer-checked:border-indigo-400 peer-checked:bg-indigo-500 peer-checked:text-white peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-400/70 peer-disabled:opacity-50\">\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 8 3 3 7-7\"></path></svg>\n </span>\n <span class=\"group-hover:text-white\">{{ scope.label }}</span>\n </label>\n </template>\n </div>\n </fieldset>\n </div>\n <template if=\"newToken\">\n <div class=\"mt-4 rounded-xl border border-amber-500/40 bg-amber-500/10 p-4\" role=\"alert\">\n <p class=\"text-sm font-semibold text-amber-200\">Copy this key now. It cannot be shown again.</p>\n <code class=\"mt-2 block select-all break-all rounded bg-gray-950 p-3 font-mono text-xs text-amber-100\">{{ newToken }}</code>\n </div>\n </template>\n <h3 class=\"mt-5 text-xs font-semibold uppercase tracking-wider text-gray-500\">Active tokens</h3>\n <ul class=\"mt-2 space-y-2\" aria-label=\"Active API keys\">\n <template for=\"key of keys\">\n <li class=\"flex items-center justify-between gap-3 rounded-xl border border-gray-800 bg-gray-950/60 px-4 py-3\">\n <div class=\"min-w-0\"><p class=\"truncate text-sm font-medium text-gray-200\">{{ key.name }}</p><p class=\"text-xs text-gray-500\">{{ key.scopes.join(', ') }} · created {{ key.created_at }}</p></div>\n <button type=\"button\" on-click=\"revokeKey(key)\" class=\"shrink-0 rounded-lg p-2 text-rose-300 outline-none hover:bg-rose-500/10 focus-visible:ring-2 focus-visible:ring-rose-400/60\" aria-label=\"Revoke {{ key.name }}\" title=\"Revoke key\"><lucide-icon icon=\"trash\" size=\"16\"></lucide-icon></button>\n </li>\n </template>\n </ul>\n </section>\n </template>\n <p class=\"text-sm text-rose-300\" role=\"alert\" class-hidden=\"!error\">{{ error }}</p>\n </main>\n <script setup>\n import { onInit, ref } from '@li3/web';\n export default function () {\n const page = document.body.dataset.page;\n const keys = ref([]);\n const keyName = ref('');\n const newToken = ref('');\n const error = ref('');\n const notificationsSupported = ref('Notification' in window && 'serviceWorker' in navigator);\n const notificationsEnabled = ref(notificationsSupported.value && Notification.permission === 'granted' && localStorage.getItem('runner-notifications') === 'enabled');\n const notificationMessage = ref('');\n const scopeOptions = ref([\n { name: 'workflows:read', label: 'Read workflows', selected: true },\n { name: 'workflows:write', label: 'Modify workflows', selected: false },\n { name: 'logs:read', label: 'Read logs', selected: false },\n { name: 'artifacts:read', label: 'Read artifacts', selected: false },\n { name: 'runs:control', label: 'Control runs', selected: false },\n { name: 'runs:dispatch', label: 'Dispatch runs', selected: false },\n ]);\n const api = async (url, options = {}) => { const response = await fetch(url, { headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) }, ...options }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`); return body; };\n const load = async () => { keys.value = (await api('/api/api-keys')).keys; };\n const issueKey = async () => { try { error.value = ''; const result = await api('/api/api-keys', { method: 'POST', body: JSON.stringify({ name: keyName.value, scopes: scopeOptions.value.filter((scope) => scope.selected).map((scope) => scope.name) }) }); newToken.value = result.token; keyName.value = ''; await load(); } catch (reason) { error.value = reason.message; } };\n const revokeKey = async (key) => { if (!confirm(`Revoke ${key.name}?`)) return; try { await api(`/api/api-keys/${key.id}`, { method: 'DELETE' }); await load(); } catch (reason) { error.value = reason.message; } };\n const setKeyName = (event) => { keyName.value = event.target.value; };\n const toggleScope = (scope) => { scope.selected = !scope.selected; };\n const toggleNotifications = async () => {\n if (notificationsEnabled.value) {\n notificationsEnabled.value = false;\n localStorage.removeItem('runner-notifications');\n notificationMessage.value = 'Notifications disabled on this device.';\n return;\n }\n if (!notificationsSupported.value) { notificationMessage.value = 'Notifications are not supported by this browser.'; return; }\n const permission = await Notification.requestPermission();\n notificationsEnabled.value = permission === 'granted';\n if (notificationsEnabled.value) { localStorage.setItem('runner-notifications', 'enabled'); notificationMessage.value = 'Notifications enabled.'; }\n else notificationMessage.value = 'Notification permission was not granted.';\n };\n onInit(() => { if (page === 'tokens') load().catch((reason) => { error.value = reason.message; }); });\n return { page, keys, keyName, newToken, error, scopeOptions, issueKey, revokeKey, setKeyName, toggleScope, notificationsEnabled, notificationMessage, toggleNotifications };\n }\n <\/script>\n </template>\n <script type=\"module\">import '@li3/web';<\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
|
|
6332
6379
|
//#endregion
|
|
6333
6380
|
//#region src/settings-ui.ts
|
|
6334
6381
|
function ua(e = "tokens") {
|
|
@@ -20859,23 +20906,27 @@ var SC = class {
|
|
|
20859
20906
|
let e = n.searchParams.get("afterId"), r = n.searchParams.get("beforeId"), i = n.searchParams.get("limit"), a = n.searchParams.get("filter")?.trim() || void 0, o = n.searchParams.get("workflowId")?.trim() || void 0, s = e === null ? void 0 : Number(e), c = r === null ? void 0 : Number(r), l = i === null ? CC : Number(i);
|
|
20860
20907
|
return s !== void 0 && (!Number.isSafeInteger(s) || s < 0) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "afterId must be a non-negative integer" }))) : c !== void 0 && (!Number.isSafeInteger(c) || c < 1) ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "beforeId must be a positive integer" }))) : !Number.isSafeInteger(l) || l < 1 || l > wC ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: `limit must be an integer from 1 to ${wC}` }))) : a && a.length > 200 ? (t.writeHead(400, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: "filter must be at most 200 characters" }))) : this.renderDashboardJobs(t, l, s, c, a, o);
|
|
20861
20908
|
}
|
|
20909
|
+
let s = n.pathname.match(/^\/api\/dispatch\/([a-z0-9-]+)$/);
|
|
20910
|
+
if (e.method === "POST" && s) return this.handleDispatch(e, t, s[1]);
|
|
20911
|
+
let c = n.pathname.match(/^\/api\/jobs\/(\d+)\/wait$/);
|
|
20912
|
+
if (e.method === "GET" && c) return this.handleJobWait(c[1], n.searchParams.get("timeout"), t);
|
|
20862
20913
|
if (e.method === "GET" && n.pathname === "/api/events") return this.events.subscribe(e, t);
|
|
20863
20914
|
if (e.method === "GET" && n.pathname === "/api/push/public-key") return this.handlePushPublicKey(t);
|
|
20864
20915
|
if (e.method === "POST" && n.pathname === "/api/push/subscriptions") return this.handlePushSubscribe(e, t);
|
|
20865
20916
|
if (e.method === "DELETE" && n.pathname === "/api/push/subscriptions") return this.handlePushUnsubscribe(e, t);
|
|
20866
20917
|
if (n.pathname === "/api/workflows/validate" && e.method === "POST") return this.handleWorkflowValidation(e, t);
|
|
20867
20918
|
if (n.pathname === "/api/secrets" && e.method === "GET") return this.handleSecretList(e, t);
|
|
20868
|
-
let
|
|
20869
|
-
if (
|
|
20870
|
-
if (
|
|
20871
|
-
let
|
|
20872
|
-
if (
|
|
20873
|
-
let
|
|
20874
|
-
if (
|
|
20919
|
+
let l = n.pathname.match(/^\/api\/secrets\/([A-Z][A-Z0-9_]*)$/);
|
|
20920
|
+
if (l && e.method === "PUT") return this.handleSecretSave(e, t, l[1]);
|
|
20921
|
+
if (l && e.method === "DELETE") return this.handleSecretDelete(e, t, l[1]);
|
|
20922
|
+
let u = n.pathname.match(/^\/api\/jobs\/(\d+)\/secrets$/);
|
|
20923
|
+
if (u && e.method === "GET") return this.handleJobSecrets(e, t, u[1]);
|
|
20924
|
+
let d = n.pathname.match(/^\/api\/jobs\/(\d+)\/cancel$/);
|
|
20925
|
+
if (d && e.method === "POST") return this.handleCancelJob(e, t, d[1]);
|
|
20875
20926
|
if (n.pathname === "/api/workflows" && e.method === "GET") return this.handleWorkflowList(e, t);
|
|
20876
|
-
let
|
|
20877
|
-
if (
|
|
20878
|
-
let [, r, i] =
|
|
20927
|
+
let p = n.pathname.match(/^\/api\/workflows\/([a-z0-9-]+)(\/publish)?$/);
|
|
20928
|
+
if (p) {
|
|
20929
|
+
let [, r, i] = p;
|
|
20879
20930
|
if (e.method === "GET" && !i) return this.handleWorkflowGet(e, t, r, n.searchParams.get("revision"));
|
|
20880
20931
|
if (e.method === "PUT" && !i) return this.handleWorkflowSave(e, t, r);
|
|
20881
20932
|
if (e.method === "DELETE" && !i) return this.handleWorkflowDelete(e, t, r);
|
|
@@ -20886,13 +20937,15 @@ var SC = class {
|
|
|
20886
20937
|
let r = n.pathname.replace("/runs/", "");
|
|
20887
20938
|
return this.renderRunDetails(r, t, "html", await this.hasScope(e, "logs:read"));
|
|
20888
20939
|
}
|
|
20889
|
-
let
|
|
20890
|
-
if (e.method === "POST" &&
|
|
20940
|
+
let m = n.pathname.match(/^\/api\/runs\/(\d+)\/ai-help$/);
|
|
20941
|
+
if (e.method === "POST" && m) return this.handleAiHelp(e, t, m[1]);
|
|
20891
20942
|
if (e.method === "GET" && n.pathname.startsWith("/api/runs/")) {
|
|
20892
|
-
let r = n.pathname.match(/^\/api\/runs\/(\d+)\/
|
|
20893
|
-
if (r) return this.
|
|
20894
|
-
let i = n.pathname.
|
|
20895
|
-
return this.
|
|
20943
|
+
let r = n.pathname.match(/^\/api\/runs\/(\d+)\/diagnostics$/);
|
|
20944
|
+
if (r) return this.handleDiagnostics(e, t, r[1]);
|
|
20945
|
+
let i = n.pathname.match(/^\/api\/runs\/(\d+)\/artifacts\/(.+)$/);
|
|
20946
|
+
if (i) return this.handleArtifactDownload(e, t, i[1], decodeURIComponent(i[2]));
|
|
20947
|
+
let a = n.pathname.replace("/api/runs/", "");
|
|
20948
|
+
return this.renderRunDetails(a, t, "json", await this.hasScope(e, "logs:read"));
|
|
20896
20949
|
}
|
|
20897
20950
|
if (e.method === "POST" && n.pathname.startsWith("/restart/")) {
|
|
20898
20951
|
let r = n.pathname.replace("/restart/", "");
|
|
@@ -20922,6 +20975,62 @@ var SC = class {
|
|
|
20922
20975
|
}));
|
|
20923
20976
|
}
|
|
20924
20977
|
}
|
|
20978
|
+
async handleDispatch(e, t, n) {
|
|
20979
|
+
if (!await this.requireScope(e, t, "runs:dispatch")) return;
|
|
20980
|
+
let r = await this.readJson(e, t);
|
|
20981
|
+
if (!r || typeof r != "object" || Array.isArray(r)) return;
|
|
20982
|
+
let i = await this.matchWorkflows(n, r, await this.workflows.published(), await this.currentSecrets());
|
|
20983
|
+
t.writeHead(202, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ jobs: i.map((e) => ({
|
|
20984
|
+
id: e,
|
|
20985
|
+
provider: n
|
|
20986
|
+
})) }));
|
|
20987
|
+
}
|
|
20988
|
+
async handleJobWait(e, t, n) {
|
|
20989
|
+
let r = Math.min(12e4, Math.max(0, Number(t || 3e4))), i = Date.now(), a;
|
|
20990
|
+
do {
|
|
20991
|
+
if (a = await this.queue.getJob(e), !a || [
|
|
20992
|
+
"success",
|
|
20993
|
+
"failed",
|
|
20994
|
+
"cancelled"
|
|
20995
|
+
].includes(a.status) || Date.now() - i >= r) break;
|
|
20996
|
+
await new Promise((e) => setTimeout(e, 500));
|
|
20997
|
+
} while (1);
|
|
20998
|
+
return a ? (n.writeHead(200, {
|
|
20999
|
+
"Cache-Control": "no-store",
|
|
21000
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
21001
|
+
}), n.end(JSON.stringify({
|
|
21002
|
+
job: we([a])[0],
|
|
21003
|
+
terminal: [
|
|
21004
|
+
"success",
|
|
21005
|
+
"failed",
|
|
21006
|
+
"cancelled"
|
|
21007
|
+
].includes(a.status)
|
|
21008
|
+
}))) : (n.writeHead(404, { "Content-Type": "application/json" }), n.end(JSON.stringify({ error: "Job not found" })));
|
|
21009
|
+
}
|
|
21010
|
+
async handleDiagnostics(e, t, n) {
|
|
21011
|
+
if (!await this.hasScope(e, "logs:read")) return this.requireScope(e, t, "logs:read");
|
|
21012
|
+
let r = await this.queue.getJob(n);
|
|
21013
|
+
if (!r) return t.writeHead(404, { "Content-Type": "application/json" }), t.end(JSON.stringify({ error: "Run not found" }));
|
|
21014
|
+
let i = r.report ? JSON.parse(r.report) : null, a = await this.workflows.getRevision(r.workflow_id, r.workflow_revision), o = await this.workflows.getRevisionSnapshot(r.workflow_id, r.workflow_revision), s = await this.queue.getJobLogs(n), c = i?.steps?.findIndex((e) => e.status === "failed") ?? -1;
|
|
21015
|
+
return t.writeHead(200, {
|
|
21016
|
+
"Cache-Control": "no-store",
|
|
21017
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
21018
|
+
}), t.end(JSON.stringify({
|
|
21019
|
+
jobId: String(r.id),
|
|
21020
|
+
workflow: {
|
|
21021
|
+
id: r.workflow_id,
|
|
21022
|
+
revision: r.workflow_revision,
|
|
21023
|
+
sourceYaml: o?.sourceYaml
|
|
21024
|
+
},
|
|
21025
|
+
status: r.status,
|
|
21026
|
+
inputs: i?.inputs || {},
|
|
21027
|
+
failedStep: c >= 0 ? i.steps[c] : null,
|
|
21028
|
+
steps: c >= 0 ? i.steps.slice(0, c + 1) : i?.steps || [],
|
|
21029
|
+
logs: s,
|
|
21030
|
+
artifacts: i?.artifacts || [],
|
|
21031
|
+
workflowFound: !!a
|
|
21032
|
+
}));
|
|
21033
|
+
}
|
|
20925
21034
|
async readRequest(e, t) {
|
|
20926
21035
|
let n = [], r = 0;
|
|
20927
21036
|
for await (let i of e) {
|
|
@@ -20967,36 +21076,38 @@ var SC = class {
|
|
|
20967
21076
|
}
|
|
20968
21077
|
}
|
|
20969
21078
|
async matchWorkflows(e, t, n, r = {}) {
|
|
20970
|
-
|
|
20971
|
-
|
|
21079
|
+
let i = [];
|
|
21080
|
+
for (let { definition: a, revision: o } of n) {
|
|
21081
|
+
if (a.on.provider !== e) continue;
|
|
20972
21082
|
let n = this.preprocessors.get(e);
|
|
20973
|
-
if (n?.filter && !n.filter(t,
|
|
20974
|
-
dr(`⏩ Skipped ${
|
|
21083
|
+
if (n?.filter && !n.filter(t, a.on).isValid) {
|
|
21084
|
+
dr(`⏩ Skipped ${a.id} based on ${e} webhook filters`, { inputs: t });
|
|
20975
21085
|
continue;
|
|
20976
21086
|
}
|
|
20977
|
-
if (
|
|
21087
|
+
if (a.on.if) try {
|
|
20978
21088
|
let e = n?.conditionContext?.(t, r) || {};
|
|
20979
|
-
if (!await mr.evaluateConditions(
|
|
21089
|
+
if (!await mr.evaluateConditions(a.on.if, {
|
|
20980
21090
|
inputs: t,
|
|
20981
21091
|
...e
|
|
20982
21092
|
})) {
|
|
20983
|
-
dr(`⏩ Skipped ${
|
|
21093
|
+
dr(`⏩ Skipped ${a.id} based on conditions: ${a.on.if}`, { inputs: t });
|
|
20984
21094
|
continue;
|
|
20985
21095
|
}
|
|
20986
21096
|
} catch (e) {
|
|
20987
|
-
console.error(`⚠️ Condition evaluation error in workflow [${
|
|
21097
|
+
console.error(`⚠️ Condition evaluation error in workflow [${a.id}]:`, e.message);
|
|
20988
21098
|
continue;
|
|
20989
21099
|
}
|
|
20990
|
-
for (let e of hr(
|
|
21100
|
+
for (let e of hr(a)) {
|
|
20991
21101
|
let n = await gr(e.tags, e.matrixContext, t), r = "";
|
|
20992
21102
|
e.concurrency?.group && (r = await mr.evaluateValue(e.concurrency.group, { inputs: t }));
|
|
20993
|
-
let
|
|
21103
|
+
let s = {
|
|
20994
21104
|
inputs: t,
|
|
20995
21105
|
matrix: e.matrixContext
|
|
20996
|
-
};
|
|
20997
|
-
|
|
21106
|
+
}, c = await this.queue.enqueue(a.id, o, s, n, r);
|
|
21107
|
+
c && i.push(c), this.events.publish("jobs.available", { tags: n });
|
|
20998
21108
|
}
|
|
20999
21109
|
}
|
|
21110
|
+
return i;
|
|
21000
21111
|
}
|
|
21001
21112
|
isAdmin(e) {
|
|
21002
21113
|
return this.matchesToken(e, this.adminToken, !0);
|
package/dist/queue.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class QueueManager {
|
|
|
8
8
|
* Enqueues a new job into the database.
|
|
9
9
|
* Includes simple GitHub-style concurrency cancellation.
|
|
10
10
|
*/
|
|
11
|
-
enqueue(workflowId: string, workflowRevision: number, payload: JobPayload, requiredTags?: string[], concurrencyKey?: string): Promise<
|
|
11
|
+
enqueue(workflowId: string, workflowRevision: number, payload: JobPayload, requiredTags?: string[], concurrencyKey?: string): Promise<number>;
|
|
12
12
|
/**
|
|
13
13
|
* Atomically claims the oldest pending job compatible with this worker.
|
|
14
14
|
* Requires SQLite >= 3.35 for the RETURNING clause.
|
package/dist/server.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export declare class WebhookServer {
|
|
|
22
22
|
* Processes incoming HTTP webhooks
|
|
23
23
|
*/
|
|
24
24
|
private handleWebhook;
|
|
25
|
+
private handleDispatch;
|
|
26
|
+
private handleJobWait;
|
|
27
|
+
private handleDiagnostics;
|
|
25
28
|
private readRequest;
|
|
26
29
|
private preprocess;
|
|
27
30
|
private matchWorkflows;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAc,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAOxF,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,IAAI,CAAiB;IAC7B,OAAO,CAAC,OAAO,CAA0B;IACzC,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;IAazC,oBAAoB,CAAC,YAAY,EAAE,mBAAmB;YAIxC,aAAa;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAc,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAOxF,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,IAAI,CAAiB;IAC7B,OAAO,CAAC,OAAO,CAA0B;IACzC,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;IAazC,oBAAoB,CAAC,YAAY,EAAE,mBAAmB;YAIxC,aAAa;IAgP3B;;OAEG;YACW,aAAa;YA0Bb,cAAc;YASd,aAAa;YAiBb,iBAAiB;YA0BjB,WAAW;YAwBX,UAAU;YAyBV,cAAc;IA8C5B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;YAIF,QAAQ;YAQR,YAAY;IAO1B,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,YAAY;YAUN,QAAQ;YAYR,wBAAwB;YAcxB,kBAAkB;YAOlB,iBAAiB;YAcjB,kBAAkB;YAoBlB,oBAAoB;YASpB,qBAAqB;YAWrB,gBAAgB;YAMhB,gBAAgB;YAiBhB,kBAAkB;YASlB,gBAAgB;IAc9B;;OAEG;YACW,kBAAkB;YAclB,iBAAiB;IA2B/B,OAAO,CAAC,mBAAmB;YASb,mBAAmB;YAWnB,qBAAqB;IAWnC;;OAEG;YACW,eAAe;IAK7B,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,mBAAmB;YAcb,mBAAmB;IAwBjC;;OAEG;YACW,gBAAgB;YA+BhB,gBAAgB;YAiBhB,sBAAsB;YAetB,YAAY;YAsDZ,eAAe;YAkBf,cAAc;IAS5B,OAAO,CAAC,MAAM;YAMA,UAAU;IAIlB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAU5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
|