From 23de918e942aef4be81912ee19a1c173f8332a8d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Jul 2026 20:59:51 +0200 Subject: [PATCH] feat: add Temporal-backed TaskTree service Co-Authored-By: Claude Opus 4.7 --- .tekton/hwlab-nc01-v03-pac.yaml | 2 +- .../hwlab-nc01-v03-ci-image-publish.yaml | 428 +++- cmd/hwlab-tasktree-api/main.ts | 20 + cmd/hwlab-tasktree-worker/main.ts | 39 + config/hwlab-access-control/nav-profiles.yaml | 1 + deploy/deploy.yaml | 60 + deploy/k8s/base/services.yaml | 28 + deploy/k8s/base/workloads.yaml | 48 + deploy/runtime/boot/hwlab-tasktree-api.sh | 9 + deploy/runtime/boot/hwlab-tasktree-worker.sh | 9 + internal/cloud/server.ts | 6 + internal/cloud/tasktree-proxy.test.ts | 39 + internal/cloud/tasktree-proxy.ts | 22 + internal/tasktree/activities.ts | 12 + internal/tasktree/contracts.ts | 71 + internal/tasktree/dispatcher.ts | 55 + internal/tasktree/http.ts | 35 + internal/tasktree/runtime.ts | 12 + internal/tasktree/store.ts | 164 ++ internal/tasktree/tasktree.test.ts | 62 + internal/tasktree/workflows.ts | 11 + package-lock.json | 2032 +++++++++++++++++ package.json | 6 + tools/hwlab-cli/bin/hwlab-cli.ts | 12 +- tools/src/tasktree-cli.test.ts | 25 + tools/src/tasktree-cli.ts | 81 + web/hwlab-cloud-web/src/api/index.ts | 3 + web/hwlab-cloud-web/src/api/tasktree.ts | 14 + .../src/components/layout/AppNavigation.vue | 8 + web/hwlab-cloud-web/src/router/index.ts | 3 + web/hwlab-cloud-web/src/stores/auth.ts | 1 + .../src/views/projects/TaskTreeView.vue | 95 + 32 files changed, 3410 insertions(+), 3 deletions(-) create mode 100644 cmd/hwlab-tasktree-api/main.ts create mode 100644 cmd/hwlab-tasktree-worker/main.ts create mode 100755 deploy/runtime/boot/hwlab-tasktree-api.sh create mode 100755 deploy/runtime/boot/hwlab-tasktree-worker.sh create mode 100644 internal/cloud/tasktree-proxy.test.ts create mode 100644 internal/cloud/tasktree-proxy.ts create mode 100644 internal/tasktree/activities.ts create mode 100644 internal/tasktree/contracts.ts create mode 100644 internal/tasktree/dispatcher.ts create mode 100644 internal/tasktree/http.ts create mode 100644 internal/tasktree/runtime.ts create mode 100644 internal/tasktree/store.ts create mode 100644 internal/tasktree/tasktree.test.ts create mode 100644 internal/tasktree/workflows.ts create mode 100644 tools/src/tasktree-cli.test.ts create mode 100644 tools/src/tasktree-cli.ts create mode 100644 web/hwlab-cloud-web/src/api/tasktree.ts create mode 100644 web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue diff --git a/.tekton/hwlab-nc01-v03-pac.yaml b/.tekton/hwlab-nc01-v03-pac.yaml index 43b4e788..b52309bf 100644 --- a/.tekton/hwlab-nc01-v03-pac.yaml +++ b/.tekton/hwlab-nc01-v03-pac.yaml @@ -77,7 +77,7 @@ spec: - name: registry-prefix value: 127.0.0.1:5000/hwlab - name: services - value: "hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills" + value: "hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-tasktree-api,hwlab-tasktree-worker,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills" - name: base-image value: 127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim - name: build-cache-mode diff --git a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml index c0e7b6c6..b3440268 100644 --- a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml +++ b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml @@ -53,7 +53,7 @@ spec: default: 127.0.0.1:5000/hwlab - name: services type: string - default: "hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills" + default: "hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-tasktree-api,hwlab-tasktree-worker,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills" - name: base-image type: string default: 127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim @@ -359,6 +359,14 @@ spec: description: hwlab-project-management rollout affected according to ci-plan - name: build-hwlab-project-management description: hwlab-project-management image build required according to ci-plan + - name: affected-hwlab-tasktree-api + description: hwlab-tasktree-api rollout affected according to ci-plan + - name: build-hwlab-tasktree-api + description: hwlab-tasktree-api image build required according to ci-plan + - name: affected-hwlab-tasktree-worker + description: hwlab-tasktree-worker rollout affected according to ci-plan + - name: build-hwlab-tasktree-worker + description: hwlab-tasktree-worker image build required according to ci-plan - name: affected-hwlab-cloud-web description: hwlab-cloud-web rollout affected according to ci-plan - name: build-hwlab-cloud-web @@ -1264,6 +1272,422 @@ spec: value: $(params.base-image) - name: build-cache-mode value: $(params.build-cache-mode) + - name: build-hwlab-tasktree-api + runAfter: + - plan-artifacts + workspaces: + - name: source + workspace: source + when: + - input: $(tasks.plan-artifacts.results.build-hwlab-tasktree-api) + operator: in + values: + - "true" + taskSpec: + params: + - name: revision + - name: lane + - name: catalog-path + - name: image-tag-mode + - name: registry-prefix + - name: service-id + - name: base-image + - name: build-cache-mode + results: + - name: status + description: hwlab-tasktree-api status + - name: service-id + description: hwlab-tasktree-api service-id + - name: image + description: hwlab-tasktree-api image + - name: image-tag + description: hwlab-tasktree-api image-tag + - name: digest + description: hwlab-tasktree-api digest + - name: repository-digest + description: hwlab-tasktree-api repository-digest + - name: source-commit-id + description: hwlab-tasktree-api source-commit-id + - name: component-input-hash + description: hwlab-tasktree-api component-input-hash + - name: environment-input-hash + description: hwlab-tasktree-api environment-input-hash + - name: code-input-hash + description: hwlab-tasktree-api code-input-hash + - name: runtime-mode + description: hwlab-tasktree-api runtime-mode + - name: boot-repo + description: hwlab-tasktree-api boot-repo + - name: boot-commit + description: hwlab-tasktree-api boot-commit + - name: boot-sh + description: hwlab-tasktree-api boot-sh + - name: build-created-at + description: hwlab-tasktree-api build-created-at + - name: build-backend + description: hwlab-tasktree-api build-backend + - name: reused-from + description: hwlab-tasktree-api reused-from + - name: go-base-image + description: hwlab-tasktree-api go-base-image + - name: go-base-image-status + description: hwlab-tasktree-api go-base-image-status + - name: go-base-digest + description: hwlab-tasktree-api go-base-digest + - name: go-base-build-duration-ms + description: hwlab-tasktree-api go-base-build-duration-ms + - name: go-base-buildkit-cache-ref + description: hwlab-tasktree-api go-base-buildkit-cache-ref + workspaces: + - name: source + volumes: + - name: service-work + emptyDir: + {} + - name: buildkit-bin + emptyDir: + {} + - name: buildkit-run + emptyDir: + {} + sidecars: + - name: buildkitd + image: 127.0.0.1:5000/hwlab/buildkit:rootless + args: + - "--addr" + - unix:///workspace/buildkit-run/buildkitd.sock + - "--oci-worker-no-process-sandbox" + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + volumeMounts: + - name: buildkit-run + mountPath: /workspace/buildkit-run + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + allowPrivilegeEscalation: true + appArmorProfile: + type: Unconfined + seccompProfile: + type: Unconfined + steps: + - name: prepare-buildkit-client + image: 127.0.0.1:5000/hwlab/buildkit:rootless + securityContext: + runAsUser: 0 + runAsGroup: 0 + volumeMounts: + - name: buildkit-bin + mountPath: /workspace/buildkit-bin + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: HWLAB_NODE_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_ALL_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: HOME + value: /tekton/home + - name: XDG_CONFIG_HOME + value: /tekton/home/.config + - name: publish + image: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: HWLAB_NODE_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_ALL_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: HOME + value: /tekton/home + - name: XDG_CONFIG_HOME + value: /tekton/home/.config + volumeMounts: + - name: service-work + mountPath: /workspace/service-work + - name: buildkit-bin + mountPath: /workspace/buildkit-bin + - name: buildkit-run + mountPath: /workspace/buildkit-run + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-cicd-timing\",schemaVersion:\"v1\",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:\"scripts/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor tool in node npm git python3 ssh curl; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -s /workspace/source/affected-services.json ] && ! node -e 'const fs=require(\"node:fs\"); const p=JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\",\"utf8\")); const service=process.argv[1]; const item=(p.entries||[]).find((entry)=>entry.serviceId===service); process.exit(item && item.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n echo '{\"event\":\"dependency-local-registry-probe-start\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"target\":\"http://127.0.0.1:5000/v2/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n echo '{\"event\":\"dependency-local-registry-probe\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"ok\":true,\"durationSeconds\":'\"$((registry_finished_at - registry_started_at))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-v03-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimePath,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --registry-prefix \"$(params.registry-prefix)\" --report \"/workspace/source/service-results/$(params.service-id).json\" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services \"$(params.service-id)\" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr \"$HWLAB_BUILDKIT_ADDR\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + imagePullPolicy: Always + params: + - name: revision + value: $(params.revision) + - name: lane + value: $(params.lane) + - name: catalog-path + value: $(params.catalog-path) + - name: image-tag-mode + value: $(params.image-tag-mode) + - name: registry-prefix + value: $(params.registry-prefix) + - name: service-id + value: hwlab-tasktree-api + - name: base-image + value: $(params.base-image) + - name: build-cache-mode + value: $(params.build-cache-mode) + - name: build-hwlab-tasktree-worker + runAfter: + - plan-artifacts + workspaces: + - name: source + workspace: source + when: + - input: $(tasks.plan-artifacts.results.build-hwlab-tasktree-worker) + operator: in + values: + - "true" + taskSpec: + params: + - name: revision + - name: lane + - name: catalog-path + - name: image-tag-mode + - name: registry-prefix + - name: service-id + - name: base-image + - name: build-cache-mode + results: + - name: status + description: hwlab-tasktree-worker status + - name: service-id + description: hwlab-tasktree-worker service-id + - name: image + description: hwlab-tasktree-worker image + - name: image-tag + description: hwlab-tasktree-worker image-tag + - name: digest + description: hwlab-tasktree-worker digest + - name: repository-digest + description: hwlab-tasktree-worker repository-digest + - name: source-commit-id + description: hwlab-tasktree-worker source-commit-id + - name: component-input-hash + description: hwlab-tasktree-worker component-input-hash + - name: environment-input-hash + description: hwlab-tasktree-worker environment-input-hash + - name: code-input-hash + description: hwlab-tasktree-worker code-input-hash + - name: runtime-mode + description: hwlab-tasktree-worker runtime-mode + - name: boot-repo + description: hwlab-tasktree-worker boot-repo + - name: boot-commit + description: hwlab-tasktree-worker boot-commit + - name: boot-sh + description: hwlab-tasktree-worker boot-sh + - name: build-created-at + description: hwlab-tasktree-worker build-created-at + - name: build-backend + description: hwlab-tasktree-worker build-backend + - name: reused-from + description: hwlab-tasktree-worker reused-from + - name: go-base-image + description: hwlab-tasktree-worker go-base-image + - name: go-base-image-status + description: hwlab-tasktree-worker go-base-image-status + - name: go-base-digest + description: hwlab-tasktree-worker go-base-digest + - name: go-base-build-duration-ms + description: hwlab-tasktree-worker go-base-build-duration-ms + - name: go-base-buildkit-cache-ref + description: hwlab-tasktree-worker go-base-buildkit-cache-ref + workspaces: + - name: source + volumes: + - name: service-work + emptyDir: + {} + - name: buildkit-bin + emptyDir: + {} + - name: buildkit-run + emptyDir: + {} + sidecars: + - name: buildkitd + image: 127.0.0.1:5000/hwlab/buildkit:rootless + args: + - "--addr" + - unix:///workspace/buildkit-run/buildkitd.sock + - "--oci-worker-no-process-sandbox" + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + volumeMounts: + - name: buildkit-run + mountPath: /workspace/buildkit-run + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + allowPrivilegeEscalation: true + appArmorProfile: + type: Unconfined + seccompProfile: + type: Unconfined + steps: + - name: prepare-buildkit-client + image: 127.0.0.1:5000/hwlab/buildkit:rootless + securityContext: + runAsUser: 0 + runAsGroup: 0 + volumeMounts: + - name: buildkit-bin + mountPath: /workspace/buildkit-bin + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: HWLAB_NODE_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_ALL_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: HOME + value: /tekton/home + - name: XDG_CONFIG_HOME + value: /tekton/home/.config + - name: publish + image: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + env: + - name: HTTP_PROXY + value: http://127.0.0.1:10808 + - name: HTTPS_PROXY + value: http://127.0.0.1:10808 + - name: ALL_PROXY + value: http://127.0.0.1:10808 + - name: NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: http_proxy + value: http://127.0.0.1:10808 + - name: https_proxy + value: http://127.0.0.1:10808 + - name: all_proxy + value: http://127.0.0.1:10808 + - name: no_proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.16.0.0/12,192.168.0.0/16,82.156.23.220,74.48.78.17,hyueapi.com,.hyueapi.com" + - name: HWLAB_NODE_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_ALL_PROXY_URL + value: http://127.0.0.1:10808 + - name: HWLAB_NODE_NO_PROXY + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: HOME + value: /tekton/home + - name: XDG_CONFIG_HOME + value: /tekton/home/.config + volumeMounts: + - name: service-work + mountPath: /workspace/service-work + - name: buildkit-bin + mountPath: /workspace/buildkit-bin + - name: buildkit-run + mountPath: /workspace/buildkit-run + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-cicd-timing\",schemaVersion:\"v1\",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:\"scripts/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor tool in node npm git python3 ssh curl; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -s /workspace/source/affected-services.json ] && ! node -e 'const fs=require(\"node:fs\"); const p=JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\",\"utf8\")); const service=process.argv[1]; const item=(p.entries||[]).find((entry)=>entry.serviceId===service); process.exit(item && item.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n echo '{\"event\":\"dependency-local-registry-probe-start\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"target\":\"http://127.0.0.1:5000/v2/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n echo '{\"event\":\"dependency-local-registry-probe\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"ok\":true,\"durationSeconds\":'\"$((registry_finished_at - registry_started_at))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-v03-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimePath,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --registry-prefix \"$(params.registry-prefix)\" --report \"/workspace/source/service-results/$(params.service-id).json\" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services \"$(params.service-id)\" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr \"$HWLAB_BUILDKIT_ADDR\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + imagePullPolicy: Always + params: + - name: revision + value: $(params.revision) + - name: lane + value: $(params.lane) + - name: catalog-path + value: $(params.catalog-path) + - name: image-tag-mode + value: $(params.image-tag-mode) + - name: registry-prefix + value: $(params.registry-prefix) + - name: service-id + value: hwlab-tasktree-worker + - name: base-image + value: $(params.base-image) + - name: build-cache-mode + value: $(params.build-cache-mode) - name: build-hwlab-cloud-web runAfter: - plan-artifacts @@ -2102,6 +2526,8 @@ spec: - build-hwlab-workbench-runtime - build-hwlab-user-billing - build-hwlab-project-management + - build-hwlab-tasktree-api + - build-hwlab-tasktree-worker - build-hwlab-cloud-web - build-hwlab-gateway - build-hwlab-edge-proxy diff --git a/cmd/hwlab-tasktree-api/main.ts b/cmd/hwlab-tasktree-api/main.ts new file mode 100644 index 00000000..1b6468ab --- /dev/null +++ b/cmd/hwlab-tasktree-api/main.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env bun +import { createTaskTreeDispatcher } from "../../internal/tasktree/dispatcher.ts"; +import { createTaskTreeHttpApp } from "../../internal/tasktree/http.ts"; +import { taskTreeRuntime } from "../../internal/tasktree/runtime.ts"; + +const runtime = taskTreeRuntime(); +const dispatch = createTaskTreeDispatcher(runtime); +const app = createTaskTreeHttpApp({ dispatch, close: () => runtime.store.close() }); +const host = process.env.TASKTREE_API_HOST || "0.0.0.0"; +const port = Number.parseInt(process.env.TASKTREE_API_PORT || process.env.PORT || "6673", 10); +const server = Bun.serve({ hostname: host, port, fetch: (request) => app.fetch(request) }); + +process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-tasktree-api", status: "listening", host, port: server.port })}\n`); +for (const signal of ["SIGINT", "SIGTERM"] as const) { + process.once(signal, async () => { + server.stop(true); + await app.close(); + process.exit(0); + }); +} diff --git a/cmd/hwlab-tasktree-worker/main.ts b/cmd/hwlab-tasktree-worker/main.ts new file mode 100644 index 00000000..9aa81aed --- /dev/null +++ b/cmd/hwlab-tasktree-worker/main.ts @@ -0,0 +1,39 @@ +#!/usr/bin/env bun +import { NativeConnection, Worker } from "@temporalio/worker"; +import path from "node:path"; + +import { createTaskTreeActivities } from "../../internal/tasktree/activities.ts"; +import { taskTreeRuntime } from "../../internal/tasktree/runtime.ts"; + +const runtime = taskTreeRuntime(); +if (!runtime.temporalAddress) throw new Error("TASKTREE_TEMPORAL_ADDRESS is required"); +await runtime.store.ensureSchema(); +const connection = await NativeConnection.connect({ address: runtime.temporalAddress }); +const worker = await Worker.create({ + connection, + namespace: runtime.temporalNamespace, + taskQueue: runtime.taskQueue, + workflowsPath: path.resolve(import.meta.dir, "../../internal/tasktree/workflows.ts"), + activities: createTaskTreeActivities(runtime.store) +}); +const healthPort = Number.parseInt(process.env.TASKTREE_WORKER_HEALTH_PORT || "6674", 10); +const health = Bun.serve({ + hostname: process.env.TASKTREE_WORKER_HEALTH_HOST || "0.0.0.0", + port: healthPort, + fetch(request) { + const pathname = new URL(request.url).pathname; + if (pathname !== "/health/live" && pathname !== "/health/ready") return Response.json({ ok: false, error: "not_found" }, { status: 404 }); + return Response.json({ ok: true, serviceId: "hwlab-tasktree-worker", namespace: runtime.temporalNamespace, taskQueue: runtime.taskQueue }); + } +}); + +process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-tasktree-worker", status: "started", namespace: runtime.temporalNamespace, taskQueue: runtime.taskQueue })}\n`); +for (const signal of ["SIGINT", "SIGTERM"] as const) process.once(signal, () => worker.shutdown()); +try { + await worker.run(); +} finally { + health.stop(true); + await connection.close(); + await runtime.store.close(); + process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-tasktree-worker", status: "stopped" })}\n`); +} diff --git a/config/hwlab-access-control/nav-profiles.yaml b/config/hwlab-access-control/nav-profiles.yaml index 15ec6180..9c072bef 100644 --- a/config/hwlab-access-control/nav-profiles.yaml +++ b/config/hwlab-access-control/nav-profiles.yaml @@ -14,3 +14,4 @@ spec: allowedIds: - workbench.code - project.mdtodo + - project.tasktree diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index 9c438bbd..223922d7 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -506,6 +506,8 @@ lanes: - hwlab-workbench-runtime - hwlab-user-billing - hwlab-project-management + - hwlab-tasktree-api + - hwlab-tasktree-worker - hwlab-cloud-web - hwlab-gateway - hwlab-edge-proxy @@ -515,6 +517,8 @@ lanes: hwlab-workbench-runtime: deploy/runtime/boot/hwlab-workbench-runtime.sh hwlab-user-billing: deploy/runtime/boot/hwlab-user-billing.sh hwlab-project-management: deploy/runtime/boot/hwlab-project-management.sh + hwlab-tasktree-api: deploy/runtime/boot/hwlab-tasktree-api.sh + hwlab-tasktree-worker: deploy/runtime/boot/hwlab-tasktree-worker.sh hwlab-cloud-web: deploy/runtime/boot/hwlab-cloud-web.sh hwlab-gateway: deploy/runtime/boot/hwlab-gateway.sh hwlab-edge-proxy: deploy/runtime/boot/hwlab-edge-proxy.sh @@ -630,6 +634,37 @@ lanes: env: TZ: Asia/Shanghai observable: true + hwlab-tasktree-api: + runtimeKind: bun-command + entrypoint: cmd/hwlab-tasktree-api/main.ts + artifactKind: bun-command + healthPath: /health/ready + healthPort: 6673 + componentPaths: + - cmd/hwlab-tasktree-api/ + - internal/tasktree/ + - tools/src/tasktree-cli.ts + - package.json + - package-lock.json + - deploy/runtime/boot/hwlab-tasktree-api.sh + env: + TZ: Asia/Shanghai + observable: true + hwlab-tasktree-worker: + runtimeKind: bun-command + entrypoint: cmd/hwlab-tasktree-worker/main.ts + artifactKind: bun-command + healthPath: /health/ready + healthPort: 6674 + componentPaths: + - cmd/hwlab-tasktree-worker/ + - internal/tasktree/ + - package.json + - package-lock.json + - deploy/runtime/boot/hwlab-tasktree-worker.sh + env: + TZ: Asia/Shanghai + observable: true hwlab-cloud-web: runtimeKind: cloud-web entrypoint: web/hwlab-cloud-web/index.html @@ -857,6 +892,7 @@ lanes: HWLAB_USER_BILLING_URL: http://hwlab-user-billing.hwlab-v03.svc.cluster.local:6670 HWLAB_WORKBENCH_RUNTIME_URL: http://hwlab-workbench-runtime.hwlab-v03.svc.cluster.local:6671 HWLAB_PROJECT_MANAGEMENT_URL: http://hwlab-project-management.hwlab-v03.svc.cluster.local:6672 + HWLAB_TASKTREE_URL: http://hwlab-tasktree-api.hwlab-v03.svc.cluster.local:6673 HWLAB_HWPOD_SPEC_REGISTRY_DIRS: /etc/hwlab/hwpod-specs HWLAB_HWPOD_NODE_WS_TOKEN: secretRef:hwlab-v03-hwpod-node-auth/token HWLAB_USER_BILLING_LOGIN_RETRY_ATTEMPTS: "2" @@ -964,7 +1000,31 @@ lanes: configMapName: hwlab-v03-project-management-bootstrap mountPath: /etc/hwlab/project-management readOnly: true + - serviceId: hwlab-tasktree-api + replicas: 1 + env: + TASKTREE_DATABASE_URL: secretRef:hwlab-tasktree-db/database-url + TASKTREE_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233 + TASKTREE_TEMPORAL_NAMESPACE: unidesk + TASKTREE_TEMPORAL_TASK_QUEUE: hwlab-v03-tasktree + TASKTREE_API_PORT: "6673" + OTEL_SERVICE_NAME: hwlab-tasktree-api + - serviceId: hwlab-tasktree-worker + replicas: 1 + env: + TASKTREE_DATABASE_URL: secretRef:hwlab-tasktree-db/database-url + TASKTREE_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233 + TASKTREE_TEMPORAL_NAMESPACE: unidesk + TASKTREE_TEMPORAL_TASK_QUEUE: hwlab-v03-tasktree + TASKTREE_WORKER_HEALTH_PORT: "6674" + OTEL_SERVICE_NAME: hwlab-tasktree-worker services: + - serviceId: hwlab-tasktree-api + namespace: hwlab-dev + healthPath: /health/live + - serviceId: hwlab-tasktree-worker + namespace: hwlab-dev + healthPath: /health/live - serviceId: hwlab-cloud-api namespace: hwlab-dev healthPath: /health/live diff --git a/deploy/k8s/base/services.yaml b/deploy/k8s/base/services.yaml index dac826e9..4055ed78 100644 --- a/deploy/k8s/base/services.yaml +++ b/deploy/k8s/base/services.yaml @@ -2,6 +2,34 @@ "apiVersion": "v1", "kind": "List", "items": [ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-tasktree-api", + "namespace": "hwlab-dev", + "labels": { "app.kubernetes.io/name": "hwlab-tasktree-api", "hwlab.pikastech.local/service-id": "hwlab-tasktree-api" } + }, + "spec": { + "type": "ClusterIP", + "selector": { "app.kubernetes.io/name": "hwlab-tasktree-api" }, + "ports": [{ "name": "http", "port": 6673, "targetPort": "http" }] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-tasktree-worker", + "namespace": "hwlab-dev", + "labels": { "app.kubernetes.io/name": "hwlab-tasktree-worker", "hwlab.pikastech.local/service-id": "hwlab-tasktree-worker" } + }, + "spec": { + "type": "ClusterIP", + "selector": { "app.kubernetes.io/name": "hwlab-tasktree-worker" }, + "ports": [{ "name": "http", "port": 6674, "targetPort": "http" }] + } + }, { "apiVersion": "v1", "kind": "Service", diff --git a/deploy/k8s/base/workloads.yaml b/deploy/k8s/base/workloads.yaml index ec474793..7cb5ad7a 100644 --- a/deploy/k8s/base/workloads.yaml +++ b/deploy/k8s/base/workloads.yaml @@ -2,6 +2,54 @@ "apiVersion": "v1", "kind": "List", "items": [ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-tasktree-api", + "namespace": "hwlab-dev", + "labels": { "app.kubernetes.io/name": "hwlab-tasktree-api", "hwlab.pikastech.local/service-id": "hwlab-tasktree-api" } + }, + "spec": { + "replicas": 0, + "selector": { "matchLabels": { "app.kubernetes.io/name": "hwlab-tasktree-api" } }, + "template": { + "metadata": { "labels": { "app.kubernetes.io/name": "hwlab-tasktree-api", "hwlab.pikastech.local/service-id": "hwlab-tasktree-api" } }, + "spec": { "containers": [{ + "name": "hwlab-tasktree-api", + "image": "127.0.0.1:5000/hwlab/hwlab-tasktree-api:dev", + "ports": [{ "name": "http", "containerPort": 6673 }], + "env": [{ "name": "TASKTREE_API_PORT", "value": "6673" }], + "readinessProbe": { "httpGet": { "path": "/health/ready", "port": "http" } }, + "livenessProbe": { "httpGet": { "path": "/health/live", "port": "http" } } + }] } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-tasktree-worker", + "namespace": "hwlab-dev", + "labels": { "app.kubernetes.io/name": "hwlab-tasktree-worker", "hwlab.pikastech.local/service-id": "hwlab-tasktree-worker" } + }, + "spec": { + "replicas": 0, + "selector": { "matchLabels": { "app.kubernetes.io/name": "hwlab-tasktree-worker" } }, + "template": { + "metadata": { "labels": { "app.kubernetes.io/name": "hwlab-tasktree-worker", "hwlab.pikastech.local/service-id": "hwlab-tasktree-worker" } }, + "spec": { "containers": [{ + "name": "hwlab-tasktree-worker", + "image": "127.0.0.1:5000/hwlab/hwlab-tasktree-worker:dev", + "ports": [{ "name": "http", "containerPort": 6674 }], + "env": [{ "name": "TASKTREE_WORKER_HEALTH_PORT", "value": "6674" }], + "readinessProbe": { "httpGet": { "path": "/health/ready", "port": "http" } }, + "livenessProbe": { "httpGet": { "path": "/health/live", "port": "http" } } + }] } + } + } + }, { "apiVersion": "v1", "kind": "PersistentVolumeClaim", diff --git a/deploy/runtime/boot/hwlab-tasktree-api.sh b/deploy/runtime/boot/hwlab-tasktree-api.sh new file mode 100755 index 00000000..91470d2d --- /dev/null +++ b/deploy/runtime/boot/hwlab-tasktree-api.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +export HWLAB_SERVICE_ID=hwlab-tasktree-api +export HWLAB_SERVICE_ENTRYPOINT=cmd/hwlab-tasktree-api/main.ts +export TASKTREE_API_PORT="${TASKTREE_API_PORT:-6673}" +export PORT="$TASKTREE_API_PORT" + +exec "${HWLAB_BUN_COMMAND:-bun}" run cmd/hwlab-tasktree-api/main.ts diff --git a/deploy/runtime/boot/hwlab-tasktree-worker.sh b/deploy/runtime/boot/hwlab-tasktree-worker.sh new file mode 100755 index 00000000..48493e6c --- /dev/null +++ b/deploy/runtime/boot/hwlab-tasktree-worker.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +export HWLAB_SERVICE_ID=hwlab-tasktree-worker +export HWLAB_SERVICE_ENTRYPOINT=cmd/hwlab-tasktree-worker/main.ts +export TASKTREE_WORKER_HEALTH_PORT="${TASKTREE_WORKER_HEALTH_PORT:-6674}" +export PORT="$TASKTREE_WORKER_HEALTH_PORT" + +exec "${HWLAB_BUN_COMMAND:-bun}" run cmd/hwlab-tasktree-worker/main.ts diff --git a/internal/cloud/server.ts b/internal/cloud/server.ts index 1c50cff3..7fa7576d 100644 --- a/internal/cloud/server.ts +++ b/internal/cloud/server.ts @@ -96,6 +96,7 @@ import { import { HWPOD_TOPOLOGY_CONTRACT_VERSION } from "./hwpod-topology-read-model.ts"; import { handleCaseRunHttp } from "./server-caserun-http.ts"; import { handleProjectManagementProxyHttp } from "./project-management-proxy.ts"; +import { handleTaskTreeProxyHttp } from "./tasktree-proxy.ts"; import { handleDashboardSummaryHttp } from "./server-dashboard-http.ts"; import { HWPOD_NODE_OPS, HWPOD_NODE_OPS_CONTRACT_VERSION } from "../../tools/src/hwpod-node-ops-contract.ts"; @@ -719,6 +720,10 @@ async function handleRestAdapter(request, response, url, options) { await handleProjectManagementProxyHttp(request, response, url, options); return; } + if (url.pathname === "/v1/tasktree" || url.pathname.startsWith("/v1/tasktree/")) { + await handleTaskTreeProxyHttp(request, response, url, options); + return; + } if (url.pathname === "/v1/users/me/profile" && request.method === "PATCH") { await handleUserBillingProfileHttp(request, response, options); @@ -1020,6 +1025,7 @@ function navIdForRestPath(pathname, method = "GET") { if (pathname.startsWith("/v1/admin/billing/")) return "admin.billing"; if (pathname === "/v1/skills" || pathname.startsWith("/v1/skills/")) return "system.skills"; if (pathname === "/v1/project-management" || pathname.startsWith("/v1/project-management/")) return "project.mdtodo"; + if (pathname === "/v1/tasktree" || pathname.startsWith("/v1/tasktree/")) return "project.tasktree"; if (pathname === "/v1/api-keys" || pathname === "/v1/api-keys/default" || pathname.startsWith("/v1/api-keys/")) return "user.apiKeys"; if (pathname === "/v1/users/me/profile" || pathname === "/v1/users/me/password") return "system.settings"; if (pathname === "/v1/workbench/debug/fake-sse" || pathname.startsWith("/v1/workbench/debug/fake-sse/") || pathname === "/v1/workbench/debug/kafka-sse" || pathname.startsWith("/v1/workbench/debug/kafka-sse/")) return "workbench.debug"; diff --git a/internal/cloud/tasktree-proxy.test.ts b/internal/cloud/tasktree-proxy.test.ts new file mode 100644 index 00000000..f897a748 --- /dev/null +++ b/internal/cloud/tasktree-proxy.test.ts @@ -0,0 +1,39 @@ +import assert from "node:assert/strict"; +import { test } from "bun:test"; + +import { handleTaskTreeProxyHttp } from "./tasktree-proxy.ts"; + +test("TaskTree proxy requires auth and forwards the unchanged API path", async () => { + const originalFetch = globalThis.fetch; + let upstreamUrl = ""; + globalThis.fetch = (async (input) => { + upstreamUrl = String(input); + return Response.json({ ok: true, operation: "group.list", data: { groups: [] } }); + }) as typeof fetch; + const response = captureResponse(); + try { + await handleTaskTreeProxyHttp( + { method: "GET", headers: {} }, + response, + new URL("http://cloud.test/v1/tasktree/groups"), + { + env: { HWLAB_TASKTREE_URL: "http://hwlab-tasktree-api.hwlab-v03.svc.cluster.local:6673" }, + accessController: { async authenticate() { return { ok: true, actor: { id: "usr_test" } }; } } + } + ); + assert.equal(upstreamUrl, "http://hwlab-tasktree-api.hwlab-v03.svc.cluster.local:6673/v1/tasktree/groups"); + assert.equal(response.status, 200); + assert.deepEqual(JSON.parse(response.body), { ok: true, operation: "group.list", data: { groups: [] } }); + } finally { + globalThis.fetch = originalFetch; + } +}); + +function captureResponse() { + return { + status: 0, + body: "", + writeHead(status: number) { this.status = status; }, + end(body: string) { this.body = body; } + }; +} diff --git a/internal/cloud/tasktree-proxy.ts b/internal/cloud/tasktree-proxy.ts new file mode 100644 index 00000000..8853bec4 --- /dev/null +++ b/internal/cloud/tasktree-proxy.ts @@ -0,0 +1,22 @@ +import { readBody, sendJson } from "./server-http-utils.ts"; + +export async function handleTaskTreeProxyHttp(request, response, url, options = {}) { + const env = options.env ?? process.env; + const baseUrl = String(env.HWLAB_TASKTREE_URL ?? "").trim(); + if (!baseUrl) return sendJson(response, 503, { ok: false, error: { code: "tasktree_service_unconfigured", message: "HWLAB_TASKTREE_URL is required" }, valuesRedacted: true }); + const auth = await options.accessController.authenticate(request, { required: true }); + if (!auth.ok) return sendJson(response, auth.status, auth); + try { + const upstreamUrl = new URL(`${url.pathname}${url.search}`, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`); + const headers = new Headers({ accept: "application/json", "x-hwlab-bridge-service": "hwlab-cloud-api" }); + const contentType = request.headers["content-type"]; + if (contentType) headers.set("content-type", Array.isArray(contentType) ? contentType[0] : contentType); + if (auth.actor?.id) headers.set("x-hwlab-actor-id", auth.actor.id); + const body = request.method === "GET" || request.method === "HEAD" ? undefined : await readBody(request, options.bodyLimitBytes); + const upstream = await fetch(upstreamUrl, { method: request.method, headers, body, signal: AbortSignal.timeout(Number(env.HWLAB_TASKTREE_PROXY_TIMEOUT_MS || 30000)) }); + response.writeHead(upstream.status, { "content-type": upstream.headers.get("content-type") || "application/json; charset=utf-8", "cache-control": "no-store", "x-hwlab-proxy-service": "hwlab-tasktree-api" }); + response.end(await upstream.text() || "\n"); + } catch (error) { + sendJson(response, 502, { ok: false, error: { code: "tasktree_proxy_failed", message: error?.message ?? String(error) }, valuesRedacted: true }); + } +} diff --git a/internal/tasktree/activities.ts b/internal/tasktree/activities.ts new file mode 100644 index 00000000..f55c8b68 --- /dev/null +++ b/internal/tasktree/activities.ts @@ -0,0 +1,12 @@ +import { TaskTreeStore } from "./store.ts"; + +export function createTaskTreeActivities(store: TaskTreeStore) { + return { + async recordTaskExecution(input: { taskId: string }) { + const task = await store.updateTask(input.taskId, { status: "completed" }); + if (!task) throw Object.assign(new Error("task was not found"), { code: "task_not_found" }); + const report = await store.createReport({ taskId: task.id, title: "Temporal execution report", body: `Task ${task.title} completed by Temporal worker.`, status: "succeeded" }); + return { taskId: task.id, reportId: report.id }; + } + }; +} diff --git a/internal/tasktree/contracts.ts b/internal/tasktree/contracts.ts new file mode 100644 index 00000000..b5be7d36 --- /dev/null +++ b/internal/tasktree/contracts.ts @@ -0,0 +1,71 @@ +export type TaskStatus = "pending" | "in_progress" | "completed" | "blocked"; +export type TaskKind = "task" | "subtask"; + +export type TaskGroup = { + id: string; + name: string; + description: string; + createdAt: string; + updatedAt: string; +}; + +export type TaskItem = { + id: string; + groupId: string; + parentId: string | null; + kind: TaskKind; + title: string; + description: string; + status: TaskStatus; + startAt: string | null; + dueAt: string | null; + sortOrder: number; + createdAt: string; + updatedAt: string; +}; + +export type Milestone = { + id: string; + groupId: string; + taskId: string | null; + title: string; + occursAt: string; + createdAt: string; +}; + +export type ExecutionReport = { + id: string; + taskId: string; + title: string; + body: string; + status: string; + createdAt: string; +}; + +export type Timeline = { + group: TaskGroup; + tasks: TaskItem[]; + milestones: Milestone[]; + reports: ExecutionReport[]; +}; + +export type TaskTreeCommand = + | { operation: "health" } + | { operation: "group.list" } + | { operation: "group.get"; groupId: string } + | { operation: "group.create"; name: string; description?: string } + | { operation: "group.delete"; groupId: string } + | { operation: "task.create"; groupId: string; title: string; description?: string; startAt?: string; dueAt?: string; parentId?: string } + | { operation: "task.update"; taskId: string; title?: string; description?: string; status?: TaskStatus; startAt?: string | null; dueAt?: string | null } + | { operation: "task.delete"; taskId: string } + | { operation: "milestone.create"; groupId: string; title: string; occursAt: string; taskId?: string } + | { operation: "report.create"; taskId: string; title: string; body: string; status?: string } + | { operation: "timeline.get"; groupId: string } + | { operation: "workflow.start"; taskId: string }; + +export type TaskTreeResult = { + ok: boolean; + operation: TaskTreeCommand["operation"]; + data?: unknown; + error?: { code: string; message: string; detail?: unknown }; +}; diff --git a/internal/tasktree/dispatcher.ts b/internal/tasktree/dispatcher.ts new file mode 100644 index 00000000..95b1e05c --- /dev/null +++ b/internal/tasktree/dispatcher.ts @@ -0,0 +1,55 @@ +import { Client, Connection } from "@temporalio/client"; + +import type { TaskTreeCommand, TaskTreeResult } from "./contracts.ts"; +import { TaskTreeStore } from "./store.ts"; + +export type TaskTreeDispatcherOptions = { + store: TaskTreeStore; + temporalAddress?: string; + temporalNamespace?: string; + taskQueue?: string; +}; + +export function createTaskTreeDispatcher(options: TaskTreeDispatcherOptions) { + const { store } = options; + return async function dispatch(command: TaskTreeCommand): Promise { + try { + const operation = command.operation; + let data: unknown; + if (operation === "health") data = await store.health(); + else if (operation === "group.list") data = { groups: await store.listGroups() }; + else if (operation === "group.get") data = required(await store.getGroup(command.groupId), "group_not_found", "taskgroup was not found"); + else if (operation === "group.create") data = await store.createGroup(requiredText(command.name, "name"), command.description); + else if (operation === "group.delete") data = { deleted: await store.deleteGroup(command.groupId) }; + else if (operation === "task.create") data = await store.createTask({ ...command, title: requiredText(command.title, "title") }); + else if (operation === "task.update") data = required(await store.updateTask(command.taskId, command), "task_not_found", "task was not found"); + else if (operation === "task.delete") data = { deleted: await store.deleteTask(command.taskId) }; + else if (operation === "milestone.create") data = await store.createMilestone({ ...command, title: requiredText(command.title, "title"), occursAt: validDate(command.occursAt, "occursAt") }); + else if (operation === "report.create") data = await store.createReport({ ...command, title: requiredText(command.title, "title"), body: requiredText(command.body, "body") }); + else if (operation === "timeline.get") data = required(await store.timeline(command.groupId), "group_not_found", "taskgroup was not found"); + else if (operation === "workflow.start") data = await startTaskWorkflow(options, command.taskId); + else throw codedError("unsupported_operation", `unsupported operation: ${String(operation)}`); + return { ok: true, operation, data }; + } catch (error: any) { + return { ok: false, operation: command.operation, error: { code: error?.code ?? "tasktree_error", message: error?.message ?? String(error) } }; + } + }; +} + +async function startTaskWorkflow(options: TaskTreeDispatcherOptions, taskId: string) { + if (!options.temporalAddress) throw codedError("temporal_address_required", "Temporal address is required for workflow.start"); + const connection = await Connection.connect({ address: options.temporalAddress }); + try { + const client = new Client({ connection, namespace: options.temporalNamespace ?? "unidesk" }); + const workflowId = `tasktree-${taskId}-${Date.now()}`; + const handle = await client.workflow.start("taskExecutionWorkflow", { taskQueue: options.taskQueue ?? "hwlab-v03-tasktree", workflowId, args: [{ taskId }] }); + return { workflowId, runId: handle.firstExecutionRunId, taskId, result: await handle.result() }; + } finally { + await connection.close(); + } +} + +function required(value: T | null, code: string, message: string): T { if (value === null) throw codedError(code, message); return value; } +function requiredText(value: unknown, field: string): string { const text = String(value ?? "").trim(); if (!text) throw codedError("invalid_input", `${field} is required`); return text; } +function validDate(value: unknown, field: string): string { const date = new Date(String(value ?? "")); if (Number.isNaN(date.valueOf())) throw codedError("invalid_input", `${field} must be an ISO date`); return date.toISOString(); } +function codedError(code: string, message: string) { return Object.assign(new Error(message), { code }); } diff --git a/internal/tasktree/http.ts b/internal/tasktree/http.ts new file mode 100644 index 00000000..f19c4872 --- /dev/null +++ b/internal/tasktree/http.ts @@ -0,0 +1,35 @@ +import type { TaskTreeCommand } from "./contracts.ts"; + +export function createTaskTreeHttpApp(options: { dispatch: (command: TaskTreeCommand) => Promise; close?: () => Promise }) { + return { + async fetch(request: Request): Promise { + const url = new URL(request.url); + if (url.pathname === "/health/live") return json(200, { ok: true, serviceId: "hwlab-tasktree-api", status: "live" }); + if (url.pathname === "/health/ready") return resultResponse(await options.dispatch({ operation: "health" })); + if (url.pathname === "/v1/tasktree/groups" && request.method === "GET") return resultResponse(await options.dispatch({ operation: "group.list" })); + if (url.pathname === "/v1/tasktree/groups" && request.method === "POST") { + const body = await bodyObject(request); + return resultResponse(await options.dispatch({ operation: "group.create", name: text(body.name), description: text(body.description) }), 201); + } + const timeline = /^\/v1\/tasktree\/groups\/([^/]+)\/timeline$/u.exec(url.pathname); + if (timeline && request.method === "GET") return resultResponse(await options.dispatch({ operation: "timeline.get", groupId: decodeURIComponent(timeline[1]) })); + if (url.pathname === "/v1/tasktree/commands" && request.method === "POST") { + return resultResponse(await options.dispatch(await bodyObject(request) as TaskTreeCommand)); + } + return json(404, { ok: false, error: { code: "not_found", message: "TaskTree route was not found" } }); + }, + close: options.close ?? (async () => {}) + }; +} + +function resultResponse(result: any, successStatus = 200) { + const status = result?.ok === true ? successStatus : result?.error?.code?.includes("not_found") ? 404 : 400; + return json(status, result); +} +async function bodyObject(request: Request): Promise> { + const body = await request.json().catch(() => null); + if (!body || typeof body !== "object" || Array.isArray(body)) throw Object.assign(new Error("JSON object body is required"), { code: "invalid_json" }); + return body as Record; +} +function text(value: unknown) { return String(value ?? ""); } +function json(status: number, body: unknown) { return Response.json(body, { status, headers: { "cache-control": "no-store" } }); } diff --git a/internal/tasktree/runtime.ts b/internal/tasktree/runtime.ts new file mode 100644 index 00000000..874f462d --- /dev/null +++ b/internal/tasktree/runtime.ts @@ -0,0 +1,12 @@ +import { TaskTreeStore } from "./store.ts"; + +export function taskTreeRuntime(env: Record = process.env) { + const databaseUrl = env.TASKTREE_DATABASE_URL || env.DATABASE_URL || ""; + const store = new TaskTreeStore(databaseUrl); + return { + store, + temporalAddress: env.TASKTREE_TEMPORAL_ADDRESS || env.TEMPORAL_ADDRESS || "", + temporalNamespace: env.TASKTREE_TEMPORAL_NAMESPACE || "unidesk", + taskQueue: env.TASKTREE_TEMPORAL_TASK_QUEUE || "hwlab-v03-tasktree" + }; +} diff --git a/internal/tasktree/store.ts b/internal/tasktree/store.ts new file mode 100644 index 00000000..a7da270c --- /dev/null +++ b/internal/tasktree/store.ts @@ -0,0 +1,164 @@ +import { randomUUID } from "node:crypto"; +import pg from "pg"; + +import type { ExecutionReport, Milestone, TaskGroup, TaskItem, TaskStatus, Timeline } from "./contracts.ts"; + +const { Pool } = pg; + +const schema = [ + `CREATE TABLE IF NOT EXISTS tasktree_schema_migrations (migration_id TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT now())`, + `CREATE TABLE IF NOT EXISTS tasktree_groups ( + id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now())`, + `CREATE TABLE IF NOT EXISTS tasktree_tasks ( + id TEXT PRIMARY KEY, group_id TEXT NOT NULL REFERENCES tasktree_groups(id) ON DELETE CASCADE, + parent_id TEXT REFERENCES tasktree_tasks(id) ON DELETE CASCADE, kind TEXT NOT NULL CHECK (kind IN ('task','subtask')), + title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending','in_progress','completed','blocked')), + start_at TIMESTAMPTZ, due_at TIMESTAMPTZ, sort_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CHECK (due_at IS NULL OR start_at IS NULL OR due_at >= start_at))`, + `CREATE TABLE IF NOT EXISTS tasktree_milestones ( + id TEXT PRIMARY KEY, group_id TEXT NOT NULL REFERENCES tasktree_groups(id) ON DELETE CASCADE, + task_id TEXT REFERENCES tasktree_tasks(id) ON DELETE CASCADE, title TEXT NOT NULL, + occurs_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now())`, + `CREATE TABLE IF NOT EXISTS tasktree_execution_reports ( + id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasktree_tasks(id) ON DELETE CASCADE, + title TEXT NOT NULL, body TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'succeeded', + created_at TIMESTAMPTZ NOT NULL DEFAULT now())`, + `CREATE INDEX IF NOT EXISTS idx_tasktree_tasks_group_order ON tasktree_tasks(group_id, sort_order, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_tasktree_tasks_parent ON tasktree_tasks(parent_id)`, + `CREATE INDEX IF NOT EXISTS idx_tasktree_reports_task ON tasktree_execution_reports(task_id, created_at DESC)` +]; + +export class TaskTreeStore { + private readonly pool: pg.Pool; + private ready = false; + + constructor(databaseUrl: string) { + if (!databaseUrl) throw new Error("TASKTREE_DATABASE_URL or DATABASE_URL is required"); + this.pool = new Pool({ connectionString: databaseUrl, max: 4, connectionTimeoutMillis: 5000, ssl: { rejectUnauthorized: false } }); + } + + async ensureSchema() { + if (this.ready) return; + const client = await this.pool.connect(); + try { + await client.query("BEGIN"); + for (const statement of schema) await client.query(statement); + await client.query("INSERT INTO tasktree_schema_migrations (migration_id) VALUES ($1) ON CONFLICT DO NOTHING", ["tasktree-20260716-v1"]); + await client.query("COMMIT"); + this.ready = true; + } catch (error) { + await client.query("ROLLBACK").catch(() => {}); + throw error; + } finally { + client.release(); + } + } + + async health() { + await this.ensureSchema(); + const result = await this.pool.query("SELECT current_database() AS database, now() AS now"); + return { storage: "native-postgresql", schemaReady: true, database: result.rows[0]?.database, checkedAt: result.rows[0]?.now }; + } + + async listGroups(): Promise { + await this.ensureSchema(); + const result = await this.pool.query("SELECT * FROM tasktree_groups ORDER BY updated_at DESC, name"); + return result.rows.map(groupRow); + } + + async getGroup(id: string): Promise { + await this.ensureSchema(); + const result = await this.pool.query("SELECT * FROM tasktree_groups WHERE id=$1", [id]); + return result.rows[0] ? groupRow(result.rows[0]) : null; + } + + async createGroup(name: string, description = ""): Promise { + await this.ensureSchema(); + const result = await this.pool.query("INSERT INTO tasktree_groups (id,name,description) VALUES ($1,$2,$3) RETURNING *", [`tg_${randomUUID()}`, name, description]); + return groupRow(result.rows[0]); + } + + async deleteGroup(id: string): Promise { + await this.ensureSchema(); + return (await this.pool.query("DELETE FROM tasktree_groups WHERE id=$1", [id])).rowCount === 1; + } + + async createTask(input: { groupId: string; title: string; description?: string; startAt?: string; dueAt?: string; parentId?: string }): Promise { + await this.ensureSchema(); + let kind = "task"; + if (input.parentId) { + const parent = await this.pool.query("SELECT group_id,parent_id FROM tasktree_tasks WHERE id=$1", [input.parentId]); + if (!parent.rows[0]) throw domainError("parent_not_found", "parent task was not found"); + if (parent.rows[0].group_id !== input.groupId) throw domainError("parent_group_mismatch", "parent task belongs to another taskgroup"); + if (parent.rows[0].parent_id) throw domainError("maximum_depth_exceeded", "TaskTree supports task and subtask only"); + kind = "subtask"; + } + const result = await this.pool.query( + `INSERT INTO tasktree_tasks (id,group_id,parent_id,kind,title,description,start_at,due_at,sort_order) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,(SELECT COALESCE(MAX(sort_order),-1)+1 FROM tasktree_tasks WHERE group_id=$2)) RETURNING *`, + [`tt_${randomUUID()}`, input.groupId, input.parentId ?? null, kind, input.title, input.description ?? "", input.startAt ?? null, input.dueAt ?? null] + ); + return taskRow(result.rows[0]); + } + + async updateTask(id: string, patch: { title?: string; description?: string; status?: TaskStatus; startAt?: string | null; dueAt?: string | null }): Promise { + await this.ensureSchema(); + const current = await this.pool.query("SELECT * FROM tasktree_tasks WHERE id=$1", [id]); + if (!current.rows[0]) return null; + const row = taskRow(current.rows[0]); + const result = await this.pool.query( + `UPDATE tasktree_tasks SET title=$2,description=$3,status=$4,start_at=$5,due_at=$6,updated_at=now() WHERE id=$1 RETURNING *`, + [id, patch.title ?? row.title, patch.description ?? row.description, patch.status ?? row.status, + patch.startAt === undefined ? row.startAt : patch.startAt, patch.dueAt === undefined ? row.dueAt : patch.dueAt] + ); + return taskRow(result.rows[0]); + } + + async deleteTask(id: string): Promise { + await this.ensureSchema(); + return (await this.pool.query("DELETE FROM tasktree_tasks WHERE id=$1", [id])).rowCount === 1; + } + + async createMilestone(input: { groupId: string; title: string; occursAt: string; taskId?: string }): Promise { + await this.ensureSchema(); + const result = await this.pool.query( + "INSERT INTO tasktree_milestones (id,group_id,task_id,title,occurs_at) VALUES ($1,$2,$3,$4,$5) RETURNING *", + [`tm_${randomUUID()}`, input.groupId, input.taskId ?? null, input.title, input.occursAt] + ); + return milestoneRow(result.rows[0]); + } + + async createReport(input: { taskId: string; title: string; body: string; status?: string }): Promise { + await this.ensureSchema(); + const result = await this.pool.query( + "INSERT INTO tasktree_execution_reports (id,task_id,title,body,status) VALUES ($1,$2,$3,$4,$5) RETURNING *", + [`tr_${randomUUID()}`, input.taskId, input.title, input.body, input.status ?? "succeeded"] + ); + return reportRow(result.rows[0]); + } + + async timeline(groupId: string): Promise { + await this.ensureSchema(); + const group = await this.getGroup(groupId); + if (!group) return null; + const [tasks, milestones, reports] = await Promise.all([ + this.pool.query("SELECT * FROM tasktree_tasks WHERE group_id=$1 ORDER BY sort_order,created_at", [groupId]), + this.pool.query("SELECT * FROM tasktree_milestones WHERE group_id=$1 ORDER BY occurs_at", [groupId]), + this.pool.query("SELECT r.* FROM tasktree_execution_reports r JOIN tasktree_tasks t ON t.id=r.task_id WHERE t.group_id=$1 ORDER BY r.created_at DESC", [groupId]) + ]); + return { group, tasks: tasks.rows.map(taskRow), milestones: milestones.rows.map(milestoneRow), reports: reports.rows.map(reportRow) }; + } + + async close() { await this.pool.end(); } +} + +function groupRow(row: any): TaskGroup { return { id: row.id, name: row.name, description: row.description, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) }; } +function taskRow(row: any): TaskItem { return { id: row.id, groupId: row.group_id, parentId: row.parent_id, kind: row.kind, title: row.title, description: row.description, status: row.status, startAt: nullableIso(row.start_at), dueAt: nullableIso(row.due_at), sortOrder: row.sort_order, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) }; } +function milestoneRow(row: any): Milestone { return { id: row.id, groupId: row.group_id, taskId: row.task_id, title: row.title, occursAt: iso(row.occurs_at), createdAt: iso(row.created_at) }; } +function reportRow(row: any): ExecutionReport { return { id: row.id, taskId: row.task_id, title: row.title, body: row.body, status: row.status, createdAt: iso(row.created_at) }; } +function iso(value: unknown): string { return new Date(value as any).toISOString(); } +function nullableIso(value: unknown): string | null { return value == null ? null : iso(value); } +function domainError(code: string, message: string) { return Object.assign(new Error(message), { code }); } diff --git a/internal/tasktree/tasktree.test.ts b/internal/tasktree/tasktree.test.ts new file mode 100644 index 00000000..ee832e28 --- /dev/null +++ b/internal/tasktree/tasktree.test.ts @@ -0,0 +1,62 @@ +import assert from "node:assert/strict"; +import { test } from "bun:test"; + +import { createTaskTreeDispatcher } from "./dispatcher.ts"; +import { createTaskTreeHttpApp } from "./http.ts"; +import { TaskTreeStore } from "./store.ts"; + +test("HTTP command endpoint returns the same dispatcher DTO", async () => { + const group = { + id: "tg_contract", + name: "Contract", + description: "", + createdAt: "2026-07-16T00:00:00.000Z", + updatedAt: "2026-07-16T00:00:00.000Z" + }; + const store = { + async listGroups() { return [group]; } + }; + const dispatch = createTaskTreeDispatcher({ store: store as TaskTreeStore }); + const expected = await dispatch({ operation: "group.list" }); + const app = createTaskTreeHttpApp({ dispatch }); + const response = await app.fetch(new Request("http://tasktree/v1/tasktree/commands", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ operation: "group.list" }) + })); + + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), expected); +}); + +test("native PostgreSQL store exposes timeline DTO and rejects a third task level", async () => { + const databaseUrl = process.env.TASKTREE_DATABASE_URL; + assert.ok(databaseUrl, "TASKTREE_DATABASE_URL is required for the TaskTree store integration test"); + const store = new TaskTreeStore(databaseUrl); + const group = await store.createGroup(`TaskTree test ${Date.now()}`); + try { + const task = await store.createTask({ + groupId: group.id, + title: "Root task", + startAt: "2026-07-16T00:00:00.000Z", + dueAt: "2026-07-18T00:00:00.000Z" + }); + const subtask = await store.createTask({ groupId: group.id, parentId: task.id, title: "Subtask" }); + await store.createMilestone({ groupId: group.id, taskId: task.id, title: "Review", occursAt: "2026-07-17T00:00:00.000Z" }); + await store.createReport({ taskId: subtask.id, title: "Execution", body: "passed" }); + + await assert.rejects( + store.createTask({ groupId: group.id, parentId: subtask.id, title: "Unsupported third level" }), + (error: any) => error?.code === "maximum_depth_exceeded" + ); + const timeline = await store.timeline(group.id); + assert.equal(timeline?.tasks.length, 2); + assert.equal(timeline?.tasks[0]?.kind, "task"); + assert.equal(timeline?.tasks[1]?.kind, "subtask"); + assert.equal(timeline?.milestones.length, 1); + assert.equal(timeline?.reports.length, 1); + } finally { + await store.deleteGroup(group.id); + await store.close(); + } +}); diff --git a/internal/tasktree/workflows.ts b/internal/tasktree/workflows.ts new file mode 100644 index 00000000..0c9c4444 --- /dev/null +++ b/internal/tasktree/workflows.ts @@ -0,0 +1,11 @@ +import { proxyActivities } from "@temporalio/workflow"; + +const { recordTaskExecution } = proxyActivities<{ recordTaskExecution(input: { taskId: string }): Promise<{ taskId: string; reportId: string }> }>({ + startToCloseTimeout: "30 seconds", + retry: { maximumAttempts: 3 } +}); + +export async function taskExecutionWorkflow(input: { taskId: string }) { + const result = await recordTaskExecution(input); + return { status: "completed", ...result }; +} diff --git a/package-lock.json b/package-lock.json index 74b3c094..079638ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,490 @@ "version": "0.0.0-l0", "dependencies": { "@openai/codex": "^0.128.0", + "@swc/wasm": "^1.15.43", + "@temporalio/activity": "^1.20.3", + "@temporalio/client": "^1.20.3", + "@temporalio/worker": "^1.20.3", + "@temporalio/workflow": "^1.20.3", "fzstd": "0.1.1", "kafkajs": "^2.2.4", + "loader-utils": "^3.3.1", "pg": "^8.21.0", "playwright": "1.59.1", "yaml": "^2.8.3" }, "devDependencies": { "typescript": "^5.9.3" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.64.0.tgz", + "integrity": "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.64.0.tgz", + "integrity": "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==", + "dependencies": { + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.64.0.tgz", + "integrity": "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==", + "dependencies": { + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.64.0.tgz", + "integrity": "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.64.0.tgz", + "integrity": "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.64.0.tgz", + "integrity": "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "glob-to-regex.js": "^1.0.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.64.0.tgz", + "integrity": "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.64.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.64.0.tgz", + "integrity": "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, "node_modules/@openai/codex": { @@ -141,6 +617,897 @@ "node": ">=16" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==" + }, + "node_modules/@swc/core": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/types": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@swc/wasm": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.43.tgz", + "integrity": "sha512-jYqeckrzZGAU+9OSfmL15MWfhkKWRCC8QMssL/MZu/MaIP76mU3VHjzqxlwKIz9DOSR/9jCqi1+QlWE0ILNehA==" + }, + "node_modules/@temporalio/activity": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/activity/-/activity-1.20.3.tgz", + "integrity": "sha512-7jtIkCkUe6wwVrnlCd/7DmnVcu1/r4KFvVHvPVszd/EXAz1ziYfsjhenLfxWIvyxSIU9v3tC96uHODN3ABdv4A==", + "dependencies": { + "@temporalio/client": "1.20.3", + "@temporalio/common": "1.20.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/client": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/client/-/client-1.20.3.tgz", + "integrity": "sha512-RmEX0Z7h3mWq8PMqeDSbbDZPK8IweajGnRsghiJI0fnaWx61YeW8bhAAKfD/iSop+0PM0oe0KaW5XPAKEY4XEw==", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.20.3", + "@temporalio/proto": "1.20.3", + "abort-controller": "^3.0.0", + "long": "^5.2.3", + "nexus-rpc": "^0.0.2", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/common": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.20.3.tgz", + "integrity": "sha512-VM9GyUAa7tl413nM24mcBhUfUeqcGazmDkx/GfDA0c+KuEVusclMg9eqa63EDv/rguC/YhWaXu6q5Kl8+99GSw==", + "dependencies": { + "@temporalio/proto": "1.20.3", + "long": "^5.2.3", + "ms": "3.0.0-canary.1", + "nexus-rpc": "^0.0.2", + "proto3-json-serializer": "^2.0.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/core-bridge": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/core-bridge/-/core-bridge-1.20.3.tgz", + "integrity": "sha512-i7hu6lg6bm2esyYYDwHh0XmGkOWSL5iicLjC7DNRU4b4As/dKcRxYMxNY6dXtKzbM94xhmX6Fh4j5/fxen/4kw==", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.20.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/nexus": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/nexus/-/nexus-1.20.3.tgz", + "integrity": "sha512-OSl73enJ9M8OMk2QaGHr4cQ1DDLEkjMhU1Z3tBt2zbWkKDs2IHKkl1wU/rMeXg7AJf5uqHcp209SBaDN/rqDiA==", + "dependencies": { + "@temporalio/client": "1.20.3", + "@temporalio/common": "1.20.3", + "@temporalio/proto": "1.20.3", + "long": "^5.2.3", + "nexus-rpc": "^0.0.2" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/proto": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.20.3.tgz", + "integrity": "sha512-RdbHC3zH+N0QqG1Q38h2LilFIQ3lbGLVmLYCyNcV+PPfzrtrZ8pwBACpSGwEuF7WWFziLAhJ2oMVXTnmXY5ocQ==", + "dependencies": { + "long": "^5.2.3", + "protobufjs": "^7.6.4" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/worker": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.20.3.tgz", + "integrity": "sha512-29W39KxGoz8QfiELT5al1jr+nnZEnpWCjr5gYT2a6ZlXr2WKxbJw4rRBy+OyGwHs5kGT0pPYayYvwcDTvWRpQQ==", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@swc/core": "^1.3.102", + "@temporalio/activity": "1.20.3", + "@temporalio/client": "1.20.3", + "@temporalio/common": "1.20.3", + "@temporalio/core-bridge": "1.20.3", + "@temporalio/nexus": "1.20.3", + "@temporalio/proto": "1.20.3", + "@temporalio/workflow": "1.20.3", + "heap-js": "^2.6.0", + "memfs": "^4.6.0", + "nexus-rpc": "^0.0.2", + "protobufjs": "^7.6.4", + "rxjs": "^7.8.1", + "source-map": "^0.7.4", + "source-map-loader": "^5.0.0", + "supports-color": "^8.1.1", + "swc-loader": "^0.2.3", + "unionfs": "^4.5.1", + "webpack": "^5.108.4" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/workflow": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/@temporalio/workflow/-/workflow-1.20.3.tgz", + "integrity": "sha512-6LRfnWp3HcvqvKFwbOhs5iDQkHNIM5Rxtxhsi2E5ViPDvJMBqlQ0sQ4oRo5SEgr5Bri650i7GfEQQJMVl7xoKg==", + "dependencies": { + "@temporalio/common": "1.20.3", + "@temporalio/proto": "1.20.3", + "nexus-rpc": "^0.0.2" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -160,6 +1527,95 @@ "resolved": "https://registry.npmjs.org/fzstd/-/fzstd-0.1.1.tgz", "integrity": "sha512-dkuVSOKKwh3eas5VkJy1AW1vFpet8TA/fGmVA5krThl8YcOVE/8ZIoEA1+U1vEn5ckxxhLirSdY837azmbaNHA==" }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/heap-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz", + "integrity": "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/kafkajs": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", @@ -169,6 +1625,165 @@ "node": ">=14.0.0" } }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/memfs": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.64.0.tgz", + "integrity": "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==", + "dependencies": { + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-to-fsa": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/nexus-rpc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.2.tgz", + "integrity": "sha512-IWjIExdVYlmwXuzHdY/Q3lXCv1gbqoAXPazQhy2w4Xgtgha3H0OOujEESVPQcFUFMWm+pAk2gKnb57g8S41JZg==", + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "engines": { + "node": ">=18" + } + }, "node_modules/pg": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", @@ -258,6 +1873,11 @@ "split2": "^4.1.0" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, "node_modules/playwright": { "version": "1.59.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", @@ -327,6 +1947,138 @@ "node": ">=0.10.0" } }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -336,6 +2088,120 @@ "node": ">= 10.x" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/swc-loader": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.7.tgz", + "integrity": "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==", + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -349,6 +2215,139 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" + }, + "node_modules/unionfs": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.6.0.tgz", + "integrity": "sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA==", + "dependencies": { + "fs-monkey": "^1.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.22.2", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -358,6 +2357,14 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yaml": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", @@ -371,6 +2378,31 @@ "funding": { "url": "https://github.com/sponsors/eemeli" } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 5ef79cec..cfa99dea 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,14 @@ }, "dependencies": { "@openai/codex": "^0.128.0", + "@swc/wasm": "^1.15.43", + "@temporalio/activity": "^1.20.3", + "@temporalio/client": "^1.20.3", + "@temporalio/worker": "^1.20.3", + "@temporalio/workflow": "^1.20.3", "fzstd": "0.1.1", "kafkajs": "^2.2.4", + "loader-utils": "^3.3.1", "pg": "^8.21.0", "playwright": "1.59.1", "yaml": "^2.8.3" diff --git a/tools/hwlab-cli/bin/hwlab-cli.ts b/tools/hwlab-cli/bin/hwlab-cli.ts index af396967..6b4c5493 100644 --- a/tools/hwlab-cli/bin/hwlab-cli.ts +++ b/tools/hwlab-cli/bin/hwlab-cli.ts @@ -2,7 +2,17 @@ import { main } from "../../src/hwlab-cli-lib.ts"; const argv = process.argv.slice(2); -if (argv[0] === "kafka") { +if (argv[0] === "tasktree") { + const { runTaskTreeCli } = await import("../../src/tasktree-cli.ts"); + try { + const result = await runTaskTreeCli(argv.slice(1)); + console.log(JSON.stringify(result, null, 2)); + process.exitCode = result?.ok === false ? 1 : 0; + } catch (error) { + console.log(JSON.stringify({ ok: false, operation: "tasktree", error: { code: (error as any)?.code ?? "tasktree_cli_error", message: (error as any)?.message ?? String(error) } }, null, 2)); + process.exitCode = 1; + } +} else if (argv[0] === "kafka") { const { mainKafkaCli } = await import("../../src/hwlab-cli/kafka-regenerate.ts"); await mainKafkaCli(argv.slice(1)); } else if (argv[0] === "case" && argv[1] === "audit") { diff --git a/tools/src/tasktree-cli.test.ts b/tools/src/tasktree-cli.test.ts new file mode 100644 index 00000000..3377574a --- /dev/null +++ b/tools/src/tasktree-cli.test.ts @@ -0,0 +1,25 @@ +import assert from "node:assert/strict"; +import { test } from "bun:test"; + +import { runTaskTreeCli } from "./tasktree-cli.ts"; + +test("--overapi preserves the command DTO and only changes transport", async () => { + const originalFetch = globalThis.fetch; + let received: unknown; + globalThis.fetch = (async (input, init) => { + assert.equal(String(input), "http://tasktree.test/v1/tasktree/commands"); + received = JSON.parse(String(init?.body)); + return Response.json({ ok: true, operation: "timeline.get", data: { group: { id: "tg_1" }, tasks: [], milestones: [], reports: [] } }); + }) as typeof fetch; + try { + const result = await runTaskTreeCli(["timeline", "--group", "tg_1", "--overapi"], { + HWLAB_TASKTREE_API_URL: "http://tasktree.test" + }); + assert.deepEqual(received, { operation: "timeline.get", groupId: "tg_1" }); + assert.equal(result.transport, "api"); + assert.equal(result.httpStatus, 200); + assert.equal(result.operation, "timeline.get"); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tools/src/tasktree-cli.ts b/tools/src/tasktree-cli.ts new file mode 100644 index 00000000..a11d804c --- /dev/null +++ b/tools/src/tasktree-cli.ts @@ -0,0 +1,81 @@ +import { readFile } from "node:fs/promises"; + +import type { TaskStatus, TaskTreeCommand } from "../../internal/tasktree/contracts.ts"; +import { createTaskTreeDispatcher } from "../../internal/tasktree/dispatcher.ts"; +import { taskTreeRuntime } from "../../internal/tasktree/runtime.ts"; + +export async function runTaskTreeCli(argv: string[], env: Record = process.env) { + const parsed = parse(argv); + if (parsed.help || parsed.positionals.length === 0) return help(); + const command = await commandFrom(parsed); + if (parsed.overapi) return overApi(command, env, parsed); + const runtime = taskTreeRuntime(env); + try { + return await createTaskTreeDispatcher(runtime)(command); + } finally { + await runtime.store.close(); + } +} + +function help() { + return { + ok: true, + operation: "help", + mode: "local-dispatcher-default", + usage: [ + "hwlab-cli tasktree health [--overapi]", + "hwlab-cli tasktree group list|get|create|delete [--group ID] [--name TEXT]", + "hwlab-cli tasktree task create|update|delete --group ID|--task ID [--parent ID] --title TEXT --start ISO --due ISO --status STATUS", + "hwlab-cli tasktree milestone create --group ID [--task ID] --title TEXT --at ISO", + "hwlab-cli tasktree report create --task ID --title TEXT (--body TEXT|--body-file PATH)", + "hwlab-cli tasktree timeline --group ID [--overapi]", + "hwlab-cli tasktree workflow start --task ID" + ], + transportContract: "--overapi only changes transport; commands and options are identical" + }; +} + +async function commandFrom(parsed: Parsed): Promise { + const [group, action = ""] = parsed.positionals; + if (group === "health") return { operation: "health" }; + if (group === "timeline") return { operation: "timeline.get", groupId: required(parsed, "group") }; + if (group === "group" && action === "list") return { operation: "group.list" }; + if (group === "group" && action === "get") return { operation: "group.get", groupId: required(parsed, "group") }; + if (group === "group" && action === "create") return { operation: "group.create", name: required(parsed, "name"), description: parsed.values.description }; + if (group === "group" && action === "delete") return { operation: "group.delete", groupId: required(parsed, "group") }; + if (group === "task" && action === "create") return { operation: "task.create", groupId: required(parsed, "group"), parentId: parsed.values.parent, title: required(parsed, "title"), description: parsed.values.description, startAt: parsed.values.start, dueAt: parsed.values.due }; + if (group === "task" && action === "update") return { operation: "task.update", taskId: required(parsed, "task"), title: parsed.values.title, description: parsed.values.description, status: parsed.values.status as TaskStatus | undefined, startAt: parsed.values.start, dueAt: parsed.values.due }; + if (group === "task" && action === "delete") return { operation: "task.delete", taskId: required(parsed, "task") }; + if (group === "milestone" && action === "create") return { operation: "milestone.create", groupId: required(parsed, "group"), taskId: parsed.values.task, title: required(parsed, "title"), occursAt: required(parsed, "at") }; + if (group === "report" && action === "create") return { operation: "report.create", taskId: required(parsed, "task"), title: required(parsed, "title"), body: parsed.values.body ?? await readFile(required(parsed, "body-file"), "utf8"), status: parsed.values.status }; + if (group === "workflow" && action === "start") return { operation: "workflow.start", taskId: required(parsed, "task") }; + throw new Error(`unsupported TaskTree command: ${group} ${action}`.trim()); +} + +async function overApi(command: TaskTreeCommand, env: Record, parsed: Parsed) { + const baseUrl = parsed.values["api-url"] || env.HWLAB_TASKTREE_API_URL; + if (!baseUrl) throw new Error("HWLAB_TASKTREE_API_URL or --api-url is required with --overapi"); + const response = await fetch(`${baseUrl.replace(/\/$/u, "")}/v1/tasktree/commands`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(command) }); + const body = await response.json().catch(() => null); + if (!body || typeof body !== "object") throw new Error(`TaskTree API returned invalid JSON (${response.status})`); + return { ...(body as object), transport: "api", httpStatus: response.status }; +} + +type Parsed = { positionals: string[]; values: Record; overapi: boolean; help: boolean }; +function parse(argv: string[]): Parsed { + const parsed: Parsed = { positionals: [], values: {}, overapi: false, help: false }; + for (let index = 0; index < argv.length; index += 1) { + const token = argv[index]; + if (token === "--overapi") parsed.overapi = true; + else if (token === "--help" || token === "-h") parsed.help = true; + else if (token.startsWith("--")) { + const key = token.slice(2); + const value = argv[index + 1]; + if (!value || value.startsWith("--")) throw new Error(`${token} requires a value`); + parsed.values[key] = value; + index += 1; + } else parsed.positionals.push(token); + } + return parsed; +} +function required(parsed: Parsed, key: string) { const value = parsed.values[key]; if (!value) throw new Error(`--${key} is required`); return value; } diff --git a/web/hwlab-cloud-web/src/api/index.ts b/web/hwlab-cloud-web/src/api/index.ts index 5f3bcb50..2709a797 100644 --- a/web/hwlab-cloud-web/src/api/index.ts +++ b/web/hwlab-cloud-web/src/api/index.ts @@ -17,6 +17,7 @@ export { usageAPI } from "./usage"; export { billingAPI } from "./billing"; export { systemAPI, type SkillUploadFileInput } from "./system"; export { projectManagementAPI, type MdtodoExecutionContext, type MdtodoFileRecord, type MdtodoLaunchContextResponse, type MdtodoReportPreviewRecord, type MdtodoTaskDetailAuthority, type MdtodoTaskDetailRecord, type MdtodoTaskLinkRecord, type MdtodoTaskMutationInput, type MdtodoTaskMutationResponse, type MdtodoTaskPage, type MdtodoTaskRecord, type MdtodoTaskStatus, type ProjectNavigationResponse, type ProjectRecord, type ProjectSource, type ProjectSourceInput, type ProjectWorkbenchLinkRecord } from "./projectManagement"; +export { tasktreeAPI, type TaskTreeGroup, type TaskTreeMilestone, type TaskTreeReport, type TaskTreeTask, type TaskTreeTimeline } from "./tasktree"; import { fetchJson } from "./client"; import { accessAPI } from "./access"; @@ -32,6 +33,7 @@ import { billingAPI } from "./billing"; import { workbenchAPI } from "./workbench"; import { systemAPI } from "./system"; import { projectManagementAPI } from "./projectManagement"; +import { tasktreeAPI } from "./tasktree"; import type { ApiResult, LiveBuildsPayload, LiveProbePayload, SkillsResponse } from "@/types"; export const api = { @@ -47,6 +49,7 @@ export const api = { usage: usageAPI, billing: billingAPI, projectManagement: projectManagementAPI, + tasktree: tasktreeAPI, system: systemAPI, healthLive: (): Promise> => fetchJson("/health/live", { timeoutMs: 12000, timeoutName: "health/live" }), health: (): Promise> => fetchJson("/health", { timeoutMs: 12000, timeoutName: "health" }), diff --git a/web/hwlab-cloud-web/src/api/tasktree.ts b/web/hwlab-cloud-web/src/api/tasktree.ts new file mode 100644 index 00000000..0a8d5748 --- /dev/null +++ b/web/hwlab-cloud-web/src/api/tasktree.ts @@ -0,0 +1,14 @@ +import { fetchJson } from "./client"; +import type { ApiResult } from "@/types"; + +export type TaskTreeGroup = { id: string; name: string; description: string; createdAt: string; updatedAt: string }; +export type TaskTreeTask = { id: string; groupId: string; parentId: string | null; kind: "task" | "subtask"; title: string; description: string; status: string; startAt: string | null; dueAt: string | null; sortOrder: number; createdAt: string; updatedAt: string }; +export type TaskTreeMilestone = { id: string; groupId: string; taskId: string | null; title: string; occursAt: string; createdAt: string }; +export type TaskTreeReport = { id: string; taskId: string; title: string; body: string; status: string; createdAt: string }; +export type TaskTreeTimeline = { group: TaskTreeGroup; tasks: TaskTreeTask[]; milestones: TaskTreeMilestone[]; reports: TaskTreeReport[] }; +type Result = { ok?: boolean; data?: T; error?: { code?: string; message?: string } }; + +export const tasktreeAPI = { + groups: (): Promise>> => fetchJson("/v1/tasktree/groups", { timeoutMs: 12000, timeoutName: "TaskTree groups" }), + timeline: (groupId: string): Promise>> => fetchJson(`/v1/tasktree/groups/${encodeURIComponent(groupId)}/timeline`, { timeoutMs: 12000, timeoutName: "TaskTree timeline" }) +}; diff --git a/web/hwlab-cloud-web/src/components/layout/AppNavigation.vue b/web/hwlab-cloud-web/src/components/layout/AppNavigation.vue index 6bd3f313..59e8a1a1 100644 --- a/web/hwlab-cloud-web/src/components/layout/AppNavigation.vue +++ b/web/hwlab-cloud-web/src/components/layout/AppNavigation.vue @@ -13,6 +13,7 @@ import { Cpu, CreditCard, FolderKanban, + GanttChart, Gauge, KeyRound, LayoutDashboard, @@ -109,6 +110,13 @@ const navSections = [ icon: ListChecks, navId: "project.mdtodo", }, + { + name: "TaskTree", + label: "TaskTree", + path: "/projects/tasktree", + icon: GanttChart, + navId: "project.tasktree", + }, ], }, { diff --git a/web/hwlab-cloud-web/src/router/index.ts b/web/hwlab-cloud-web/src/router/index.ts index e68bfd39..e648264c 100644 --- a/web/hwlab-cloud-web/src/router/index.ts +++ b/web/hwlab-cloud-web/src/router/index.ts @@ -10,6 +10,7 @@ const WorkbenchDebugView = () => import("@/views/workbench/WorkbenchDebugView.vu const OpenCodeFrameView = () => import("@/views/opencode/OpenCodeFrameView.vue"); const ProjectsView = () => import("@/views/projects/ProjectsView.vue"); const ProjectMdtodoView = () => import("@/views/projects/MdtodoView.vue"); +const TaskTreeView = () => import("@/views/projects/TaskTreeView.vue"); const AgentRunsView = () => import("@/views/agents/AgentRunsView.vue"); const routes: RouteRecordRaw[] = [ @@ -25,6 +26,8 @@ const routes: RouteRecordRaw[] = [ { path: "/opencode", name: "OpenCode", component: OpenCodeFrameView, meta: { requiresAuth: true, navId: "opencode.root", title: "OpenCode", section: "opencode" } }, { path: "/projects", name: "Projects", component: ProjectsView, meta: { requiresAuth: true, navId: "project.overview", title: "项目", section: "project" } }, { path: "/projects/mdtodo", name: "ProjectMdtodo", component: ProjectMdtodoView, meta: { requiresAuth: true, navId: "project.mdtodo", title: "MDTODO", section: "project" } }, + { path: "/projects/tasktree", name: "TaskTree", component: TaskTreeView, meta: { requiresAuth: true, navId: "project.tasktree", title: "TaskTree", section: "project" } }, + { path: "/projects/tasktree/:groupId", name: "TaskTreeGroup", component: TaskTreeView, meta: { requiresAuth: true, navId: "project.tasktree", navParent: "TaskTree", title: "TaskTree", section: "project" } }, { path: "/projects/mdtodo/sources/:sourceId/files/:fileRef", name: "ProjectMdtodoFile", component: ProjectMdtodoView, meta: { requiresAuth: true, navId: "project.mdtodo", title: "MDTODO", section: "project" } }, { path: "/projects/mdtodo/sources/:sourceId/files/:fileRef/tasks/:taskId", name: "ProjectMdtodoTask", component: ProjectMdtodoView, meta: { requiresAuth: true, navId: "project.mdtodo", title: "MDTODO", section: "project" } }, { path: "/projects/mdtodo/sources/:sourceId/files/:fileRef/tasks/:taskId/reports/:linkId", name: "ProjectMdtodoReport", component: ProjectMdtodoView, meta: { requiresAuth: true, navId: "project.mdtodo", title: "MDTODO", section: "project" } }, diff --git a/web/hwlab-cloud-web/src/stores/auth.ts b/web/hwlab-cloud-web/src/stores/auth.ts index d2ac81c3..3229159d 100644 --- a/web/hwlab-cloud-web/src/stores/auth.ts +++ b/web/hwlab-cloud-web/src/stores/auth.ts @@ -144,6 +144,7 @@ function firstAllowedNavPath(allowedIds: string[]): string { { id: "workbench.debug", path: "/workbench/debug" }, { id: "opencode.root", path: "/opencode" }, { id: "project.mdtodo", path: "/projects/mdtodo" }, + { id: "project.tasktree", path: "/projects/tasktree" }, { id: "user.dashboard", path: "/dashboard" }, { id: "project.overview", path: "/projects" }, { id: "user.billing", path: "/billing" }, diff --git a/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue b/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue new file mode 100644 index 00000000..489b714e --- /dev/null +++ b/web/hwlab-cloud-web/src/views/projects/TaskTreeView.vue @@ -0,0 +1,95 @@ + + + + +