From f3d92235db5321be75f8a1c0455fcf1bf7edf68e Mon Sep 17 00:00:00 2001 From: Michal Date: Fri, 17 Jul 2026 22:02:22 +0100 Subject: [PATCH] =?UTF-8?q?feat(pulumi):=20@mcpctl/pulumi=20=E2=80=94=20ge?= =?UTF-8?q?neric=20Pulumi=20provider=20for=20mcpctl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New workspace package: a Pulumi dynamic provider (TypeScript, in-process) that manages mcpctl resources declaratively from any Pulumi program. Motivated by model drift: when the deployed vLLM/LiteLLM model changes, mcpctl's llm target must follow, and a Pulumi resource makes that automatic on `pulumi up`. - Generic core `McpctlResource({ kind, name, spec, mcpd })` round-trips ANY mcpctl resource kind; typed `Llm` wrapper for ergonomics. - Engine talks direct HTTP to mcpd's REST API (ported from cli/api-client.ts), mirroring apply's name-keyed upsert (PUT strips immutable name/type) and get -o json's read field-stripping. No mcpctl binary needed → CI-friendly. - CRUD/diff/check with correct replace semantics (name/type/kind/mcpd.url force delete-before-replace), 429 retry, secret token in state. - Validation deferred to mcpd's Zod schemas, so new resource kinds work with no provider release. - CommonJS build so the serialized dynamic provider's module refs are captured. - 18 Vitest tests (mocked mcpd + Pulumi mocks); build + lint clean; README with auth (PAT) setup and consumer example for kubernetes-deployment. Co-Authored-By: Claude Opus 4.8 (1M context) --- pnpm-lock.yaml | 1249 ++++++++++++++++++++++++++++- src/pulumi/README.md | 96 +++ src/pulumi/package.json | 34 + src/pulumi/src/engine.ts | 179 +++++ src/pulumi/src/index.ts | 20 + src/pulumi/src/kinds.ts | 88 ++ src/pulumi/src/llm.ts | 51 ++ src/pulumi/src/provider.ts | 123 +++ src/pulumi/src/resource.ts | 50 ++ src/pulumi/tests/engine.test.ts | 70 ++ src/pulumi/tests/llm.test.ts | 43 + src/pulumi/tests/mock-mcpd.ts | 107 +++ src/pulumi/tests/provider.test.ts | 99 +++ src/pulumi/tsconfig.json | 12 + src/pulumi/vitest.config.ts | 8 + tsconfig.json | 3 +- 16 files changed, 2222 insertions(+), 10 deletions(-) create mode 100644 src/pulumi/README.md create mode 100644 src/pulumi/package.json create mode 100644 src/pulumi/src/engine.ts create mode 100644 src/pulumi/src/index.ts create mode 100644 src/pulumi/src/kinds.ts create mode 100644 src/pulumi/src/llm.ts create mode 100644 src/pulumi/src/provider.ts create mode 100644 src/pulumi/src/resource.ts create mode 100644 src/pulumi/tests/engine.test.ts create mode 100644 src/pulumi/tests/llm.test.ts create mode 100644 src/pulumi/tests/mock-mcpd.ts create mode 100644 src/pulumi/tests/provider.test.ts create mode 100644 src/pulumi/tsconfig.json create mode 100644 src/pulumi/vitest.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68b6b79..10ef364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: version: 8.56.0(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.18(vitest@4.0.18(@opentelemetry/api@1.9.1)(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2)) eslint: specifier: ^10.0.1 version: 10.0.1(jiti@2.6.1) @@ -37,7 +37,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.18(@opentelemetry/api@1.9.1)(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2) src/cli: dependencies: @@ -187,6 +187,12 @@ importers: specifier: ^25.3.0 version: 25.3.0 + src/pulumi: + devDependencies: + '@pulumi/pulumi': + specifier: ^3.0.0 + version: 3.253.0(typescript@5.9.3) + src/shared: dependencies: zod: @@ -209,7 +215,7 @@ importers: version: 5.2.2 geist: specifier: ^1.5.1 - version: 1.7.0(next@16.2.5(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 1.7.0(next@16.2.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) lucide-react: specifier: ^0.487.0 version: 0.487.0(react@19.2.5) @@ -651,6 +657,10 @@ packages: '@fastify/static@8.3.0': resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==} + '@gar/promise-retry@1.0.3': + resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} + engines: {node: ^20.17.0 || >=22.9.0} + '@grpc/grpc-js@1.14.3': resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} @@ -968,6 +978,13 @@ packages: resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@isaacs/string-locale-compare@1.1.0': + resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1002,6 +1019,10 @@ packages: '@kubernetes/client-node@1.4.0': resolution: {integrity: sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA==} + '@logdna/tail-file@2.2.0': + resolution: {integrity: sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng==} + engines: {node: '>=10.3.0'} + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -1081,6 +1102,166 @@ packages: cpu: [x64] os: [win32] + '@npmcli/agent@4.0.2': + resolution: {integrity: sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/arborist@9.9.0': + resolution: {integrity: sha512-tzsb1R8mx0k0drlLDT/9ckEYaQHIaWnUti/ci2IaFoQqdqwfmYrc+90p1+QEZC/ocvqcxBep9P1xKw4FbAGofw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/git@7.0.2': + resolution: {integrity: sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/installed-package-contents@4.0.0': + resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + '@npmcli/map-workspaces@5.0.3': + resolution: {integrity: sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/metavuln-calculator@9.0.3': + resolution: {integrity: sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/name-from-folder@4.0.0': + resolution: {integrity: sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/node-gyp@5.0.0': + resolution: {integrity: sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/package-json@7.0.5': + resolution: {integrity: sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/promise-spawn@9.0.1': + resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/query@5.0.0': + resolution: {integrity: sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/redact@4.0.0': + resolution: {integrity: sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/run-script@10.0.4': + resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.9.0': + resolution: {integrity: sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.9.0': + resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-grpc@0.220.0': + resolution: {integrity: sha512-bv1xmNhmNwIM6MdUBw4yYuJeVcEViVLk3uD69vOQMwueHBnfyl/u0HnBlB1FNY/Te0UOzJzvcbyR8wN6b+iGbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.9.0': + resolution: {integrity: sha512-RwINoce2BH8T4obT5pMcAla2sWma1YZvYuaktWmTluQ0PkQdvv5D060rWI1+kawX+J2qBRcMbwrZJJNcMJUauQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-grpc@0.220.0': + resolution: {integrity: sha512-U1EF8KKu52XwH2ybUkjVDmaVQZGf3mXirRSw1KJQrOV5aymgJgkPJV7+kRPqawZe0rpVc/BK+pPSyMWuQoyJJQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.220.0': + resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.220.0': + resolution: {integrity: sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.220.0': + resolution: {integrity: sha512-/eIkBPMBTIvM3x/0mDX4aJeSkYifYClnBPr68PL1h5LV4VQv4+SV6CGrpiZ4fIWDnobVmhTWCm1J/QRdAWUfvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.220.0': + resolution: {integrity: sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.9.0': + resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.220.0': + resolution: {integrity: sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.9.0': + resolution: {integrity: sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.9.0': + resolution: {integrity: sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.9.0': + resolution: {integrity: sha512-ec9a7ps37huy5itYk0MalaZdSLlM6AXWp/FhtEjgMpp5leEGojBDvAl/UWttQnkMZOvFHKzRESn8TD3yKTF5nQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.9.0': + resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + engines: {node: '>=14'} + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -1144,6 +1325,18 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@pulumi/pulumi@3.253.0': + resolution: {integrity: sha512-riKRPh6hNCA/NRxVSOLx08c0rqgWYnKGOTgPZzCXi5TglVEERiXHW4U2DaTpP978FI1Xo1iqCa41vmd5i/PNHw==} + engines: {node: '>=22'} + peerDependencies: + ts-node: '>= 7.0.1 < 12' + typescript: '>= 3.8.3 < 7' + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -1272,6 +1465,30 @@ packages: cpu: [x64] os: [win32] + '@sigstore/bundle@4.0.0': + resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/core@3.2.1': + resolution: {integrity: sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/protobuf-specs@0.5.1': + resolution: {integrity: sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@4.1.1': + resolution: {integrity: sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/tuf@4.0.2': + resolution: {integrity: sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/verify@3.1.1': + resolution: {integrity: sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==} + engines: {node: ^20.17.0 || >=22.9.0} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1391,6 +1608,14 @@ packages: '@types/react-dom': optional: true + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@4.1.0': + resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} + engines: {node: ^20.17.0 || >=22.9.0} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1434,6 +1659,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/google-protobuf@3.15.12': + resolution: {integrity: sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==} + '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -1460,6 +1688,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} @@ -1575,6 +1806,10 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -1754,6 +1989,10 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bin-links@6.0.2: + resolution: {integrity: sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w==} + engines: {node: ^20.17.0 || >=22.9.0} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1776,6 +2015,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -1795,6 +2037,10 @@ packages: magicast: optional: true + cacache@20.0.4: + resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} + engines: {node: ^20.17.0 || >=22.9.0} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1828,12 +2074,19 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} citty@0.2.1: resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1868,6 +2121,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmd-shim@8.0.0: + resolution: {integrity: sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==} + engines: {node: ^20.17.0 || >=22.9.0} + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1891,6 +2148,10 @@ packages: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + common-ancestor-path@2.0.0: + resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} + engines: {node: '>= 18'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1954,6 +2215,11 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + cssstyle@6.2.0: resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} engines: {node: '>=20'} @@ -2080,6 +2346,10 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -2095,6 +2365,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2192,10 +2465,17 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + express-rate-limit@8.2.1: resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} @@ -2305,6 +2585,10 @@ packages: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2346,6 +2630,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} @@ -2371,6 +2659,9 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + google-protobuf@3.21.4: + resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2405,6 +2696,14 @@ packages: resolution: {integrity: sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==} engines: {node: '>=16.9.0'} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + hpagent@1.2.0: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} @@ -2416,6 +2715,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -2432,6 +2734,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -2439,6 +2745,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@8.0.0: + resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} + engines: {node: ^20.17.0 || >=22.9.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2447,6 +2757,10 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-in-the-middle@3.3.1: + resolution: {integrity: sha512-0rymlHSFLwZ0ixx8DaQkoIyZojJPY2a0K2nEYslhKJ6jIYO/m0IcCb7iQsFPmS7WmKwISZiIrv5Icstrw/CmqA==} + engines: {node: '>=18'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2466,6 +2780,14 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + ink@6.8.0: resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==} engines: {node: '>=20'} @@ -2527,6 +2849,10 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -2534,6 +2860,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + isomorphic-ws@5.0.0: resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} peerDependencies: @@ -2593,6 +2923,10 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@5.0.0: + resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} + engines: {node: ^20.17.0 || >=22.9.0} + json-schema-ref-resolver@3.0.0: resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} @@ -2608,16 +2942,29 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-nice@1.1.4: + resolution: {integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + jsonpath-plus@10.4.0: resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} engines: {node: '>=18.0.0'} hasBin: true + just-diff-apply@5.5.0: + resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==} + + just-diff@6.0.2: + resolution: {integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2708,6 +3055,9 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.6: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} @@ -2738,6 +3088,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + make-fetch-happen@15.0.6: + resolution: {integrity: sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==} + engines: {node: ^20.17.0 || >=22.9.0} + marked@14.0.0: resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} engines: {node: '>= 18'} @@ -2758,6 +3112,9 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2798,6 +3155,29 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@5.0.2: + resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} + engines: {node: '>=8'} + minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -2814,6 +3194,10 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -2825,6 +3209,9 @@ packages: mnemonist@0.40.0: resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + monaco-editor@0.55.1: resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} @@ -2886,6 +3273,11 @@ packages: encoding: optional: true + node-gyp@12.4.0: + resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -2894,6 +3286,47 @@ packages: engines: {node: '>=6'} hasBin: true + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-bundled@5.0.0: + resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-install-checks@8.0.0: + resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-package-arg@13.0.2: + resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-packlist@10.0.4: + resolution: {integrity: sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@11.0.3: + resolution: {integrity: sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-registry-fetch@19.1.1: + resolution: {integrity: sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -2953,9 +3386,22 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@7.0.5: + resolution: {integrity: sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==} + engines: {node: '>=18'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pacote@21.5.1: + resolution: {integrity: sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + parse-conflict-json@5.0.1: + resolution: {integrity: sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==} + engines: {node: ^20.17.0 || >=22.9.0} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -3016,6 +3462,10 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + postcss-selector-parser@7.1.4: + resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + engines: {node: '>=4'} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -3042,12 +3492,26 @@ packages: typescript: optional: true + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + process-warning@4.0.1: resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + proggy@4.0.0: + resolution: {integrity: sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + promise-all-reject-late@1.0.1: + resolution: {integrity: sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==} + + promise-call-limit@3.0.2: + resolution: {integrity: sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==} + protobufjs@7.5.4: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} @@ -3127,6 +3591,10 @@ packages: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} + read-cmd-shim@6.0.0: + resolution: {integrity: sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==} + engines: {node: ^20.17.0 || >=22.9.0} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -3151,6 +3619,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -3285,6 +3757,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sigstore@4.1.1: + resolution: {integrity: sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==} + engines: {node: ^20.17.0 || >=22.9.0} + slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -3312,6 +3788,28 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + split-ca@1.0.1: resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} @@ -3323,6 +3821,10 @@ packages: resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} engines: {node: '>=10.16.0'} + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} + engines: {node: ^20.17.0 || >=22.9.0} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -3370,6 +3872,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -3426,6 +3932,10 @@ packages: engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.20: + resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + engines: {node: '>=18'} + teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} @@ -3481,6 +3991,10 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + treeverse@3.0.0: + resolution: {integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -3495,6 +4009,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tuf-js@4.1.0: + resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} + engines: {node: ^20.17.0 || >=22.9.0} + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -3524,6 +4042,10 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@6.27.0: + resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} + engines: {node: '>=18.17'} + undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} @@ -3532,6 +4054,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -3548,6 +4074,13 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3630,6 +4163,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -3653,6 +4190,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3684,6 +4226,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} + engines: {node: ^20.17.0 || >=22.9.0} + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -3713,6 +4259,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -4095,6 +4645,8 @@ snapshots: fastq: 1.20.1 glob: 11.1.0 + '@gar/promise-retry@1.0.3': {} + '@grpc/grpc-js@1.14.3': dependencies: '@grpc/proto-loader': 0.8.0 @@ -4361,6 +4913,12 @@ snapshots: '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@isaacs/string-locale-compare@1.1.0': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4417,6 +4975,8 @@ snapshots: - supports-color - utf-8-validate + '@logdna/tail-file@2.2.0': {} + '@lukeed/ms@2.0.2': {} '@mapbox/node-pre-gyp@1.0.11': @@ -4493,6 +5053,241 @@ snapshots: '@next/swc-win32-x64-msvc@16.2.5': optional: true + '@npmcli/agent@4.0.2': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.2.6 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/arborist@9.9.0': + dependencies: + '@gar/promise-retry': 1.0.3 + '@isaacs/string-locale-compare': 1.1.0 + '@npmcli/fs': 5.0.0 + '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/map-workspaces': 5.0.3 + '@npmcli/metavuln-calculator': 9.0.3 + '@npmcli/name-from-folder': 4.0.0 + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/query': 5.0.0 + '@npmcli/redact': 4.0.0 + '@npmcli/run-script': 10.0.4 + bin-links: 6.0.2 + cacache: 20.0.4 + common-ancestor-path: 2.0.0 + hosted-git-info: 9.0.3 + json-stringify-nice: 1.1.4 + lru-cache: 11.2.6 + minimatch: 10.2.2 + nopt: 9.0.0 + npm-install-checks: 8.0.0 + npm-package-arg: 13.0.2 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.1 + pacote: 21.5.1 + parse-conflict-json: 5.0.1 + proc-log: 6.1.0 + proggy: 4.0.0 + promise-all-reject-late: 1.0.1 + promise-call-limit: 3.0.2 + semver: 7.7.4 + ssri: 13.0.1 + treeverse: 3.0.0 + walk-up-path: 4.0.0 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@5.0.0': + dependencies: + semver: 7.7.4 + + '@npmcli/git@7.0.2': + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/promise-spawn': 9.0.1 + ini: 6.0.0 + lru-cache: 11.2.6 + npm-pick-manifest: 11.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + which: 6.0.1 + + '@npmcli/installed-package-contents@4.0.0': + dependencies: + npm-bundled: 5.0.0 + npm-normalize-package-bin: 5.0.0 + + '@npmcli/map-workspaces@5.0.3': + dependencies: + '@npmcli/name-from-folder': 4.0.0 + '@npmcli/package-json': 7.0.5 + glob: 13.0.6 + minimatch: 10.2.2 + + '@npmcli/metavuln-calculator@9.0.3': + dependencies: + cacache: 20.0.4 + json-parse-even-better-errors: 5.0.0 + pacote: 21.5.1 + proc-log: 6.1.0 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + '@npmcli/name-from-folder@4.0.0': {} + + '@npmcli/node-gyp@5.0.0': {} + + '@npmcli/package-json@7.0.5': + dependencies: + '@npmcli/git': 7.0.2 + glob: 13.0.6 + hosted-git-info: 9.0.3 + json-parse-even-better-errors: 5.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + spdx-expression-parse: 4.0.0 + + '@npmcli/promise-spawn@9.0.1': + dependencies: + which: 6.0.1 + + '@npmcli/query@5.0.0': + dependencies: + postcss-selector-parser: 7.1.4 + + '@npmcli/redact@4.0.0': {} + + '@npmcli/run-script@10.0.4': + dependencies: + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.1 + node-gyp: 12.4.0 + proc-log: 6.1.0 + + '@opentelemetry/api-logs@0.220.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/context-async-hooks@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-zipkin@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/instrumentation-grpc@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + import-in-the-middle: 3.3.1 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-grpc-exporter-base@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-logs@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-metrics@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-trace-node@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/semantic-conventions@1.43.0': {} + '@pinojs/redact@0.4.0': {} '@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)': @@ -4553,6 +5348,38 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@pulumi/pulumi@3.253.0(typescript@5.9.3)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@logdna/tail-file': 2.2.0 + '@npmcli/arborist': 9.9.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-grpc': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + '@types/google-protobuf': 3.15.12 + '@types/semver': 7.7.1 + execa: 5.1.1 + google-protobuf: 3.21.4 + ini: 2.0.0 + js-yaml: 4.1.1 + minimist: 1.2.8 + normalize-package-data: 6.0.2 + require-from-string: 2.0.2 + semver: 7.7.4 + source-map-support: 0.5.21 + upath: 1.2.0 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/rollup-android-arm-eabi@4.58.0': @@ -4630,6 +5457,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.58.0': optional: true + '@sigstore/bundle@4.0.0': + dependencies: + '@sigstore/protobuf-specs': 0.5.1 + + '@sigstore/core@3.2.1': {} + + '@sigstore/protobuf-specs@0.5.1': {} + + '@sigstore/sign@4.1.1': + dependencies: + '@gar/promise-retry': 1.0.3 + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.1 + make-fetch-happen: 15.0.6 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/tuf@4.0.2': + dependencies: + '@sigstore/protobuf-specs': 0.5.1 + tuf-js: 4.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/verify@3.1.1': + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.1 + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.15': @@ -4734,6 +5593,13 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@4.1.0': + dependencies: + '@tufjs/canonical-json': 2.0.0 + minimatch: 10.2.2 + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -4789,6 +5655,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/google-protobuf@3.15.12': {} + '@types/js-yaml@4.0.9': {} '@types/json-schema@7.0.15': {} @@ -4818,6 +5686,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/semver@7.7.1': {} + '@types/ssh2@1.15.5': dependencies: '@types/node': 18.19.130 @@ -4932,7 +5802,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@opentelemetry/api@1.9.1)(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.18 @@ -4944,7 +5814,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.18(@opentelemetry/api@1.9.1)(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2) '@vitest/expect@4.0.18': dependencies: @@ -4987,6 +5857,8 @@ snapshots: abbrev@1.1.1: {} + abbrev@4.0.0: {} + abstract-logging@2.0.1: {} accepts@2.0.0: @@ -5138,6 +6010,14 @@ snapshots: dependencies: require-from-string: 2.0.2 + bin-links@6.0.2: + dependencies: + cmd-shim: 8.0.0 + npm-normalize-package-bin: 5.0.0 + proc-log: 6.1.0 + read-cmd-shim: 6.0.0 + write-file-atomic: 7.0.1 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -5179,6 +6059,8 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-from@1.1.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -5204,6 +6086,19 @@ snapshots: pkg-types: 2.3.0 rc9: 2.1.2 + cacache@20.0.4: + dependencies: + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.6 + lru-cache: 11.2.6 + minipass: 7.1.3 + minipass-collect: 2.0.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + p-map: 7.0.5 + ssri: 13.0.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5230,12 +6125,16 @@ snapshots: chownr@2.0.0: {} + chownr@3.0.0: {} + citty@0.1.6: dependencies: consola: 3.4.2 citty@0.2.1: {} + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -5265,6 +6164,8 @@ snapshots: clsx@2.1.1: {} + cmd-shim@8.0.0: {} + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 @@ -5283,6 +6184,8 @@ snapshots: commander@13.1.0: {} + common-ancestor-path@2.0.0: {} + concat-map@0.0.1: {} confbox@0.2.4: {} @@ -5333,6 +6236,8 @@ snapshots: css.escape@1.5.1: {} + cssesc@3.0.0: {} + cssstyle@6.2.0: dependencies: '@asamuzakjp/css-color': 5.1.11 @@ -5444,6 +6349,8 @@ snapshots: entities@8.0.0: {} + env-paths@2.2.1: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -5452,6 +6359,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -5590,8 +6499,22 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + expect-type@1.3.0: {} + exponential-backoff@3.1.3: {} + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 @@ -5749,6 +6672,10 @@ snapshots: dependencies: minipass: 3.3.6 + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -5768,9 +6695,9 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 - geist@1.7.0(next@16.2.5(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)): + geist@1.7.0(next@16.2.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)): dependencies: - next: 16.2.5(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) gensync@1.0.0-beta.2: {} @@ -5796,6 +6723,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -5837,6 +6766,8 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + google-protobuf@3.21.4: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -5859,6 +6790,14 @@ snapshots: hono@4.12.0: {} + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.2.6 + hpagent@1.2.0: {} html-encoding-sniffer@6.0.0: @@ -5869,6 +6808,8 @@ snapshots: html-escaper@2.0.2: {} + http-cache-semantics@4.2.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -5898,16 +6839,28 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@2.1.0: {} + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 ieee754@1.2.1: {} + ignore-walk@8.0.0: + dependencies: + minimatch: 10.2.2 + ignore@5.3.2: {} ignore@7.0.5: {} + import-in-the-middle@3.3.1: + dependencies: + cjs-module-lexer: 2.2.0 + es-module-lexer: 2.3.1 + module-details-from-path: 1.0.4 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -5921,6 +6874,10 @@ snapshots: inherits@2.0.4: {} + ini@2.0.0: {} + + ini@6.0.0: {} + ink@6.8.0(@types/react@19.2.14)(react@19.2.4): dependencies: '@alcalzone/ansi-tokenize': 0.2.5 @@ -5991,10 +6948,14 @@ snapshots: is-promise@4.0.0: {} + is-stream@2.0.1: {} + is-unicode-supported@2.1.0: {} isexe@2.0.0: {} + isexe@4.0.0: {} + isomorphic-ws@5.0.0(ws@8.19.0): dependencies: ws: 8.19.0 @@ -6061,6 +7022,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@5.0.0: {} + json-schema-ref-resolver@3.0.0: dependencies: dequal: 2.0.3 @@ -6073,14 +7036,22 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-nice@1.1.4: {} + json5@2.2.3: {} + jsonparse@1.3.1: {} + jsonpath-plus@10.4.0: dependencies: '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) jsep: 1.4.0 + just-diff-apply@5.5.0: {} + + just-diff@6.0.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6153,6 +7124,8 @@ snapshots: long@5.3.2: {} + lru-cache@10.4.3: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: @@ -6183,6 +7156,23 @@ snapshots: dependencies: semver: 7.7.4 + make-fetch-happen@15.0.6: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/agent': 4.0.2 + '@npmcli/redact': 4.0.0 + cacache: 20.0.4 + http-cache-semantics: 4.2.0 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 6.1.0 + ssri: 13.0.1 + transitivePeerDependencies: + - supports-color + marked@14.0.0: {} math-intrinsics@1.1.0: {} @@ -6193,6 +7183,8 @@ snapshots: merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -6223,6 +7215,32 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.3 + + minipass-fetch@5.0.2: + dependencies: + minipass: 7.1.3 + minipass-sized: 2.0.0 + minizlib: 3.1.0 + optionalDependencies: + iconv-lite: 0.7.2 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@2.0.0: + dependencies: + minipass: 7.1.3 + minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -6236,6 +7254,10 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + mkdirp-classic@0.5.3: {} mkdirp@1.0.4: {} @@ -6244,6 +7266,8 @@ snapshots: dependencies: obliterator: 2.0.5 + module-details-from-path@1.0.4: {} + monaco-editor@0.55.1: dependencies: dompurify: 3.2.7 @@ -6262,7 +7286,7 @@ snapshots: negotiator@1.0.0: {} - next@16.2.5(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + next@16.2.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: '@next/env': 16.2.5 '@swc/helpers': 0.5.15 @@ -6281,6 +7305,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.5 '@next/swc-win32-arm64-msvc': 16.2.5 '@next/swc-win32-x64-msvc': 16.2.5 + '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -6294,12 +7319,81 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-gyp@12.4.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + tar: 7.5.20 + tinyglobby: 0.2.15 + undici: 6.27.0 + which: 6.0.1 + node-releases@2.0.38: {} nopt@5.0.0: dependencies: abbrev: 1.1.1 + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + npm-bundled@5.0.0: + dependencies: + npm-normalize-package-bin: 5.0.0 + + npm-install-checks@8.0.0: + dependencies: + semver: 7.7.4 + + npm-normalize-package-bin@5.0.0: {} + + npm-package-arg@13.0.2: + dependencies: + hosted-git-info: 9.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + validate-npm-package-name: 7.0.2 + + npm-packlist@10.0.4: + dependencies: + ignore-walk: 8.0.0 + proc-log: 6.1.0 + + npm-pick-manifest@11.0.3: + dependencies: + npm-install-checks: 8.0.0 + npm-normalize-package-bin: 5.0.0 + npm-package-arg: 13.0.2 + semver: 7.7.4 + + npm-registry-fetch@19.1.1: + dependencies: + '@npmcli/redact': 4.0.0 + jsonparse: 1.3.1 + make-fetch-happen: 15.0.6 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minizlib: 3.1.0 + npm-package-arg: 13.0.2 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + npmlog@5.0.1: dependencies: are-we-there-yet: 2.0.0 @@ -6361,8 +7455,38 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@7.0.5: {} + package-json-from-dist@1.0.1: {} + pacote@21.5.1: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/git': 7.0.2 + '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.1 + '@npmcli/run-script': 10.0.4 + cacache: 20.0.4 + fs-minipass: 3.0.3 + minipass: 7.1.3 + npm-package-arg: 13.0.2 + npm-packlist: 10.0.4 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.1 + proc-log: 6.1.0 + sigstore: 4.1.1 + ssri: 13.0.1 + tar: 7.5.20 + transitivePeerDependencies: + - supports-color + + parse-conflict-json@5.0.1: + dependencies: + json-parse-even-better-errors: 5.0.0 + just-diff: 6.0.2 + just-diff-apply: 5.5.0 + parse5@8.0.1: dependencies: entities: 8.0.0 @@ -6420,6 +7544,11 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + postcss-selector-parser@7.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -6449,10 +7578,18 @@ snapshots: transitivePeerDependencies: - magicast + proc-log@6.1.0: {} + process-warning@4.0.1: {} process-warning@5.0.0: {} + proggy@4.0.0: {} + + promise-all-reject-late@1.0.1: {} + + promise-call-limit@3.0.2: {} + protobufjs@7.5.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -6534,6 +7671,8 @@ snapshots: react@19.2.5: {} + read-cmd-shim@6.0.0: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -6553,6 +7692,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resolve-pkg-maps@1.0.0: {} restore-cursor@4.0.0: @@ -6749,6 +7895,17 @@ snapshots: signal-exit@4.1.0: {} + sigstore@4.1.1: + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.1 + '@sigstore/sign': 4.1.1 + '@sigstore/tuf': 4.0.2 + '@sigstore/verify': 3.1.1 + transitivePeerDependencies: + - supports-color + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -6780,6 +7937,32 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + split-ca@1.0.1: {} split2@4.2.0: {} @@ -6792,6 +7975,10 @@ snapshots: cpu-features: 0.0.10 nan: 2.25.0 + ssri@13.0.1: + dependencies: + minipass: 7.1.3 + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -6844,6 +8031,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-final-newline@2.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -6916,6 +8105,14 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tar@7.5.20: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + teex@1.0.1: dependencies: streamx: 2.25.0 @@ -6966,6 +8163,8 @@ snapshots: dependencies: punycode: 2.3.1 + treeverse@3.0.0: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -6979,6 +8178,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tuf-js@4.1.0: + dependencies: + '@tufjs/models': 4.1.0 + debug: 4.4.3 + make-fetch-happen: 15.0.6 + transitivePeerDependencies: + - supports-color + tweetnacl@0.14.5: {} type-check@0.4.0: @@ -7003,10 +8210,14 @@ snapshots: undici-types@7.18.2: {} + undici@6.27.0: {} + undici@7.25.0: {} unpipe@1.0.0: {} + upath@1.2.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -7021,6 +8232,13 @@ snapshots: uuid@10.0.0: {} + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@7.0.2: {} + vary@1.1.2: {} vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2): @@ -7039,7 +8257,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vitest@4.0.18(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@opentelemetry/api@1.9.1)(@types/node@25.3.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2)) @@ -7062,6 +8280,7 @@ snapshots: vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 25.3.0 jsdom: 28.1.0 transitivePeerDependencies: @@ -7081,6 +8300,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} + webidl-conversions@3.0.1: {} webidl-conversions@8.0.1: {} @@ -7104,6 +8325,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@6.0.1: + dependencies: + isexe: 4.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -7139,6 +8364,10 @@ snapshots: wrappy@1.0.2: {} + write-file-atomic@7.0.1: + dependencies: + signal-exit: 4.1.0 + ws@8.19.0: {} xml-name-validator@5.0.0: {} @@ -7151,6 +8380,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yaml@2.8.2: {} yargs-parser@21.1.1: {} diff --git a/src/pulumi/README.md b/src/pulumi/README.md new file mode 100644 index 0000000..e0e5660 --- /dev/null +++ b/src/pulumi/README.md @@ -0,0 +1,96 @@ +# @mcpctl/pulumi + +A [Pulumi](https://www.pulumi.com/) dynamic provider for [mcpctl](../../). Manage +mcpctl resources — `llm`, `server`, `project`, `secret`, `agent`, and any other +kind mcpctl supports — declaratively from any TypeScript Pulumi program. + +It talks **directly to mcpd's REST API** (the same endpoints the `mcpctl` CLI +uses) and mirrors the CLI's two round-trip guarantees: + +- `apply`'s name-keyed **upsert** (look up by `name`; PUT with immutable fields + stripped if it exists, else POST), and +- `get -o json`'s field-stripping on read, so state does not churn. + +No `mcpctl` binary or `~/.mcpctl` credentials are required on the machine +running `pulumi up`. + +## Install + +```bash +npm install @mcpctl/pulumi @pulumi/pulumi +``` + +## Usage + +### Typed `Llm` wrapper + +```ts +import * as pulumi from "@pulumi/pulumi"; +import * as mcpctl from "@mcpctl/pulumi"; + +const cfg = new pulumi.Config(); +const mcpd = { url: cfg.require("mcpdUrl"), token: cfg.requireSecret("mcpdPat") }; + +new mcpctl.Llm("homelab-qwen", { + name: "qwen3-thinking", // upsert key (immutable — changing it replaces) + type: "openai", // immutable + model: servedModelName, // pulumi.Input — e.g. from another resource + url: "http://litellm.nvidia-nim.svc.cluster.local:4000", + tier: "heavy", + mcpd, +}); +``` + +When `model` (or `url`, `tier`, `description`, ...) changes, Pulumi issues an +in-place `PUT /api/v1/llms/qwen3-thinking` on the next `up`. Changing `name` or +`type` forces a replacement (delete-before-replace, since `name` is the identity). + +### Generic resource — any kind + +```ts +new mcpctl.McpctlResource("my-server", { + kind: "server", + name: "my-server", + spec: { /* sent verbatim to mcpd; mcpd's Zod schema validates it */ }, + mcpd, +}); +``` + +The provider does no per-kind schema validation beyond the name shape and the +required connection — it defers to mcpd. New mcpctl resource kinds work the day +mcpd ships them, without a provider release. + +## Auth + +The provider authenticates with a **long-lived PAT** (`mcpctl_pat_…`), passed as +`mcpd.token` (use `cfg.requireSecret(...)`). + +Mint one with resource-wide `view:llms` + `edit:llms` bindings, e.g. via +`mcpctl apply -f -`: + +```yaml +kind: mcptoken +name: pulumi-automation +projectId: # currently required (see note) +expiresAt: null # never expires +bindings: + - { role: view, resource: llms } + - { role: edit, resource: llms } +``` + +The bindings are resource-wide, so the token drives `/api/v1/llms/*` regardless +of the project it is attached to (the project-scope guard only gates +`/api/v1/projects/*`). Grant broader bindings if you manage other kinds. + +> **Note:** mcpd currently requires `projectId` when minting an mcptoken, so a +> management-scope PAT must attach to some (throwaway) project. A future mcpd +> change may make `projectId` optional for management tokens. + +## Notes + +- **Secrets in state:** dynamic-provider inputs (including the token) are stored, + encrypted, in Pulumi state. Use an encrypted state backend. +- **Serialization:** this package is CommonJS on purpose — Pulumi serializes the + provider object, and the CJS build lets its module references (`./engine`, + `./kinds`) be captured and re-required at runtime. Validate end-to-end with a + real `pulumi up` against a disposable mcpd before relying on it in production. diff --git a/src/pulumi/package.json b/src/pulumi/package.json new file mode 100644 index 0000000..c22a535 --- /dev/null +++ b/src/pulumi/package.json @@ -0,0 +1,34 @@ +{ + "name": "@mcpctl/pulumi", + "version": "0.0.1", + "description": "Pulumi dynamic provider for mcpctl resources (llms, servers, projects, ...). Manage mcpctl declaratively from any TypeScript Pulumi program.", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "exports": { + ".": { + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsc --build", + "clean": "rimraf dist", + "test": "vitest", + "test:run": "vitest run" + }, + "keywords": [ + "pulumi", + "mcpctl", + "mcp", + "provider" + ], + "peerDependencies": { + "@pulumi/pulumi": "^3.0.0" + }, + "devDependencies": { + "@pulumi/pulumi": "^3.0.0" + } +} diff --git a/src/pulumi/src/engine.ts b/src/pulumi/src/engine.ts new file mode 100644 index 0000000..3e3bca0 --- /dev/null +++ b/src/pulumi/src/engine.ts @@ -0,0 +1,179 @@ +/** + * Dependency-free HTTP engine for the mcpctl Pulumi provider. + * + * Talks to mcpd's REST API directly — the *same* endpoints the `mcpctl` CLI + * uses (`GET/POST/PUT/DELETE /api/v1/[/]`) — and mirrors two + * contracts the CLI guarantees: + * + * 1. `apply`'s name-keyed upsert: look the resource up by `name`; if it + * exists, PUT with immutable fields stripped; otherwise POST. + * (see src/cli/src/commands/apply.ts `applyConfig`) + * 2. name resolution by listing `/api/v1/` and matching `name`, + * which works uniformly for every resource kind. + * (see src/cli/src/commands/shared.ts `resolveNameOrId`) + * + * No `mcpctl` binary or `~/.mcpctl` credentials are required — every call + * carries its own `{ url, token }`. Ported from src/cli/src/api-client.ts. + */ +import http from 'node:http'; +import https from 'node:https'; + +/** Connection + auth for a single mcpd daemon. */ +export interface McpdConn { + url: string; + token: string; + timeoutMs?: number; +} + +/** A resource row as mcpd returns it (always carries an `id`). */ +export interface McpctlEntity { + id: string; + name?: string; + [key: string]: unknown; +} + +export class McpdHttpError extends Error { + readonly status: number; + readonly body: string; + constructor(status: number, body: string, method: string, path: string) { + super(`mcpd ${method} ${path} -> HTTP ${status}: ${body}`); + this.name = 'McpdHttpError'; + this.status = status; + this.body = body; + } +} + +interface RawResponse { + status: number; + body: string; +} + +const MAX_RETRIES = 3; +const DEFAULT_TIMEOUT_MS = 15_000; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function rawRequest( + method: string, + fullUrl: string, + token: string, + timeoutMs: number, + body?: unknown, +): Promise { + return new Promise((resolve, reject) => { + const parsed = new URL(fullUrl); + const isHttps = parsed.protocol === 'https:'; + const headers: Record = { Authorization: `Bearer ${token}` }; + let payload: string | undefined; + if (body !== undefined) { + payload = JSON.stringify(body); + headers['Content-Type'] = 'application/json'; + headers['Content-Length'] = String(Buffer.byteLength(payload)); + } + const driver = isHttps ? https : http; + const req = driver.request( + { + hostname: parsed.hostname, + port: parsed.port || (isHttps ? 443 : 80), + path: parsed.pathname + parsed.search, + method, + timeout: timeoutMs, + headers, + }, + (res) => { + const chunks: Buffer[] = []; + res.on('data', (chunk: Buffer) => chunks.push(chunk)); + res.on('end', () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf-8') })); + }, + ); + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error(`Request to ${fullUrl} timed out after ${timeoutMs}ms`)); + }); + if (payload !== undefined) req.write(payload); + req.end(); + }); +} + +/** + * Perform a request, retrying on 429 with exponential backoff (mirrors the + * CLI's apply backoff). Throws {@link McpdHttpError} on any >=400 status; + * callers that tolerate 404 catch it. + */ +async function request(conn: McpdConn, method: string, path: string, body?: unknown): Promise { + const timeout = conn.timeoutMs ?? DEFAULT_TIMEOUT_MS; + const base = conn.url.replace(/\/$/, ''); + let attempt = 0; + for (;;) { + const res = await rawRequest(method, `${base}${path}`, conn.token, timeout, body); + if (res.status === 429 && attempt < MAX_RETRIES) { + attempt += 1; + await sleep(250 * 2 ** (attempt - 1)); + continue; + } + if (res.status >= 400) { + throw new McpdHttpError(res.status, res.body, method, path); + } + if (!res.body) return null; + try { + return JSON.parse(res.body) as T; + } catch { + return null; + } + } +} + +/** List all rows of a resource kind (RBAC-filtered by the token). */ +export async function listResource(conn: McpdConn, resource: string): Promise { + const data = await request(conn, 'GET', `/api/v1/${resource}`); + return Array.isArray(data) ? data : []; +} + +/** Resolve a resource by human name (list + match), like the CLI does. */ +export async function findByName(conn: McpdConn, resource: string, name: string): Promise { + const items = await listResource(conn, resource); + return items.find((item) => item.name === name) ?? null; +} + +/** + * Name-keyed upsert. `doc` is the full body (must include `name`). If a row + * with that name exists, PUT it with `immutable` fields stripped; otherwise + * POST `doc`. Mirrors apply.ts exactly. + */ +export async function upsert( + conn: McpdConn, + resource: string, + doc: Record, + immutable: string[], +): Promise { + const name = String(doc['name'] ?? ''); + const existing = await findByName(conn, resource, name); + if (existing) { + const updateBody: Record = {}; + for (const [key, value] of Object.entries(doc)) { + if (!immutable.includes(key)) updateBody[key] = value; + } + const updated = await request(conn, 'PUT', `/api/v1/${resource}/${existing.id}`, updateBody); + return updated ?? { ...existing, ...updateBody }; + } + const created = await request(conn, 'POST', `/api/v1/${resource}`, doc); + if (!created || typeof created.id !== 'string') { + throw new Error(`create ${resource}/${name}: mcpd returned no id`); + } + return created; +} + +/** Delete a resource by name. No-op if it is already gone (idempotent). */ +export async function del(conn: McpdConn, resource: string, name: string): Promise { + const existing = await findByName(conn, resource, name); + if (!existing) return; + try { + await request(conn, 'DELETE', `/api/v1/${resource}/${existing.id}`); + } catch (err) { + if (err instanceof McpdHttpError && err.status === 404) return; + throw err; + } +} diff --git a/src/pulumi/src/index.ts b/src/pulumi/src/index.ts new file mode 100644 index 0000000..0927a3e --- /dev/null +++ b/src/pulumi/src/index.ts @@ -0,0 +1,20 @@ +/** + * @mcpctl/pulumi — a Pulumi dynamic provider for mcpctl. + * + * Manage mcpctl resources (llms, servers, projects, ...) declaratively from any + * TypeScript Pulumi program. Talks directly to mcpd's REST API; no `mcpctl` + * binary required. See README for auth setup. + */ +export { McpctlResource } from './resource'; +export type { McpctlResourceArgs, McpdConnInput } from './resource'; + +export { Llm, LLM_TYPES, LLM_TIERS } from './llm'; +export type { LlmArgs, LlmType, LlmTier, LlmApiKeyRef } from './llm'; + +export { mcpctlProvider } from './provider'; +export type { McpctlResourceInputs } from './provider'; + +export { KIND_TO_RESOURCE } from './kinds'; + +export { McpdHttpError } from './engine'; +export type { McpdConn, McpctlEntity } from './engine'; diff --git a/src/pulumi/src/kinds.ts b/src/pulumi/src/kinds.ts new file mode 100644 index 0000000..da2f59b --- /dev/null +++ b/src/pulumi/src/kinds.ts @@ -0,0 +1,88 @@ +/** + * Per-kind metadata, kept deliberately small and generic. The provider defers + * all real validation to mcpd's own Zod schemas — this table only covers the + * few things a client must know to round-trip correctly: + * + * - singular kind -> plural REST resource (mirrors apply.ts KIND_TO_RESOURCE) + * - which fields mcpd cannot change in place (stripped from PUT bodies) + * - which server-managed/runtime fields to drop on read so state does not + * churn (mirrors get.ts toApplyDocs) + * - the Zod defaults mcpd applies, so `check` can normalize inputs and avoid + * spurious diffs + * + * Unknown kinds still work (resource falls back to the kind string) — new mcpd + * resource kinds are usable the day they ship, without a provider release. + */ + +/** singular kind -> plural resource key (mirror of apply.ts KIND_TO_RESOURCE). */ +export const KIND_TO_RESOURCE: Record = { + server: 'servers', + project: 'projects', + secret: 'secrets', + template: 'templates', + user: 'users', + group: 'groups', + rbac: 'rbac', + prompt: 'prompts', + promptrequest: 'promptrequests', + serverattachment: 'serverattachments', + mcptoken: 'mcptokens', + secretbackend: 'secretbackends', + llm: 'llms', + agent: 'agents', +}; + +/** + * Fields mcpd treats as immutable on update (beyond `name`, which is always + * immutable). Changing one forces a replacement. Mirrors the update bodies in + * apply.ts (e.g. llms strip `type`). + */ +export const KIND_IMMUTABLE_FIELDS: Record = { + llm: ['type'], +}; + +/** + * Server-managed/runtime fields dropped on read so desired-vs-live comparison + * stays clean. Mirrors get.ts `toApplyDocs`. `ALWAYS_STRIPPED` applies to every + * kind. + */ +export const ALWAYS_STRIPPED: readonly string[] = ['id', 'createdAt', 'updatedAt', 'version']; + +export const KIND_STRIPPED_ON_READ: Record = { + llm: ['kind', 'status', 'lastHeartbeatAt', 'inactiveSince', 'providerSessionId'], + agent: ['kind', 'status', 'lastHeartbeatAt', 'inactiveSince', 'providerSessionId'], +}; + +/** Zod defaults mcpd applies; mirrored so `check` normalization avoids diffs. */ +export const KIND_SPEC_DEFAULTS: Record> = { + llm: { tier: 'fast', description: '', extraConfig: {}, poolName: null }, +}; + +export function resourceForKind(kind: string): string { + return KIND_TO_RESOURCE[kind] ?? kind; +} + +/** Immutable fields for a kind, including the always-immutable `name`. */ +export function immutableFieldsForKind(kind: string): string[] { + return ['name', ...(KIND_IMMUTABLE_FIELDS[kind] ?? [])]; +} + +/** Drop undefined-valued keys and apply the kind's defaults (spec wins). */ +export function normalizeSpec(kind: string, spec: Record): Record { + const defaults = KIND_SPEC_DEFAULTS[kind] ?? {}; + const out: Record = { ...defaults }; + for (const [key, value] of Object.entries(spec)) { + if (value !== undefined) out[key] = value; + } + return out; +} + +/** Strip server-managed + kind-specific runtime fields from a live row. */ +export function stripForRead(kind: string, entity: Record): Record { + const drop = new Set([...ALWAYS_STRIPPED, ...(KIND_STRIPPED_ON_READ[kind] ?? [])]); + const out: Record = {}; + for (const [key, value] of Object.entries(entity)) { + if (!drop.has(key)) out[key] = value; + } + return out; +} diff --git a/src/pulumi/src/llm.ts b/src/pulumi/src/llm.ts new file mode 100644 index 0000000..561e8e5 --- /dev/null +++ b/src/pulumi/src/llm.ts @@ -0,0 +1,51 @@ +/** + * Typed convenience wrapper for the mcpctl `llm` resource. Compiles down to a + * generic {@link McpctlResource} with `kind: "llm"`. Field types mirror mcpd's + * CreateLlmSchema / UpdateLlmSchema (src/mcpd/src/validation/llm.schema.ts). + * + * new mcpctl.Llm("homelab-qwen", { + * name: "qwen3-thinking", + * type: "openai", + * model: servedModelName, // pulumi.Input + * url: litellmUrl, + * tier: "heavy", + * mcpd: { url, token: cfg.requireSecret("mcpdPat") }, + * }); + */ +import type { Input, CustomResourceOptions } from '@pulumi/pulumi'; +import { McpctlResource, McpdConnInput } from './resource'; + +export const LLM_TYPES = ['anthropic', 'openai', 'deepseek', 'vllm', 'ollama', 'gemini-cli'] as const; +export type LlmType = (typeof LLM_TYPES)[number]; + +export const LLM_TIERS = ['fast', 'heavy'] as const; +export type LlmTier = (typeof LLM_TIERS)[number]; + +/** Reference to a key inside a Secret; mcpd resolves it server-side. */ +export interface LlmApiKeyRef { + name: Input; + key: Input; +} + +export interface LlmArgs { + /** llm resource name (upsert key). Immutable — changing it replaces. */ + name: Input; + /** Provider type. Immutable — changing it replaces. */ + type: Input; + model: Input; + /** Endpoint URL; omit for the provider default. */ + url?: Input; + tier?: Input; + description?: Input; + apiKeyRef?: Input; + poolName?: Input; + extraConfig?: Input>; + mcpd: Input; +} + +export class Llm extends McpctlResource { + constructor(name: string, args: LlmArgs, opts?: CustomResourceOptions) { + const { name: llmName, mcpd, ...spec } = args; + super(name, { kind: 'llm', name: llmName, spec: spec as Record, mcpd }, opts); + } +} diff --git a/src/pulumi/src/provider.ts b/src/pulumi/src/provider.ts new file mode 100644 index 0000000..965713f --- /dev/null +++ b/src/pulumi/src/provider.ts @@ -0,0 +1,123 @@ +/** + * The mcpctl Pulumi dynamic provider. + * + * A dynamic provider's object is *serialized* into the deployment. This package + * compiles to CommonJS, so these top-level imports emit as `require(...)` in the + * built output; Pulumi's closure serializer captures those module references + * (the helpers are pure and dependency-light) and re-requires them at runtime. + * The `@pulumi/pulumi` import is type-only, so it is erased and leaves no + * runtime reference in the serialized object. + * + * CRUD maps onto mcpd's REST API via ./engine, mirroring the CLI's `apply` + * (upsert) and `get -o json` (round-trip) contracts. Validation is deferred to + * mcpd's Zod schemas — this provider only enforces the few client-side + * invariants (name shape, required connection). + */ +import type * as pulumi from '@pulumi/pulumi'; +import { upsert, findByName, del } from './engine'; +import { + resourceForKind, + immutableFieldsForKind, + normalizeSpec, + stripForRead, + KIND_IMMUTABLE_FIELDS, +} from './kinds'; + +/** Inputs for a generic mcpctl resource (all values resolved by the time the + * provider sees them). */ +export interface McpctlResourceInputs { + kind: string; + name: string; + spec: Record; + mcpd: { url: string; token: string; timeoutMs?: number }; +} + +/** Deterministic JSON for order-insensitive equality checks. */ +function stableStringify(value: unknown): string { + if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'; + if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`; + const obj = value as Record; + const keys = Object.keys(obj).sort(); + return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`; +} + +export const mcpctlProvider: pulumi.dynamic.ResourceProvider = { + async check(_olds: unknown, news: unknown): Promise { + const input = (news ?? {}) as Partial; + const failures: pulumi.dynamic.CheckFailure[] = []; + + if (typeof input.kind !== 'string' || input.kind.length === 0) { + failures.push({ property: 'kind', reason: 'kind is required (e.g. "llm", "server", "project")' }); + } + if (typeof input.name !== 'string' || !/^[a-z0-9-]+$/.test(input.name)) { + failures.push({ property: 'name', reason: 'name is required and must be lowercase alphanumeric with hyphens' }); + } + if (input.spec === null || typeof input.spec !== 'object' || Array.isArray(input.spec)) { + failures.push({ property: 'spec', reason: 'spec must be an object' }); + } + const mcpd = input.mcpd; + if (mcpd === undefined || typeof mcpd.url !== 'string' || mcpd.url.length === 0) { + failures.push({ property: 'mcpd', reason: 'mcpd.url is required' }); + } + if (mcpd === undefined || typeof mcpd.token !== 'string' || mcpd.token.length === 0) { + failures.push({ property: 'mcpd', reason: 'mcpd.token is required' }); + } + if (failures.length > 0) return { failures }; + + const kind = input.kind as string; + const spec = input.spec as Record; + return { inputs: { ...input, spec: normalizeSpec(kind, spec) } }; + }, + + async diff(_id: string, olds: unknown, news: unknown): Promise { + const o = olds as McpctlResourceInputs; + const n = news as McpctlResourceInputs; + const replaces: string[] = []; + + if (o.kind !== n.kind) replaces.push('kind'); + if (o.name !== n.name) replaces.push('name'); + // Re-pointing at a different daemon is a different resource. + if (o.mcpd?.url !== n.mcpd?.url) replaces.push('mcpd'); + + const immutable = KIND_IMMUTABLE_FIELDS[n.kind] ?? []; + for (const field of immutable) { + if (stableStringify(o.spec?.[field]) !== stableStringify(n.spec?.[field])) { + replaces.push(`spec.${field}`); + } + } + + const specChanged = stableStringify(o.spec ?? {}) !== stableStringify(n.spec ?? {}); + const changes = replaces.length > 0 || specChanged; + // Any replacement keeps or reuses the unique `name`, so the old row must go + // first to avoid a uniqueness collision (or an orphan on rename). + return { changes, replaces, deleteBeforeReplace: replaces.length > 0 }; + }, + + async create(inputs: unknown): Promise { + const inp = inputs as McpctlResourceInputs; + const spec = normalizeSpec(inp.kind, inp.spec ?? {}); + const entity = await upsert(inp.mcpd, resourceForKind(inp.kind), { name: inp.name, ...spec }, immutableFieldsForKind(inp.kind)); + return { id: `${inp.kind}/${inp.name}`, outs: { kind: inp.kind, name: inp.name, spec, mcpd: inp.mcpd, resourceId: entity.id } }; + }, + + async read(id: string, props: unknown): Promise { + const p = props as McpctlResourceInputs; + const entity = await findByName(p.mcpd, resourceForKind(p.kind), p.name); + if (!entity) return {}; // gone — Pulumi will recreate + const spec = stripForRead(p.kind, entity); + delete spec['name']; + return { id, props: { kind: p.kind, name: p.name, mcpd: p.mcpd, spec, resourceId: entity.id } }; + }, + + async update(_id: string, _olds: unknown, news: unknown): Promise { + const n = news as McpctlResourceInputs; + const spec = normalizeSpec(n.kind, n.spec ?? {}); + const entity = await upsert(n.mcpd, resourceForKind(n.kind), { name: n.name, ...spec }, immutableFieldsForKind(n.kind)); + return { outs: { kind: n.kind, name: n.name, spec, mcpd: n.mcpd, resourceId: entity.id } }; + }, + + async delete(_id: string, props: unknown): Promise { + const p = props as McpctlResourceInputs; + await del(p.mcpd, resourceForKind(p.kind), p.name); + }, +}; diff --git a/src/pulumi/src/resource.ts b/src/pulumi/src/resource.ts new file mode 100644 index 0000000..5f03ddc --- /dev/null +++ b/src/pulumi/src/resource.ts @@ -0,0 +1,50 @@ +/** + * Generic mcpctl resource. Round-trips ANY mcpctl resource kind through the + * dynamic provider: give it a `kind`, a `name`, an opaque `spec` (sent + * verbatim to mcpd, which validates it), and an `mcpd` connection. + * + * new mcpctl.McpctlResource("my-server", { + * kind: "server", + * name: "my-server", + * spec: { image: "...", transport: "stdio", ... }, + * mcpd: { url, token }, + * }); + */ +import * as pulumi from '@pulumi/pulumi'; +import { mcpctlProvider } from './provider'; + +/** mcpd connection + auth. `token` should be a Pulumi secret. */ +export interface McpdConnInput { + url: pulumi.Input; + token: pulumi.Input; + timeoutMs?: pulumi.Input; +} + +export interface McpctlResourceArgs { + /** Singular resource kind, e.g. "llm", "server", "project", "secret". */ + kind: pulumi.Input; + /** Human name — the upsert key (must be unique for the kind). */ + name: pulumi.Input; + /** Resource body sent verbatim to mcpd (mcpd's Zod schema validates it). */ + spec: pulumi.Input>; + /** Target daemon + credentials. */ + mcpd: pulumi.Input; +} + +export class McpctlResource extends pulumi.dynamic.Resource { + /** mcpd's own resource id (CUID) for the managed row. */ + public readonly resourceId!: pulumi.Output; + + constructor(name: string, args: McpctlResourceArgs, opts?: pulumi.CustomResourceOptions) { + super( + mcpctlProvider, + name, + { ...args, resourceId: undefined }, + { + ...opts, + // The connection carries a bearer token — keep it encrypted in state. + additionalSecretOutputs: [...(opts?.additionalSecretOutputs ?? []), 'mcpd'], + }, + ); + } +} diff --git a/src/pulumi/tests/engine.test.ts b/src/pulumi/tests/engine.test.ts new file mode 100644 index 0000000..b3c069d --- /dev/null +++ b/src/pulumi/tests/engine.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { upsert, findByName, del, McpdHttpError } from '../src/engine'; +import { startMockMcpd, type MockMcpd } from './mock-mcpd'; + +let mock: MockMcpd; +const conn = () => ({ url: mock.baseUrl, token: 'test-token' }); + +beforeEach(async () => { + mock = await startMockMcpd(); +}); +afterEach(async () => { + await mock.close(); +}); + +describe('engine.upsert', () => { + it('creates when absent: GET lookup then POST full spec', async () => { + const row = await upsert(conn(), 'llms', { name: 'qwen', type: 'openai', model: 'qwen3' }, ['name', 'type']); + expect(row.id).toMatch(/^cmock/); + const methods = mock.requests.map((r) => `${r.method} ${r.path}`); + expect(methods).toEqual(['GET /api/v1/llms', 'POST /api/v1/llms']); + const post = mock.requests.find((r) => r.method === 'POST')!; + expect(post.body).toEqual({ name: 'qwen', type: 'openai', model: 'qwen3' }); + }); + + it('updates when present: PUT with immutable name+type STRIPPED', async () => { + mock.seed('llms', { id: 'cmock9', name: 'qwen', type: 'openai', model: 'old' }); + await upsert(conn(), 'llms', { name: 'qwen', type: 'openai', model: 'new-model' }, ['name', 'type']); + const put = mock.requests.find((r) => r.method === 'PUT')!; + expect(put.path).toBe('/api/v1/llms/cmock9'); + // The immutability contract: name and type must NOT be in the PUT body. + expect(put.body).toEqual({ model: 'new-model' }); + expect(mock.store['llms']!.get('cmock9')).toMatchObject({ model: 'new-model', type: 'openai' }); + }); + + it('sends Authorization: Bearer on every call', async () => { + await upsert(conn(), 'llms', { name: 'q', type: 'openai', model: 'm' }, ['name', 'type']); + expect(mock.requests.every((r) => r.auth === 'Bearer test-token')).toBe(true); + }); + + it('retries on 429 then succeeds', async () => { + mock.failNextWith(429, 1); // first request (the GET lookup) 429s once + const row = await upsert(conn(), 'llms', { name: 'q', type: 'openai', model: 'm' }, ['name', 'type']); + expect(row.id).toMatch(/^cmock/); + expect(mock.requests.filter((r) => r.method === 'GET').length).toBe(2); // retried + }); +}); + +describe('engine.findByName / del', () => { + it('findByName returns null when absent', async () => { + expect(await findByName(conn(), 'llms', 'nope')).toBeNull(); + }); + + it('del is idempotent when the row is already gone', async () => { + await expect(del(conn(), 'llms', 'ghost')).resolves.toBeUndefined(); + expect(mock.requests.some((r) => r.method === 'DELETE')).toBe(false); + }); + + it('del removes an existing row by resolved id', async () => { + mock.seed('llms', { id: 'cmock3', name: 'gone', type: 'openai', model: 'm' }); + await del(conn(), 'llms', 'gone'); + expect(mock.store['llms']!.has('cmock3')).toBe(false); + }); +}); + +describe('engine error surface', () => { + it('throws McpdHttpError on a 500', async () => { + mock.failNextWith(500, 10); + await expect(findByName(conn(), 'llms', 'x')).rejects.toBeInstanceOf(McpdHttpError); + }); +}); diff --git a/src/pulumi/tests/llm.test.ts b/src/pulumi/tests/llm.test.ts new file mode 100644 index 0000000..357f740 --- /dev/null +++ b/src/pulumi/tests/llm.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as pulumi from '@pulumi/pulumi'; + +let captured: Record | undefined; + +beforeAll(() => { + pulumi.runtime.setMocks({ + newResource: (args: pulumi.runtime.MockResourceArgs) => { + // Dynamic resources carry a serialized __provider plus our inputs. + captured = args.inputs; + return { id: `${args.inputs.name}-id`, state: args.inputs }; + }, + call: () => ({}), + }); +}); + +function promiseOf(o: pulumi.Output): Promise { + return new Promise((resolve) => o.apply(resolve)); +} + +describe('Llm wrapper', () => { + it('maps typed args onto a generic {kind:"llm", name, spec, mcpd} resource', async () => { + // Import after setMocks so resource registration is intercepted. + const { Llm } = await import('../src/llm'); + const llm = new Llm('homelab-qwen', { + name: 'qwen3-thinking', + type: 'openai', + model: 'qwen3-thinking', + url: 'http://litellm:4000', + tier: 'heavy', + mcpd: { url: 'http://mcpd', token: 'secret-pat' }, + }); + await promiseOf(llm.urn); + + expect(captured).toBeDefined(); + expect(captured!.kind).toBe('llm'); + expect(captured!.name).toBe('qwen3-thinking'); + expect(captured!.spec).toMatchObject({ type: 'openai', model: 'qwen3-thinking', url: 'http://litellm:4000', tier: 'heavy' }); + // name is a top-level field, not part of spec. + expect(captured!.spec).not.toHaveProperty('name'); + expect(captured!.mcpd).toMatchObject({ url: 'http://mcpd', token: 'secret-pat' }); + }); +}); diff --git a/src/pulumi/tests/mock-mcpd.ts b/src/pulumi/tests/mock-mcpd.ts new file mode 100644 index 0000000..1b18dd9 --- /dev/null +++ b/src/pulumi/tests/mock-mcpd.ts @@ -0,0 +1,107 @@ +/** + * In-memory mcpd mock for provider/engine tests. Implements the generic REST + * shape the provider relies on: GET list, POST create, PUT /:id, DELETE /:id. + * Records every request for assertions. + */ +import http from 'node:http'; +import type { AddressInfo } from 'node:net'; + +export interface RecordedRequest { + method: string; + path: string; + auth: string | undefined; + body: unknown; +} + +export interface MockMcpd { + baseUrl: string; + requests: RecordedRequest[]; + /** Seed/read the store directly: store[resource][id] = row. */ + store: Record>>; + seed(resource: string, row: Record): void; + /** Force the next N responses (any method) to a given status (e.g. 429). */ + failNextWith(status: number, times: number): void; + close(): Promise; +} + +export async function startMockMcpd(): Promise { + const store: Record>> = {}; + const requests: RecordedRequest[] = []; + let idCounter = 0; + let forcedStatus: number | null = null; + let forcedTimes = 0; + + const ensure = (resource: string) => { + if (!store[resource]) store[resource] = new Map(); + return store[resource]!; + }; + + const server = http.createServer((req, res) => { + const chunks: Buffer[] = []; + req.on('data', (c: Buffer) => chunks.push(c)); + req.on('end', () => { + const raw = Buffer.concat(chunks).toString('utf-8'); + const body = raw ? JSON.parse(raw) : undefined; + const url = new URL(req.url ?? '/', 'http://mock'); + requests.push({ method: req.method ?? '', path: url.pathname, auth: req.headers['authorization'], body }); + + if (forcedStatus !== null && forcedTimes > 0) { + forcedTimes -= 1; + res.statusCode = forcedStatus; + res.end(JSON.stringify({ error: 'forced' })); + return; + } + + // /api/v1/[/] + const parts = url.pathname.split('/').filter(Boolean); // ["api","v1","", "?"] + const resource = parts[2] ?? ''; + const id = parts[3]; + const table = ensure(resource); + + const send = (status: number, payload?: unknown) => { + res.statusCode = status; + res.end(payload === undefined ? '' : JSON.stringify(payload)); + }; + + if (req.method === 'GET' && !id) return send(200, [...table.values()]); + if (req.method === 'POST' && !id) { + idCounter += 1; + const row = { id: `cmock${idCounter}0000000000000000000`, ...(body as Record) }; + table.set(row.id, row); + return send(201, row); + } + if (req.method === 'PUT' && id) { + const existing = table.get(id); + if (!existing) return send(404, { error: 'not found' }); + const updated = { ...existing, ...(body as Record) }; + table.set(id, updated); + return send(200, updated); + } + if (req.method === 'DELETE' && id) { + if (!table.has(id)) return send(404, { error: 'not found' }); + table.delete(id); + return send(200, { ok: true }); + } + return send(404, { error: 'unhandled' }); + }); + }); + + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + const { port } = server.address() as AddressInfo; + + return { + baseUrl: `http://127.0.0.1:${port}`, + requests, + store, + seed(resource, row) { + ensure(resource).set(String(row['id']), row); + }, + failNextWith(status, times) { + forcedStatus = status; + forcedTimes = times; + }, + close() { + return new Promise((resolve) => server.close(() => resolve())); + }, + }; +} diff --git a/src/pulumi/tests/provider.test.ts b/src/pulumi/tests/provider.test.ts new file mode 100644 index 0000000..05824d3 --- /dev/null +++ b/src/pulumi/tests/provider.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mcpctlProvider } from '../src/provider'; +import { startMockMcpd, type MockMcpd } from './mock-mcpd'; + +let mock: MockMcpd; +const mcpd = () => ({ url: mock.baseUrl, token: 'tok' }); + +beforeEach(async () => { + mock = await startMockMcpd(); +}); +afterEach(async () => { + await mock.close(); +}); + +const llmInputs = (over: Record = {}) => ({ + kind: 'llm', + name: 'qwen3-thinking', + spec: { type: 'openai', model: 'qwen3-thinking', url: 'http://litellm:4000', tier: 'heavy' }, + mcpd: mcpd(), + ...over, +}); + +describe('check', () => { + it('accepts a valid llm and applies defaults', async () => { + const res = await mcpctlProvider.check!(undefined, llmInputs()); + expect(res.failures).toBeUndefined(); + // description/extraConfig/poolName defaults injected. + expect(res.inputs.spec).toMatchObject({ tier: 'heavy', description: '', extraConfig: {}, poolName: null }); + }); + + it('rejects a bad name and missing mcpd', async () => { + const res = await mcpctlProvider.check!(undefined, { kind: 'llm', name: 'Bad Name', spec: {}, mcpd: { url: '', token: '' } }); + const props = (res.failures ?? []).map((f) => f.property); + expect(props).toContain('name'); + expect(props).toContain('mcpd'); + }); + + it('does not reject unknown kinds (forward-compatible)', async () => { + const res = await mcpctlProvider.check!(undefined, { kind: 'somenewkind', name: 'x', spec: {}, mcpd: mcpd() }); + expect(res.failures).toBeUndefined(); + }); +}); + +describe('diff', () => { + const base = { kind: 'llm', name: 'a', mcpd: { url: 'http://x' }, spec: { type: 'openai', model: 'm1' } }; + + it('model change → in-place update (changes, no replace)', async () => { + const r = await mcpctlProvider.diff!('llm/a', base, { ...base, spec: { type: 'openai', model: 'm2' } }); + expect(r.changes).toBe(true); + expect(r.replaces ?? []).toEqual([]); + }); + + it('name change → replace + deleteBeforeReplace', async () => { + const r = await mcpctlProvider.diff!('llm/a', base, { ...base, name: 'b' }); + expect(r.replaces).toContain('name'); + expect(r.deleteBeforeReplace).toBe(true); + }); + + it('type change (immutable) → replace on spec.type', async () => { + const r = await mcpctlProvider.diff!('llm/a', base, { ...base, spec: { type: 'vllm', model: 'm1' } }); + expect(r.replaces).toContain('spec.type'); + expect(r.deleteBeforeReplace).toBe(true); + }); + + it('no change → changes:false', async () => { + const r = await mcpctlProvider.diff!('llm/a', base, { ...base }); + expect(r.changes).toBe(false); + }); +}); + +describe('create / read / update / delete round-trip', () => { + it('create posts, read reflects, update puts, delete removes', async () => { + const created = await mcpctlProvider.create!(llmInputs()); + expect(created.id).toBe('llm/qwen3-thinking'); + expect(created.outs.resourceId).toMatch(/^cmock/); + + const read = await mcpctlProvider.read!('llm/qwen3-thinking', { ...llmInputs(), ...created.outs }); + expect(read.props?.spec).toMatchObject({ model: 'qwen3-thinking', type: 'openai' }); + expect(read.props?.spec).not.toHaveProperty('name'); // name lives at top level + + const updated = await mcpctlProvider.update!( + 'llm/qwen3-thinking', + created.outs, + llmInputs({ spec: { type: 'openai', model: 'qwen3-next', url: 'http://litellm:4000', tier: 'heavy' } }), + ); + expect(updated.outs.spec.model).toBe('qwen3-next'); + const put = mock.requests.find((r) => r.method === 'PUT')!; + expect(put.body).not.toHaveProperty('type'); // immutable stripped + expect(put.body).not.toHaveProperty('name'); + + await mcpctlProvider.delete!('llm/qwen3-thinking', created.outs); + expect([...mock.store['llms']!.values()]).toHaveLength(0); + }); + + it('read returns {} when the row is gone (triggers recreate)', async () => { + const res = await mcpctlProvider.read!('llm/ghost', { kind: 'llm', name: 'ghost', mcpd: mcpd(), spec: {} }); + expect(res).toEqual({}); + }); +}); diff --git a/src/pulumi/tsconfig.json b/src/pulumi/tsconfig.json new file mode 100644 index 0000000..0cdafad --- /dev/null +++ b/src/pulumi/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "types": ["node"], + "exactOptionalPropertyTypes": false, + "module": "CommonJS", + "moduleResolution": "Node" + }, + "include": ["src/**/*.ts"] +} diff --git a/src/pulumi/vitest.config.ts b/src/pulumi/vitest.config.ts new file mode 100644 index 0000000..b0e95a5 --- /dev/null +++ b/src/pulumi/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.test.ts'], + environment: 'node', + }, +}); diff --git a/tsconfig.json b/tsconfig.json index 7859f36..cf0c1d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ { "path": "src/db" }, { "path": "src/cli" }, { "path": "src/mcpd" }, - { "path": "src/mcplocal" } + { "path": "src/mcplocal" }, + { "path": "src/pulumi" } ] } -- 2.49.1