diff --git a/.gitignore b/.gitignore
index ee955b40..8ec76fe2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -422,3 +422,4 @@ terrabuild-debug.md
*.fsyacc.output
!**/results/terrabuild-debug.*
+.pnpm-store
diff --git a/Makefile b/Makefile
index c3206f73..e8955ad8 100644
--- a/Makefile
+++ b/Makefile
@@ -20,9 +20,13 @@ current_dir = $(shell pwd)
# |_______/ |_______| \__/
#
-build:
+build: webui
dotnet build -c $(config) terrabuild.slnx
+webui:
+ cd src/Terrabuild.UI && pnpm install
+ cd src/Terrabuild.UI && pnpm build
+
test:
dotnet test -c $(config) terrabuild.slnx
@@ -100,6 +104,9 @@ terrabuild:
# |__| |_______|_______/ |__| |_______/
#
+ui: webui
+ $(terrabuild) graph --no-open --port 5179
+
test-scaffold:
$(terrabuild) scaffold --workspace tests/scaffold --debug --log
diff --git a/WORKSPACE b/WORKSPACE
index ef734339..5d36bece 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -20,6 +20,7 @@ locals {
versions = {
dotnet_sdk: "10.0.100" # https://mcr.microsoft.com/artifact/mar/dotnet/sdk/tags
+ pnpm: "22-10" # https://hub.docker.com/r/guergeiro/pnpm/tags
}
}
@@ -53,3 +54,13 @@ extension @dotnet {
evaluate = local.dotnet.evaluate
}
}
+
+extension @pnpm {
+ image = local.is_local_build ? nothing : "docker.io/guergeiro/pnpm:${local.versions.pnpm}"
+ defaults {
+ frozen = true
+ }
+ env {
+ CI = true
+ }
+}
diff --git a/src/Terrabuild.UI/PROJECT b/src/Terrabuild.UI/PROJECT
new file mode 100644
index 00000000..96b25091
--- /dev/null
+++ b/src/Terrabuild.UI/PROJECT
@@ -0,0 +1,11 @@
+project terrabuild_ui {
+ @pnpm { }
+}
+
+target install {
+ @pnpm install { }
+}
+
+target build {
+ @pnpm build { }
+}
diff --git a/src/Terrabuild.UI/index.html b/src/Terrabuild.UI/index.html
new file mode 100644
index 00000000..f1bba0a4
--- /dev/null
+++ b/src/Terrabuild.UI/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Terrabuild Graph
+
+
+
+
+
+
diff --git a/src/Terrabuild.UI/package.json b/src/Terrabuild.UI/package.json
new file mode 100644
index 00000000..25c03654
--- /dev/null
+++ b/src/Terrabuild.UI/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "terrabuild-ui",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "packageManager": "pnpm@9.15.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.11.4",
+ "@emotion/styled": "^11.11.5",
+ "@mantine/core": "^6.0.0",
+ "@mantine/hooks": "^6.0.0",
+ "@tabler/icons-react": "^3.19.0",
+ "dagre": "^0.8.5",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "reactflow": "^11.11.3",
+ "xterm": "^5.3.0",
+ "xterm-addon-fit": "^0.8.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.3",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.11"
+ }
+}
diff --git a/src/Terrabuild.UI/pnpm-lock.yaml b/src/Terrabuild.UI/pnpm-lock.yaml
new file mode 100644
index 00000000..eee07750
--- /dev/null
+++ b/src/Terrabuild.UI/pnpm-lock.yaml
@@ -0,0 +1,2348 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@emotion/react':
+ specifier: ^11.11.4
+ version: 11.14.0(@types/react@18.3.27)(react@18.3.1)
+ '@emotion/styled':
+ specifier: ^11.11.5
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(@types/react@18.3.27)(react@18.3.1)
+ '@mantine/core':
+ specifier: ^6.0.0
+ version: 6.0.22(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(@mantine/hooks@6.0.22(react@18.3.1))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mantine/hooks':
+ specifier: ^6.0.0
+ version: 6.0.22(react@18.3.1)
+ '@tabler/icons-react':
+ specifier: ^3.19.0
+ version: 3.36.1(react@18.3.1)
+ dagre:
+ specifier: ^0.8.5
+ version: 0.8.5
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ reactflow:
+ specifier: ^11.11.3
+ version: 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ xterm:
+ specifier: ^5.3.0
+ version: 5.3.0
+ xterm-addon-fit:
+ specifier: ^0.8.0
+ version: 0.8.0(xterm@5.3.0)
+ devDependencies:
+ '@types/react':
+ specifier: ^18.3.12
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.3.1
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.3
+ version: 4.7.0(vite@5.4.21)
+ typescript:
+ specifier: ^5.6.3
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.11
+ version: 5.4.21
+
+packages:
+
+ '@babel/code-frame@7.28.6':
+ resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.6':
+ resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.6':
+ resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.6':
+ resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.6':
+ resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.28.6':
+ resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.6':
+ resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.6':
+ resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
+ engines: {node: '>=6.9.0'}
+
+ '@emotion/babel-plugin@11.13.5':
+ resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
+
+ '@emotion/cache@11.14.0':
+ resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
+
+ '@emotion/hash@0.9.2':
+ resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
+
+ '@emotion/is-prop-valid@1.4.0':
+ resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==}
+
+ '@emotion/memoize@0.9.0':
+ resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
+
+ '@emotion/react@11.14.0':
+ resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/serialize@1.3.3':
+ resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
+
+ '@emotion/sheet@1.4.0':
+ resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
+
+ '@emotion/styled@11.14.1':
+ resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==}
+ peerDependencies:
+ '@emotion/react': ^11.0.0-rc.0
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/unitless@0.10.0':
+ resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0':
+ resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@emotion/utils@1.4.2':
+ resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
+
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.21.5':
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.21.5':
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.21.5':
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.21.5':
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.21.5':
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.21.5':
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.21.5':
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.21.5':
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.21.5':
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.21.5':
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.21.5':
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.21.5':
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.21.5':
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.21.5':
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.21.5':
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.21.5':
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-x64@0.21.5':
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.21.5':
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.21.5':
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.21.5':
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.21.5':
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/react-dom@1.3.0':
+ resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/react@0.19.2':
+ resolution: {integrity: sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@mantine/core@6.0.22':
+ resolution: {integrity: sha512-6kv0eY7n565fyjgS20qUYeCSxg3f1TJ5vurzbP1HHtFXXKSY0bYoqqDoHipFCt6NxsPQGeiC6cC0c/IWIlxoKQ==}
+ peerDependencies:
+ '@mantine/hooks': 6.0.22
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@mantine/hooks@6.0.22':
+ resolution: {integrity: sha512-e10//QTN2sAmC4Ryeu5X5L/TsxnrjXMOaGq3dxFPIPsCSwLzyxqySfjzVViWmoPWAj0Ak9MvE2MHFjzmOpA80w==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@mantine/styles@6.0.22':
+ resolution: {integrity: sha512-Rud/IQp2EFYDiP4csRy2XBrho/Ct+W2/b+XbvCRTeQTmpFy/NfAKm/TWJa5zPvuv/iLTjGkVos9SHw/DteESpQ==}
+ peerDependencies:
+ '@emotion/react': '>=11.9.0'
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@mantine/utils@6.0.22':
+ resolution: {integrity: sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@radix-ui/number@1.0.0':
+ resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==}
+
+ '@radix-ui/primitive@1.0.0':
+ resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==}
+
+ '@radix-ui/react-compose-refs@1.0.0':
+ resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-context@1.0.0':
+ resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-direction@1.0.0':
+ resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-presence@1.0.0':
+ resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-primitive@1.0.1':
+ resolution: {integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-scroll-area@1.0.2':
+ resolution: {integrity: sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-slot@1.0.1':
+ resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-use-callback-ref@1.0.0':
+ resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@radix-ui/react-use-layout-effect@1.0.0':
+ resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+
+ '@reactflow/background@11.3.14':
+ resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@reactflow/controls@11.2.14':
+ resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@reactflow/core@11.11.4':
+ resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@reactflow/minimap@11.7.14':
+ resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@reactflow/node-resizer@2.2.14':
+ resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@reactflow/node-toolbar@1.3.14':
+ resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/rollup-android-arm-eabi@4.55.1':
+ resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.55.1':
+ resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.55.1':
+ resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.55.1':
+ resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.55.1':
+ resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.55.1':
+ resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.55.1':
+ resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.55.1':
+ resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.55.1':
+ resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.55.1':
+ resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.55.1':
+ resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.55.1':
+ resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.55.1':
+ resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.55.1':
+ resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.55.1':
+ resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.55.1':
+ resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.55.1':
+ resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.55.1':
+ resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.55.1':
+ resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.55.1':
+ resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.55.1':
+ resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.55.1':
+ resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.55.1':
+ resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.55.1':
+ resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.55.1':
+ resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==}
+ cpu: [x64]
+ os: [win32]
+
+ '@tabler/icons-react@3.36.1':
+ resolution: {integrity: sha512-/8nOXeNeMoze9xY/QyEKG65wuvRhkT3q9aytaur6Gj8bYU2A98YVJyLc9MRmc5nVvpy+bRlrrwK/Ykr8WGyUWg==}
+ peerDependencies:
+ react: '>= 16'
+
+ '@tabler/icons@3.36.1':
+ resolution: {integrity: sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/d3-array@3.2.2':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/d3-axis@3.0.6':
+ resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+ '@types/d3-brush@3.0.6':
+ resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+ '@types/d3-chord@3.0.6':
+ resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-contour@3.0.6':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/d3-delaunay@6.0.4':
+ resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+ '@types/d3-dispatch@3.0.7':
+ resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-dsv@3.0.7':
+ resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-fetch@3.0.7':
+ resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+ '@types/d3-format@3.0.4':
+ resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+ '@types/d3-geo@3.1.0':
+ resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+ '@types/d3-hierarchy@3.1.7':
+ resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-polygon@3.0.2':
+ resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+ '@types/d3-quadtree@3.0.6':
+ resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+ '@types/d3-random@3.0.3':
+ resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+ '@types/d3-scale-chromatic@3.1.0':
+ resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-shape@3.1.8':
+ resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
+
+ '@types/d3-time-format@4.0.3':
+ resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
+ '@types/parse-json@4.0.2':
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.27':
+ resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+
+ baseline-browser-mapping@2.9.14:
+ resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==}
+ hasBin: true
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001764:
+ resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==}
+
+ classcat@5.0.5:
+ resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
+ clsx@1.1.1:
+ resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==}
+ engines: {node: '>=6'}
+
+ convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+
+ csstype@3.0.9:
+ resolution: {integrity: sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ dagre@0.8.5:
+ resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ electron-to-chromium@1.5.267:
+ resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ graphlib@2.1.8:
+ resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.2:
+ resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-textarea-autosize@8.3.4:
+ resolution: {integrity: sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ reactflow@11.11.4:
+ resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ rollup@4.55.1:
+ resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+
+ stylis@4.2.0:
+ resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tabbable@6.4.0:
+ resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-composed-ref@1.4.0:
+ resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-isomorphic-layout-effect@1.2.1:
+ resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-latest@1.3.0:
+ resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ vite@5.4.21:
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ xterm-addon-fit@0.8.0:
+ resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
+ deprecated: This package is now deprecated. Move to @xterm/addon-fit instead.
+ peerDependencies:
+ xterm: ^5.0.0
+
+ xterm@5.3.0:
+ resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
+ deprecated: This package is now deprecated. Move to @xterm/xterm instead.
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+snapshots:
+
+ '@babel/code-frame@7.28.6':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.6': {}
+
+ '@babel/core@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.6':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@babel/parser@7.28.6':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/runtime@7.28.6': {}
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@babel/traverse@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.6':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@emotion/babel-plugin@11.13.5':
+ dependencies:
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/runtime': 7.28.6
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/serialize': 1.3.3
+ babel-plugin-macros: 3.1.0
+ convert-source-map: 1.9.0
+ escape-string-regexp: 4.0.0
+ find-root: 1.1.0
+ source-map: 0.5.7
+ stylis: 4.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/cache@11.14.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ stylis: 4.2.0
+
+ '@emotion/hash@0.9.2': {}
+
+ '@emotion/is-prop-valid@1.4.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+
+ '@emotion/memoize@0.9.0': {}
+
+ '@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ hoist-non-react-statics: 3.3.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/serialize@1.3.3':
+ dependencies:
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/unitless': 0.10.0
+ '@emotion/utils': 1.4.2
+ csstype: 3.2.3
+
+ '@emotion/sheet@1.4.0': {}
+
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(@types/react@18.3.27)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/is-prop-valid': 1.4.0
+ '@emotion/react': 11.14.0(@types/react@18.3.27)(react@18.3.1)
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/utils': 1.4.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/unitless@0.10.0': {}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@emotion/utils@1.4.2': {}
+
+ '@emotion/weak-memoize@0.4.0': {}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm@0.21.5':
+ optional: true
+
+ '@esbuild/android-x64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-x64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/linux-loong64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-s390x@0.21.5':
+ optional: true
+
+ '@esbuild/linux-x64@0.21.5':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/sunos-x64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/win32-x64@0.21.5':
+ optional: true
+
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/react-dom@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@floating-ui/react@0.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ aria-hidden: 1.2.6
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tabbable: 6.4.0
+
+ '@floating-ui/utils@0.2.10': {}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@mantine/core@6.0.22(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(@mantine/hooks@6.0.22(react@18.3.1))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react': 0.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mantine/hooks': 6.0.22(react@18.3.1)
+ '@mantine/styles': 6.0.22(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mantine/utils': 6.0.22(react@18.3.1)
+ '@radix-ui/react-scroll-area': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1)
+ react-textarea-autosize: 8.3.4(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@emotion/react'
+ - '@types/react'
+
+ '@mantine/hooks@6.0.22(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@mantine/styles@6.0.22(@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@emotion/react': 11.14.0(@types/react@18.3.27)(react@18.3.1)
+ clsx: 1.1.1
+ csstype: 3.0.9
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@mantine/utils@6.0.22(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@radix-ui/number@1.0.0':
+ dependencies:
+ '@babel/runtime': 7.28.6
+
+ '@radix-ui/primitive@1.0.0':
+ dependencies:
+ '@babel/runtime': 7.28.6
+
+ '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+
+ '@radix-ui/react-context@1.0.0(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+
+ '@radix-ui/react-direction@1.0.0(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+
+ '@radix-ui/react-presence@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@radix-ui/react-primitive@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@radix-ui/react-slot': 1.0.1(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@radix-ui/react-scroll-area@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@radix-ui/number': 1.0.0
+ '@radix-ui/primitive': 1.0.0
+ '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1)
+ '@radix-ui/react-context': 1.0.0(react@18.3.1)
+ '@radix-ui/react-direction': 1.0.0(react@18.3.1)
+ '@radix-ui/react-presence': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@radix-ui/react-slot@1.0.1(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1)
+ react: 18.3.1
+
+ '@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+
+ '@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+
+ '@reactflow/background@11.3.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ classcat: 5.0.5
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@reactflow/controls@11.2.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ classcat: 5.0.5
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@reactflow/core@11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@types/d3': 7.4.3
+ '@types/d3-drag': 3.0.7
+ '@types/d3-selection': 3.0.11
+ '@types/d3-zoom': 3.0.8
+ classcat: 5.0.5
+ d3-drag: 3.0.0
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@reactflow/minimap@11.7.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@types/d3-selection': 3.0.11
+ '@types/d3-zoom': 3.0.8
+ classcat: 5.0.5
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@reactflow/node-resizer@2.2.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ classcat: 5.0.5
+ d3-drag: 3.0.0
+ d3-selection: 3.0.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@reactflow/node-toolbar@1.3.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ classcat: 5.0.5
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.55.1':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.55.1':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.55.1':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.55.1':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.55.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.55.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.55.1':
+ optional: true
+
+ '@tabler/icons-react@3.36.1(react@18.3.1)':
+ dependencies:
+ '@tabler/icons': 3.36.1
+ react: 18.3.1
+
+ '@tabler/icons@3.36.1': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-chord@3.0.6': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-contour@3.0.6':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-delaunay@6.0.4': {}
+
+ '@types/d3-dispatch@3.0.7': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-dsv@3.0.7': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-fetch@3.0.7':
+ dependencies:
+ '@types/d3-dsv': 3.0.7
+
+ '@types/d3-force@3.0.10': {}
+
+ '@types/d3-format@3.0.4': {}
+
+ '@types/d3-geo@3.1.0':
+ dependencies:
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-hierarchy@3.1.7': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-polygon@3.0.2': {}
+
+ '@types/d3-quadtree@3.0.6': {}
+
+ '@types/d3-random@3.0.3': {}
+
+ '@types/d3-scale-chromatic@3.1.0': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-shape@3.1.8':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time-format@4.0.3': {}
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3@7.4.3':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-axis': 3.0.6
+ '@types/d3-brush': 3.0.6
+ '@types/d3-chord': 3.0.6
+ '@types/d3-color': 3.1.3
+ '@types/d3-contour': 3.0.6
+ '@types/d3-delaunay': 6.0.4
+ '@types/d3-dispatch': 3.0.7
+ '@types/d3-drag': 3.0.7
+ '@types/d3-dsv': 3.0.7
+ '@types/d3-ease': 3.0.2
+ '@types/d3-fetch': 3.0.7
+ '@types/d3-force': 3.0.10
+ '@types/d3-format': 3.0.4
+ '@types/d3-geo': 3.1.0
+ '@types/d3-hierarchy': 3.1.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-path': 3.1.1
+ '@types/d3-polygon': 3.0.2
+ '@types/d3-quadtree': 3.0.6
+ '@types/d3-random': 3.0.3
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.11
+ '@types/d3-shape': 3.1.8
+ '@types/d3-time': 3.0.4
+ '@types/d3-time-format': 4.0.3
+ '@types/d3-timer': 3.0.2
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+
+ '@types/estree@1.0.8': {}
+
+ '@types/geojson@7946.0.16': {}
+
+ '@types/parse-json@4.0.2': {}
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.27)':
+ dependencies:
+ '@types/react': 18.3.27
+
+ '@types/react@18.3.27':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
+ '@vitejs/plugin-react@4.7.0(vite@5.4.21)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21
+ transitivePeerDependencies:
+ - supports-color
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ babel-plugin-macros@3.1.0:
+ dependencies:
+ '@babel/runtime': 7.28.6
+ cosmiconfig: 7.1.0
+ resolve: 1.22.11
+
+ baseline-browser-mapping@2.9.14: {}
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.14
+ caniuse-lite: 1.0.30001764
+ electron-to-chromium: 1.5.267
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ callsites@3.1.0: {}
+
+ caniuse-lite@1.0.30001764: {}
+
+ classcat@5.0.5: {}
+
+ clsx@1.1.1: {}
+
+ convert-source-map@1.9.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ cosmiconfig@7.1.0:
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.1
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.2
+
+ csstype@3.0.9: {}
+
+ csstype@3.2.3: {}
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ dagre@0.8.5:
+ dependencies:
+ graphlib: 2.1.8
+ lodash: 4.17.21
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ detect-node-es@1.1.0: {}
+
+ electron-to-chromium@1.5.267: {}
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ esbuild@0.21.5:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ find-root@1.1.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-nonce@1.0.1: {}
+
+ graphlib@2.1.8:
+ dependencies:
+ lodash: 4.17.21
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ is-arrayish@0.2.1: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ js-tokens@4.0.0: {}
+
+ jsesc@3.1.0: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json5@2.2.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ lodash@4.17.21: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.27: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ path-parse@1.0.7: {}
+
+ path-type@4.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-is@16.13.1: {}
+
+ react-refresh@0.17.0: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-remove-scroll@2.7.2(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@18.3.1)
+ react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@18.3.27)(react@18.3.1)
+ use-sidecar: 1.1.3(@types/react@18.3.27)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-style-singleton@2.2.3(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-textarea-autosize@8.3.4(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.28.6
+ react: 18.3.1
+ use-composed-ref: 1.4.0(@types/react@18.3.27)(react@18.3.1)
+ use-latest: 1.3.0(@types/react@18.3.27)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ reactflow@11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@reactflow/background': 11.3.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@reactflow/controls': 11.2.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@reactflow/core': 11.11.4(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@reactflow/minimap': 11.7.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@reactflow/node-resizer': 2.2.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@reactflow/node-toolbar': 1.3.14(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ resolve-from@4.0.0: {}
+
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ rollup@4.55.1:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.55.1
+ '@rollup/rollup-android-arm64': 4.55.1
+ '@rollup/rollup-darwin-arm64': 4.55.1
+ '@rollup/rollup-darwin-x64': 4.55.1
+ '@rollup/rollup-freebsd-arm64': 4.55.1
+ '@rollup/rollup-freebsd-x64': 4.55.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.55.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.55.1
+ '@rollup/rollup-linux-arm64-gnu': 4.55.1
+ '@rollup/rollup-linux-arm64-musl': 4.55.1
+ '@rollup/rollup-linux-loong64-gnu': 4.55.1
+ '@rollup/rollup-linux-loong64-musl': 4.55.1
+ '@rollup/rollup-linux-ppc64-gnu': 4.55.1
+ '@rollup/rollup-linux-ppc64-musl': 4.55.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.55.1
+ '@rollup/rollup-linux-riscv64-musl': 4.55.1
+ '@rollup/rollup-linux-s390x-gnu': 4.55.1
+ '@rollup/rollup-linux-x64-gnu': 4.55.1
+ '@rollup/rollup-linux-x64-musl': 4.55.1
+ '@rollup/rollup-openbsd-x64': 4.55.1
+ '@rollup/rollup-openharmony-arm64': 4.55.1
+ '@rollup/rollup-win32-arm64-msvc': 4.55.1
+ '@rollup/rollup-win32-ia32-msvc': 4.55.1
+ '@rollup/rollup-win32-x64-gnu': 4.55.1
+ '@rollup/rollup-win32-x64-msvc': 4.55.1
+ fsevents: 2.3.3
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ semver@6.3.1: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.5.7: {}
+
+ stylis@4.2.0: {}
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tabbable@6.4.0: {}
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-callback-ref@1.3.3(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-composed-ref@1.4.0(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-isomorphic-layout-effect@1.2.1(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-latest@1.3.0(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.27)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-sidecar@1.1.3(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-sync-external-store@1.6.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ vite@5.4.21:
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.6
+ rollup: 4.55.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ xterm-addon-fit@0.8.0(xterm@5.3.0):
+ dependencies:
+ xterm: 5.3.0
+
+ xterm@5.3.0: {}
+
+ yallist@3.1.1: {}
+
+ yaml@1.10.2: {}
+
+ zustand@4.5.7(@types/react@18.3.27)(react@18.3.1):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ react: 18.3.1
diff --git a/src/Terrabuild.UI/src/App.tsx b/src/Terrabuild.UI/src/App.tsx
new file mode 100644
index 00000000..54514067
--- /dev/null
+++ b/src/Terrabuild.UI/src/App.tsx
@@ -0,0 +1,937 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+import {
+ AppShell,
+ Badge,
+ Box,
+ Button,
+ Checkbox,
+ Group,
+ MultiSelect,
+ Navbar,
+ NumberInput,
+ Paper,
+ ActionIcon,
+ Stack,
+ Text,
+ Title,
+ useMantineTheme,
+ useMantineColorScheme,
+} from "@mantine/core";
+import ReactFlow, {
+ Background,
+ Controls,
+ Node,
+ Edge,
+ Position,
+ applyNodeChanges,
+ useEdgesState,
+ useNodesState,
+ ReactFlowInstance,
+ MarkerType,
+} from "reactflow";
+import "reactflow/dist/style.css";
+import dagre from "dagre";
+import { Terminal } from "xterm";
+import { FitAddon } from "xterm-addon-fit";
+import "xterm/css/xterm.css";
+import {
+ IconAffiliate,
+ IconMoon,
+ IconSun,
+ IconSquareRoundedChevronDown,
+} from "@tabler/icons-react";
+
+type ProjectInfo = {
+ id: string;
+ name?: string | null;
+ directory: string;
+ hash: string;
+};
+
+type GraphNode = {
+ id: string;
+ projectId: string;
+ projectName?: string | null;
+ projectDir: string;
+ target: string;
+ dependencies: string[];
+ projectHash: string;
+ targetHash: string;
+};
+
+type GraphResponse = {
+ nodes: Record;
+};
+
+type ProjectStatus = {
+ projectId: string;
+ status: "success" | "failed";
+};
+
+type ProjectNode = {
+ id: string;
+ name?: string | null;
+ directory: string;
+ hash: string;
+ targets: GraphNode[];
+};
+
+type OperationSummary = {
+ metaCommand: string;
+ command: string;
+ arguments: string;
+ log: string;
+ exitCode: number;
+};
+
+type TargetSummary = {
+ project: string;
+ target: string;
+ operations: OperationSummary[][];
+ isSuccessful: boolean;
+ startedAt: string;
+ endedAt: string;
+ duration: string;
+ cache: string;
+ outputs?: string | null;
+};
+
+const nodeWidth = 320;
+const nodeHeight = 120;
+
+const layoutGraph = (nodes: Node[], edges: Edge[]) => {
+ const graph = new dagre.graphlib.Graph();
+ graph.setDefaultEdgeLabel(() => ({}));
+ graph.setGraph({ rankdir: "LR", nodesep: 90, ranksep: 140 });
+
+ nodes.forEach((node) => {
+ graph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
+ });
+ edges.forEach((edge) => graph.setEdge(edge.source, edge.target));
+
+ dagre.layout(graph);
+
+ const layoutedNodes = nodes.map((node) => {
+ const position = graph.node(node.id);
+ return {
+ ...node,
+ position: {
+ x: position.x - nodeWidth / 2,
+ y: position.y - nodeHeight / 2,
+ },
+ };
+ });
+
+ return { nodes: layoutedNodes, edges };
+};
+
+const App = () => {
+ const [targets, setTargets] = useState([]);
+ const [projects, setProjects] = useState([]);
+ const [selectedTargets, setSelectedTargets] = useState([]);
+ const [selectedProjects, setSelectedProjects] = useState([]);
+ const [graph, setGraph] = useState(null);
+ const [graphError, setGraphError] = useState(null);
+ const [buildError, setBuildError] = useState(null);
+ const [buildRunning, setBuildRunning] = useState(false);
+ const [forceBuild, setForceBuild] = useState(false);
+ const [retryBuild, setRetryBuild] = useState(false);
+ const [parallelism, setParallelism] = useState("");
+ const [selectedProject, setSelectedProject] = useState(
+ null
+ );
+ const [selectedNodeId, setSelectedNodeId] = useState(null);
+ const [selectedTargetKey, setSelectedTargetKey] = useState(
+ null
+ );
+ const [projectStatus, setProjectStatus] = useState<
+ Record
+ >({});
+ const [showTerminal, setShowTerminal] = useState(false);
+ const [nodeResults, setNodeResults] = useState>(
+ {}
+ );
+ const [layoutVersion, setLayoutVersion] = useState(0);
+ const [manualPositions, setManualPositions] = useState<
+ Record
+ >({});
+ const [nodes, setNodes] = useNodesState([]);
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+ const flowInstance = useRef(null);
+ const { colorScheme, toggleColorScheme } = useMantineColorScheme();
+ const theme = useMantineTheme();
+
+ const terminalRef = useRef(null);
+ const terminal = useRef(null);
+ const fitAddon = useRef(null);
+ const logAbort = useRef(null);
+ const terminalReady = useRef(false);
+
+ useEffect(() => {
+ const term = new Terminal({
+ convertEol: true,
+ scrollback: 3000,
+ fontSize: 12,
+ });
+ const fit = new FitAddon();
+ term.loadAddon(fit);
+ terminal.current = term;
+ fitAddon.current = fit;
+ if (terminalRef.current) {
+ term.open(terminalRef.current);
+ terminalReady.current = true;
+ const resizeObserver = new ResizeObserver(() => {
+ if (!terminalRef.current) {
+ return;
+ }
+ if (terminalRef.current.offsetWidth === 0) {
+ return;
+ }
+ fit.fit();
+ });
+ resizeObserver.observe(terminalRef.current);
+ requestAnimationFrame(() => {
+ if (!terminalRef.current) {
+ return;
+ }
+ if (terminalRef.current.offsetWidth === 0) {
+ return;
+ }
+ fit.fit();
+ });
+ return () => {
+ resizeObserver.disconnect();
+ term.dispose();
+ };
+ }
+ const handleResize = () => fit.fit();
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ term.dispose();
+ };
+ }, []);
+
+ useEffect(() => {
+ if (!terminal.current) {
+ return;
+ }
+ if (!terminalReady.current) {
+ return;
+ }
+ if (!terminalRef.current || terminalRef.current.offsetWidth === 0) {
+ return;
+ }
+ terminal.current.options.theme = {
+ background: colorScheme === "dark" ? "#141517" : "#ffffff",
+ foreground: colorScheme === "dark" ? "#d8dbe0" : "#1f2328",
+ selectionBackground: colorScheme === "dark" ? "#3b3f45" : "#c9d0d8",
+ };
+ }, [colorScheme]);
+
+ const getNodeStyle = (nodeId: string) => {
+ const isDark = colorScheme === "dark";
+ const defaultBorder = isDark ? theme.colors.dark[3] : theme.colors.gray[6];
+ const selectedBorder = theme.colors.blue[6];
+ const nodeBackground = isDark ? theme.colors.dark[6] : theme.white;
+ const nodeText = isDark ? theme.colors.gray[1] : theme.black;
+ const status = projectStatus[nodeId];
+ const statusColor =
+ status === "failed"
+ ? theme.fn.rgba(theme.colors.red[6], isDark ? 0.35 : 0.15)
+ : status === "success"
+ ? theme.fn.rgba(theme.colors.green[6], isDark ? 0.35 : 0.15)
+ : null;
+ return {
+ borderRadius: 12,
+ borderStyle: "solid",
+ borderWidth: nodeId === selectedNodeId ? 2 : 1,
+ borderColor: nodeId === selectedNodeId ? selectedBorder : defaultBorder,
+ background: statusColor ?? nodeBackground,
+ color: nodeText,
+ padding: 8,
+ fontSize: 32,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ textAlign: "center",
+ width: "fit-content",
+ minHeight: 80,
+ whiteSpace: "nowrap",
+ boxShadow:
+ nodeId === selectedNodeId
+ ? "0 0 0 2px rgba(34, 139, 230, 0.2)"
+ : "none",
+ };
+ };
+
+ useEffect(() => {
+ const selected = nodes.find((node) => node.selected);
+ setSelectedNodeId(selected ? selected.id : null);
+ }, [nodes]);
+
+ useEffect(() => {
+ setNodes((current) =>
+ current.map((node) => ({
+ ...node,
+ style: getNodeStyle(node.id),
+ }))
+ );
+ }, [selectedNodeId, colorScheme, theme, projectStatus, setNodes]);
+
+ useEffect(() => {
+ const load = async () => {
+ const [targetsRes, projectsRes] = await Promise.all([
+ fetch("/api/targets"),
+ fetch("/api/projects"),
+ ]);
+ if (targetsRes.ok) {
+ setTargets(await targetsRes.json());
+ }
+ if (projectsRes.ok) {
+ setProjects(await projectsRes.json());
+ }
+ };
+ load().catch(() => null);
+ }, []);
+
+ useEffect(() => {
+ const fetchGraph = async () => {
+ if (selectedTargets.length === 0) {
+ setGraph(null);
+ setGraphError("Select at least one target to load the graph.");
+ return;
+ }
+ setGraphError(null);
+ const params = new URLSearchParams();
+ selectedTargets.forEach((target) => params.append("targets", target));
+ selectedProjects.forEach((project) => params.append("projects", project));
+ const response = await fetch(`/api/graph?${params.toString()}`);
+ if (!response.ok) {
+ setGraphError(await response.text());
+ setGraph(null);
+ return;
+ }
+ const data = (await response.json()) as GraphResponse;
+ setManualPositions({});
+ setGraph(data);
+
+ const statusResponse = await fetch(
+ `/api/build/status?${params.toString()}`
+ );
+ if (statusResponse.ok) {
+ const statusData = (await statusResponse.json()) as ProjectStatus[];
+ const statusMap: Record = {};
+ statusData.forEach((item) => {
+ statusMap[item.projectId] = item.status;
+ });
+ setProjectStatus(statusMap);
+ } else {
+ setProjectStatus({});
+ }
+ };
+ fetchGraph().catch(() => {
+ setGraphError("Failed to load graph.");
+ setGraph(null);
+ });
+ }, [selectedTargets, selectedProjects]);
+
+ const baseGraph = useMemo(() => {
+ if (!graph) {
+ return { nodes: [], edges: [] };
+ }
+ const projectMap = new Map();
+ const nodeMap = new Map();
+ Object.values(graph.nodes).forEach((node) => {
+ nodeMap.set(node.id, node);
+ const existing = projectMap.get(node.projectId);
+ if (existing) {
+ existing.targets.push(node);
+ } else {
+ projectMap.set(node.projectId, {
+ id: node.projectId,
+ name: node.projectName,
+ directory: node.projectDir,
+ hash: node.projectHash,
+ targets: [node],
+ });
+ }
+ });
+
+ const isDark = colorScheme === "dark";
+ const edgeStroke = isDark ? theme.colors.dark[3] : theme.colors.gray[5];
+
+ const flowNodes: Node[] = Array.from(projectMap.values())
+ .filter((project) => project.directory !== ".")
+ .map((project) => ({
+ id: project.id,
+ data: {
+ label: project.directory,
+ meta: project,
+ },
+ position: { x: 0, y: 0 },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ style: getNodeStyle(project.id),
+ }));
+
+ const visibleProjects = new Set(flowNodes.map((node) => node.id));
+ const edgeSet = new Set();
+ const flowEdges: Edge[] = [];
+ nodeMap.forEach((node) => {
+ node.dependencies.forEach((dependency) => {
+ const depNode = nodeMap.get(dependency);
+ if (!depNode) {
+ return;
+ }
+ if (depNode.projectId === node.projectId) {
+ return;
+ }
+ if (
+ !visibleProjects.has(depNode.projectId) ||
+ !visibleProjects.has(node.projectId)
+ ) {
+ return;
+ }
+ const edgeId = `${depNode.projectId}->${node.projectId}`;
+ if (edgeSet.has(edgeId)) {
+ return;
+ }
+ edgeSet.add(edgeId);
+ flowEdges.push({
+ id: edgeId,
+ source: depNode.projectId,
+ target: node.projectId,
+ type: "default",
+ style: { stroke: edgeStroke },
+ markerStart: {
+ type: MarkerType.ArrowClosed,
+ color: edgeStroke,
+ width: 52,
+ height: 52,
+ },
+ });
+ });
+ });
+ return layoutGraph(flowNodes, flowEdges);
+ }, [graph, selectedNodeId, layoutVersion, colorScheme, theme]);
+
+ useEffect(() => {
+ if (!graph) {
+ setNodes([]);
+ setEdges([]);
+ return;
+ }
+ const manualKeys = Object.keys(manualPositions);
+ if (manualKeys.length > 0) {
+ setNodes((current) =>
+ current.map((node) => ({
+ ...node,
+ position: manualPositions[node.id] ?? node.position,
+ }))
+ );
+ return;
+ }
+ setNodes(baseGraph.nodes);
+ setEdges(baseGraph.edges);
+ requestAnimationFrame(() => {
+ flowInstance.current?.fitView({
+ padding: 0.5,
+ duration: 300,
+ minZoom: 0.1,
+ });
+ });
+ }, [graph, baseGraph, manualPositions, setNodes, setEdges]);
+
+ const handleSelectTargets = (event: React.ChangeEvent) => {
+ const values = Array.from(event.target.selectedOptions).map(
+ (option) => option.value
+ );
+ setSelectedTargets(values);
+ };
+
+ const handleSelectProjects = (event: React.ChangeEvent) => {
+ const values = Array.from(event.target.selectedOptions).map(
+ (option) => option.value
+ );
+ setSelectedProjects(values);
+ };
+
+ const startLogStream = async () => {
+ if (!terminal.current) {
+ return;
+ }
+ terminal.current.reset();
+ setShowTerminal(true);
+ logAbort.current?.abort();
+ const controller = new AbortController();
+ logAbort.current = controller;
+
+ const response = await fetch("/api/build/log", { signal: controller.signal });
+ if (!response.body) {
+ return;
+ }
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) {
+ break;
+ }
+ if (value) {
+ const chunk = decoder.decode(value, { stream: true });
+ terminal.current.write(chunk);
+ }
+ }
+ setBuildRunning(false);
+ };
+
+ const startBuild = async () => {
+ if (selectedTargets.length === 0 || buildRunning) {
+ return;
+ }
+ setBuildRunning(true);
+ setBuildError(null);
+ const parallel =
+ parallelism.trim().length > 0 ? Number(parallelism) : null;
+ const payload = {
+ targets: selectedTargets,
+ projects: selectedProjects,
+ parallelism: parallel && parallel > 0 ? parallel : undefined,
+ force: forceBuild,
+ retry: retryBuild,
+ };
+ const response = await fetch("/api/build", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+ if (!response.ok) {
+ setBuildRunning(false);
+ setBuildError(await response.text());
+ return;
+ }
+ startLogStream().catch(() => {
+ setBuildRunning(false);
+ });
+ };
+
+ useEffect(() => {
+ if (buildRunning) {
+ return;
+ }
+ if (selectedTargets.length === 0) {
+ return;
+ }
+ const refresh = async () => {
+ const params = new URLSearchParams();
+ selectedTargets.forEach((target) => params.append("targets", target));
+ selectedProjects.forEach((project) => params.append("projects", project));
+ const response = await fetch(`/api/graph?${params.toString()}`);
+ if (response.ok) {
+ const data = (await response.json()) as GraphResponse;
+ setManualPositions({});
+ setGraph(data);
+ }
+ const statusResponse = await fetch(
+ `/api/build/status?${params.toString()}`
+ );
+ if (statusResponse.ok) {
+ const statusData = (await statusResponse.json()) as ProjectStatus[];
+ const statusMap: Record = {};
+ statusData.forEach((item) => {
+ statusMap[item.projectId] = item.status;
+ });
+ setProjectStatus(statusMap);
+ }
+ };
+ refresh().catch(() => null);
+ }, [buildRunning, selectedTargets, selectedProjects]);
+
+ const loadProjectResults = async (project: ProjectNode) => {
+ setSelectedProject(project);
+ setSelectedNodeId(project.id);
+ setSelectedTargetKey(null);
+ await Promise.all(
+ project.targets.map(async (node) => {
+ const cacheKey = `${node.projectHash}/${node.target}/${node.targetHash}`;
+ if (nodeResults[cacheKey]) {
+ return;
+ }
+ const response = await fetch(
+ `/api/build/result/${node.projectHash}/${node.target}/${node.targetHash}`
+ );
+ if (!response.ok) {
+ return;
+ }
+ const summary = (await response.json()) as TargetSummary;
+ setNodeResults((prev) => ({ ...prev, [cacheKey]: summary }));
+ })
+ );
+ if (project.targets.length > 0) {
+ const first = project.targets[0];
+ const cacheKey = `${first.projectHash}/${first.target}/${first.targetHash}`;
+ await showTargetLog(cacheKey, first);
+ }
+ };
+
+ const buildTargetLog = (summary: TargetSummary) => {
+ return summary.operations
+ .flatMap((group) =>
+ group.map((operation) => {
+ const header = operation.metaCommand || operation.command;
+ return `${header}\n${operation.log || ""}`.trim();
+ })
+ )
+ .filter((value) => value.length > 0)
+ .join("\n\n");
+ };
+
+ const showTargetLog = async (key: string, target: GraphNode) => {
+ if (!terminal.current) {
+ return;
+ }
+ setSelectedTargetKey(key);
+ terminal.current.reset();
+ setShowTerminal(true);
+ try {
+ const response = await fetch(
+ `/api/build/target-log/${target.projectHash}/${target.target}/${target.targetHash}`
+ );
+ if (!response.ok) {
+ terminal.current.write("No cached log available.\n");
+ return;
+ }
+ const log = await response.text();
+ terminal.current.write(log.length > 0 ? log : "No cached log available.\n");
+ } catch {
+ terminal.current.write("Failed to load cached log.\n");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Terrabuild
+
+ Graph Console
+
+
+ toggleColorScheme()}
+ variant="light"
+ size="lg"
+ aria-label="Toggle color scheme"
+ >
+ {colorScheme === "dark" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ ({ value: target, label: target }))}
+ label="Targets (required)"
+ placeholder="Select targets"
+ searchable
+ nothingFound="No targets"
+ value={selectedTargets}
+ onChange={(values) => setSelectedTargets(values)}
+ />
+
+ project.name)
+ .map((project) => ({
+ value: project.name as string,
+ label: project.name as string,
+ }))}
+ label="Projects (optional)"
+ placeholder="Select projects"
+ searchable
+ nothingFound="No projects"
+ value={selectedProjects}
+ onChange={(values) => setSelectedProjects(values)}
+ />
+
+
+ setForceBuild(event.currentTarget.checked)}
+ />
+ setRetryBuild(event.currentTarget.checked)}
+ />
+
+
+ {
+ if (value === "" || value === null) {
+ setParallelism("");
+ } else {
+ setParallelism(String(value));
+ }
+ }}
+ />
+
+
+
+ {buildError && (
+
+ {buildError}
+
+ )}
+
+
+
+
+
+ Node Details
+ {selectedProject ? (
+ <>
+ {selectedProject.directory}
+
+ {selectedProject.targets.map((target) => {
+ const cacheKey = `${target.projectHash}/${target.target}/${target.targetHash}`;
+ const summary = nodeResults[cacheKey];
+ return (
+
+ );
+ })}
+
+ >
+ ) : (
+
+ Select a node in the graph to inspect it.
+
+ )}
+
+
+
+
+ }
+ styles={{ main: { height: "100vh" } }}
+ >
+
+
+
+
+ Execution Graph
+
+ {graphError && (
+
+ {graphError}
+
+ )}
+ {
+ setManualPositions({});
+ setLayoutVersion((value) => value + 1);
+ }}
+ aria-label="Reflow graph"
+ >
+
+
+
+
+
+ {graph ? (
+ {
+ flowInstance.current = instance;
+ }}
+ proOptions={{ hideAttribution: true }}
+ nodesDraggable
+ elementsSelectable
+ panOnDrag
+ onNodesChange={(changes) => {
+ setNodes((current) => {
+ const updated = applyNodeChanges(changes, current);
+ const positions: Record =
+ {};
+ updated.forEach((node) => {
+ positions[node.id] = node.position;
+ if (node.selected) {
+ setSelectedNodeId(node.id);
+ }
+ });
+ setManualPositions(positions);
+ return updated;
+ });
+ }}
+ onEdgesChange={onEdgesChange}
+ onNodeClick={(_, node) =>
+ loadProjectResults(node.data.meta as ProjectNode)
+ }
+ >
+
+
+
+ ) : (
+
+
+ Select at least one target to view the graph.
+
+
+ )}
+
+
+
+
+ {showTerminal && (
+
+ Build Log
+
+
+ {buildRunning ? "Live" : "Idle"}
+
+ setShowTerminal(false)}
+ aria-label="Hide terminal"
+ >
+
+
+
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/src/Terrabuild.UI/src/main.tsx b/src/Terrabuild.UI/src/main.tsx
new file mode 100644
index 00000000..6e2bae13
--- /dev/null
+++ b/src/Terrabuild.UI/src/main.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core";
+import { useLocalStorage } from "@mantine/hooks";
+import App from "./App";
+import "./styles.css";
+
+const Root = () => {
+ const [colorScheme, setColorScheme] = useLocalStorage({
+ key: "tb-color-scheme",
+ defaultValue: "dark",
+ getInitialValueInEffect: true,
+ });
+
+ const toggleColorScheme = (value?: ColorScheme) =>
+ setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"));
+
+ return (
+
+
+
+
+
+ );
+};
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+
+);
diff --git a/src/Terrabuild.UI/src/styles.css b/src/Terrabuild.UI/src/styles.css
new file mode 100644
index 00000000..6401d5b4
--- /dev/null
+++ b/src/Terrabuild.UI/src/styles.css
@@ -0,0 +1,17 @@
+.terminal-body {
+ flex: 1;
+ min-height: 0;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+ "Liberation Mono", "Courier New", monospace;
+}
+
+@keyframes terminal-slide {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/src/Terrabuild.UI/tsconfig.json b/src/Terrabuild.UI/tsconfig.json
new file mode 100644
index 00000000..a823d001
--- /dev/null
+++ b/src/Terrabuild.UI/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "jsx": "react-jsx",
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "types": ["vite/client"]
+ },
+ "include": ["src"]
+}
diff --git a/src/Terrabuild.UI/tsconfig.node.json b/src/Terrabuild.UI/tsconfig.node.json
new file mode 100644
index 00000000..16dfedc6
--- /dev/null
+++ b/src/Terrabuild.UI/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/src/Terrabuild.UI/vite.config.ts b/src/Terrabuild.UI/vite.config.ts
new file mode 100644
index 00000000..bf2d4988
--- /dev/null
+++ b/src/Terrabuild.UI/vite.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+const apiPort = process.env.TB_API_PORT ?? "5179";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ "/api": `http://127.0.0.1:${apiPort}`,
+ },
+ },
+ build: {
+ outDir: "dist",
+ emptyOutDir: true,
+ },
+});
diff --git a/src/Terrabuild/CLI.fs b/src/Terrabuild/CLI.fs
index b88d067d..3fcac54c 100644
--- a/src/Terrabuild/CLI.fs
+++ b/src/Terrabuild/CLI.fs
@@ -103,6 +103,19 @@ with
| Type _-> "Select projects based on extension types."
| Project _ -> "Select projets base on id."
+[]
+type GraphArgs =
+ | [] Workspace of path:string
+ | [] No_Open
+ | [] Port of port:int
+with
+ interface IArgParserTemplate with
+ member this.Usage =
+ match this with
+ | Workspace _ -> "Root of workspace. If not specified, current directory is used."
+ | No_Open -> "Do not open the browser automatically."
+ | Port _ -> "Port to bind the graph API and UI server."
+
[]
type ClearArgs =
@@ -145,6 +158,7 @@ type TerrabuildArgs =
| [] Logs of ParseResults
| [] Run of ParseResults
| [] Serve of ParseResults
+ | [] Graph of ParseResults
| [] Clear of ParseResults
| [] Login of ParseResults
| [] Logout of ParseResults
@@ -159,6 +173,7 @@ with
| Logs _ -> "dump logs."
| Run _ -> "Run specified targets."
| Serve _ -> "Serve specified targets."
+ | Graph _ -> "Visualize and build graph in a web UI."
| Clear _ -> "Clear specified caches."
| Login _ -> "Connect to backend."
| Logout _ -> "Disconnect from backend."
diff --git a/src/Terrabuild/Core/Configuration.fs b/src/Terrabuild/Core/Configuration.fs
index 3c6d4537..26662299 100644
--- a/src/Terrabuild/Core/Configuration.fs
+++ b/src/Terrabuild/Core/Configuration.fs
@@ -717,7 +717,7 @@ let read (options: ConfigOptions.Options) =
$"Targets [{targets}]"
if labels.IsSome then $"Labels [{labels.Value}]"
if types.IsSome then $"Types [{types.Value}]"
- if projects.IsSome then $"Types [{projects.Value}]"
+ if projects.IsSome then $"Projects [{projects.Value}]"
]
configInfos |> List.iter (fun configInfo -> $" {Ansi.Styles.green}{Ansi.Emojis.arrow}{Ansi.Styles.reset} {configInfo}" |> Terminal.writeLine)
diff --git a/src/Terrabuild/Helpers/Terminal.fs b/src/Terrabuild/Helpers/Terminal.fs
index ac2b5832..615d0e7e 100644
--- a/src/Terrabuild/Helpers/Terminal.fs
+++ b/src/Terrabuild/Helpers/Terminal.fs
@@ -21,8 +21,15 @@ let private terms = [
"alacritty" // Alacritty
]
+let private forceAnsi =
+ match System.Environment.GetEnvironmentVariable("TB_FORCE_ANSI") |> Option.ofObj with
+ | Some "1" -> true
+ | Some "true" -> true
+ | Some "TRUE" -> true
+ | _ -> false
+
let supportAnsi =
- not Console.IsOutputRedirected
+ (forceAnsi || not Console.IsOutputRedirected)
&&
match System.Environment.GetEnvironmentVariable("TERM") |> Option.ofObj with
| Some currTerm ->
diff --git a/src/Terrabuild/PROJECT b/src/Terrabuild/PROJECT
index 08aaf4f8..97ea2335 100644
--- a/src/Terrabuild/PROJECT
+++ b/src/Terrabuild/PROJECT
@@ -2,7 +2,7 @@
project terrabuild {
labels = [ "app"
"dotnet"]
-
+ depends_on = [ project.terrabuild_ui ]
@dotnet { }
}
diff --git a/src/Terrabuild/Program.fs b/src/Terrabuild/Program.fs
index 54c238a9..44c26919 100644
--- a/src/Terrabuild/Program.fs
+++ b/src/Terrabuild/Program.fs
@@ -300,6 +300,9 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe
RunTargetOptions.Engine = None }
runTarget options
+ let graph (graphArgs: ParseResults) =
+ GraphServer.start graphArgs
+
let logs (logsArgs: ParseResults) =
let targets = logsArgs.GetResult(LogsArgs.Target) |> Seq.map String.toLower
let wsDir =
@@ -366,6 +369,7 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe
| p when p.Contains(TerrabuildArgs.Logs) -> p.GetResult(TerrabuildArgs.Logs) |> logs
| p when p.Contains(TerrabuildArgs.Run) -> p.GetResult(TerrabuildArgs.Run) |> run
| p when p.Contains(TerrabuildArgs.Serve) -> p.GetResult(TerrabuildArgs.Serve) |> serve
+ | p when p.Contains(TerrabuildArgs.Graph) -> p.GetResult(TerrabuildArgs.Graph) |> graph
| p when p.Contains(TerrabuildArgs.Clear) -> p.GetResult(TerrabuildArgs.Clear) |> clear
| p when p.Contains(TerrabuildArgs.Login) -> p.GetResult(TerrabuildArgs.Login) |> login
| p when p.Contains(TerrabuildArgs.Logout) -> p.GetResult(TerrabuildArgs.Logout) |> logout
diff --git a/src/Terrabuild/Terrabuild.fsproj b/src/Terrabuild/Terrabuild.fsproj
index ef73d5e1..a43eba9f 100644
--- a/src/Terrabuild/Terrabuild.fsproj
+++ b/src/Terrabuild/Terrabuild.fsproj
@@ -41,6 +41,7 @@
+
@@ -54,6 +55,7 @@
+
@@ -74,6 +76,14 @@
+
+
+ PreserveNewest
+ PreserveNewest
+ ui\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
Terrabuild
@@ -89,4 +99,4 @@
Terrabuild.png
-
\ No newline at end of file
+
diff --git a/src/Terrabuild/Web/GraphServer.fs b/src/Terrabuild/Web/GraphServer.fs
new file mode 100644
index 00000000..0e5ae244
--- /dev/null
+++ b/src/Terrabuild/Web/GraphServer.fs
@@ -0,0 +1,502 @@
+module GraphServer
+
+open System
+open System.IO
+open System.Text
+open System.Diagnostics
+open System.Threading
+open System.Threading.Channels
+open System.Threading.Tasks
+open System.Collections.Concurrent
+open System.Runtime.InteropServices
+open Argu
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.Http
+open Microsoft.Extensions.FileProviders
+open Microsoft.Extensions.Hosting
+open Microsoft.Extensions.DependencyInjection
+open Collections
+open Environment
+open CLI
+open Errors
+
+type BuildRequest = {
+ Targets: string list option
+ Projects: string list option
+ Parallelism: int option
+ Force: bool option
+ Retry: bool option
+}
+
+type ProjectInfo = {
+ Id: string
+ Name: string option
+ Directory: string
+ Hash: string
+}
+
+type ProjectStatus = {
+ ProjectId: string
+ Status: string
+}
+
+type BuildLogState = {
+ Buffer: StringBuilder
+ Subscribers: ConcurrentDictionary>
+ Lock: obj
+}
+
+type BuildState = {
+ mutable Active: Process option
+ mutable ExitCode: int option
+ Lock: obj
+}
+
+let rec private findWorkspace dir =
+ if FS.combinePath dir "WORKSPACE" |> IO.exists then
+ Some dir
+ else
+ dir |> FS.parentDirectory |> Option.bind findWorkspace
+
+let private createLogState () =
+ { Buffer = StringBuilder()
+ Subscribers = ConcurrentDictionary>()
+ Lock = obj() }
+
+let private createBuildState () =
+ { Active = None
+ ExitCode = None
+ Lock = obj() }
+
+let private appendLog (state: BuildLogState) (text: string) =
+ if String.IsNullOrEmpty(text) |> not then
+ lock state.Lock (fun () ->
+ state.Buffer.Append(text) |> ignore
+ )
+ for KeyValue(_, channel) in state.Subscribers do
+ channel.Writer.TryWrite(text) |> ignore
+
+let private clearLog (state: BuildLogState) =
+ lock state.Lock (fun () ->
+ state.Buffer.Clear() |> ignore
+ )
+
+let private completeLogStreams (state: BuildLogState) =
+ for KeyValue(_, channel) in state.Subscribers do
+ channel.Writer.TryComplete() |> ignore
+
+let private logSnapshot (state: BuildLogState) =
+ lock state.Lock (fun () -> state.Buffer.ToString())
+
+let private parseCsvValues (values: Microsoft.Extensions.Primitives.StringValues) =
+ values.ToArray()
+ |> Array.choose (fun (value: string | null) ->
+ if isNull value || value = "" then None else Some (string value))
+ |> Array.collect (fun (value: string) ->
+ value.Split(',', StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries))
+ |> Array.toList
+
+let private readBody (ctx: HttpContext) =
+ task {
+ use reader = new StreamReader(ctx.Request.Body, Encoding.UTF8)
+ return! reader.ReadToEndAsync()
+ }
+
+let private buildConfig (workspace: string) (targets: string list) (projects: string list option) =
+ let previousDir = System.Environment.CurrentDirectory
+ System.Environment.CurrentDirectory <- workspace
+ try
+ let homeDir = Cache.createHome()
+ let tmpDir = Cache.createTmp()
+ let sharedDir = ".terrabuild"
+ IO.createDirectory sharedDir
+ let sourceControl = SourceControls.Factory.create()
+ let options =
+ { ConfigOptions.Options.Workspace = workspace
+ ConfigOptions.Options.HomeDir = homeDir
+ ConfigOptions.Options.TmpDir = tmpDir
+ ConfigOptions.Options.SharedDir = sharedDir
+ ConfigOptions.Options.WhatIf = false
+ ConfigOptions.Options.Debug = false
+ ConfigOptions.Options.MaxConcurrency = 1
+ ConfigOptions.Options.Force = false
+ ConfigOptions.Options.Retry = false
+ ConfigOptions.Options.LocalOnly = true
+ ConfigOptions.Options.StartedAt = DateTime.UtcNow
+ ConfigOptions.Options.Targets = targets |> Seq.map String.toLower |> Set
+ ConfigOptions.Options.LogTypes = sourceControl.LogTypes
+ ConfigOptions.Options.Configuration = None
+ ConfigOptions.Options.Environment = None
+ ConfigOptions.Options.Note = None
+ ConfigOptions.Options.Label = None
+ ConfigOptions.Options.Types = None
+ ConfigOptions.Options.Labels = None
+ ConfigOptions.Options.Projects = projects |> Option.map (fun items -> items |> Seq.map String.toLower |> Set)
+ ConfigOptions.Options.Variables = Map.empty
+ ConfigOptions.Options.Engine = None
+ ConfigOptions.Options.HeadCommit = sourceControl.HeadCommit
+ ConfigOptions.Options.CommitLog = sourceControl.CommitLog
+ ConfigOptions.Options.BranchOrTag = sourceControl.BranchOrTag
+ ConfigOptions.Options.Run = sourceControl.Run }
+ Configuration.read options
+ finally
+ System.Environment.CurrentDirectory <- previousDir
+
+let private buildBatchGraph workspace targets projects =
+ let options, config = buildConfig workspace targets projects
+ let cache = Cache.Cache(Storages.Factory.create None, None) :> Cache.ICache
+ let options = { options with MaxConcurrency = System.Environment.ProcessorCount |> max 1 }
+ let graph = GraphPipeline.Node.build options config
+ let graph = GraphPipeline.Action.build options cache graph
+ let graph = GraphPipeline.Cascade.build graph
+ GraphPipeline.Batch.build options config graph
+
+let private resolveWorkspace (workspace: string option) =
+ match workspace with
+ | Some ws -> ws
+ | _ ->
+ match Environment.currentDir() |> findWorkspace with
+ | Some ws -> ws
+ | _ -> raiseInvalidArg "Can't find workspace root directory. Check you are in a workspace."
+
+let private pickPort () =
+ let listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0)
+ listener.Start()
+ let port = (listener.LocalEndpoint :?> Net.IPEndPoint).Port
+ listener.Stop()
+ port
+
+let private isDotnetHost (path: string) =
+ let file =
+ Path.GetFileName(path)
+ |> Option.ofObj
+ |> Option.defaultValue ""
+ |> fun name -> name.ToLowerInvariant()
+ file = "dotnet" || file = "dotnet.exe"
+
+let private openBrowser (url: string) =
+ try
+ if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
+ Process.Start(ProcessStartInfo("cmd", $"/c start {url}")) |> ignore
+ elif RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then
+ Process.Start(ProcessStartInfo("open", url)) |> ignore
+ else
+ Process.Start(ProcessStartInfo("xdg-open", url)) |> ignore
+ with _ -> ()
+
+let private createBuildCommand (workspace: string) (request: BuildRequest) =
+ let exePath = System.Environment.ProcessPath |> Option.ofObj |> Option.defaultValue "dotnet"
+ let assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location
+ let targets = request.Targets |> Option.defaultValue [] |> Seq.map String.toLower |> String.join " "
+ let projectArgs =
+ match request.Projects with
+ | Some projects when projects.Length > 0 ->
+ projects |> Seq.map String.toLower |> String.join " " |> sprintf " -p %s"
+ | _ -> ""
+ let parallelArg =
+ match request.Parallelism with
+ | Some value when value > 0 -> $" --parallel {value}"
+ | _ -> ""
+ let forceArg = if request.Force |> Option.defaultValue false then " -f" else ""
+ let retryArg = if request.Retry |> Option.defaultValue false then " -r" else ""
+ let baseArgs = $"run {targets} -w \"{workspace}\"{projectArgs}{parallelArg}{forceArg}{retryArg}"
+ if isDotnetHost exePath then
+ exePath, $"\"{assemblyPath}\" {baseArgs}"
+ else
+ exePath, baseArgs
+
+let private startBuildProcess (workspace: string) (request: BuildRequest) (logState: BuildLogState) (buildState: BuildState) =
+ lock buildState.Lock (fun () ->
+ match buildState.Active with
+ | Some proc when not proc.HasExited -> Error "Build already running."
+ | _ ->
+ clearLog logState
+ completeLogStreams logState
+ let command, args = createBuildCommand workspace request
+ let psi =
+ ProcessStartInfo(
+ FileName = command,
+ Arguments = args,
+ WorkingDirectory = workspace,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false
+ )
+ psi.EnvironmentVariables["TB_FORCE_ANSI"] <- "1"
+ psi.EnvironmentVariables["TERM"] <- "xterm-256color"
+ let proc = new Process(StartInfo = psi, EnableRaisingEvents = true)
+ if proc.Start() |> not then
+ Error "Failed to start build process."
+ else
+ buildState.Active <- Some proc
+ buildState.ExitCode <- None
+
+ let streamToLog (stream: Stream) =
+ task {
+ use reader = new StreamReader(stream, Encoding.UTF8)
+ let buffer = Array.zeroCreate 4096
+ let mutable keepReading = true
+ while keepReading do
+ let! read = reader.ReadAsync(buffer, 0, buffer.Length)
+ if read = 0 then
+ keepReading <- false
+ else
+ appendLog logState (String(buffer, 0, read))
+ }
+
+ streamToLog proc.StandardOutput.BaseStream |> ignore
+ streamToLog proc.StandardError.BaseStream |> ignore
+
+ proc.Exited.Add(fun _ ->
+ buildState.ExitCode <- Some proc.ExitCode
+ buildState.Active <- None
+ completeLogStreams logState
+ )
+ Ok proc.Id
+ )
+
+let start (graphArgs: ParseResults) =
+ let workspace =
+ graphArgs.TryGetResult(CLI.GraphArgs.Workspace)
+ |> resolveWorkspace
+ let shouldOpenBrowser = graphArgs.Contains(GraphArgs.No_Open) |> not
+ let uiRoot = Path.Combine(AppContext.BaseDirectory, "ui")
+ let port = graphArgs.TryGetResult(GraphArgs.Port) |> Option.defaultValue 5179
+ let url = $"http://127.0.0.1:{port}"
+ let builder = WebApplication.CreateBuilder()
+ builder.Services.AddCors() |> ignore
+ builder.WebHost.UseUrls(url) |> ignore
+
+ let app = builder.Build()
+
+ let logState = createLogState()
+ let buildState = createBuildState()
+ let workspaceLock = obj()
+
+ let fileProvider = new PhysicalFileProvider(uiRoot)
+ let defaultFiles =
+ DefaultFilesOptions(
+ FileProvider = fileProvider,
+ RequestPath = PathString.Empty
+ )
+ defaultFiles.DefaultFileNames.Clear()
+ defaultFiles.DefaultFileNames.Add("index.html")
+
+ app.UseCors(fun policy ->
+ policy
+ .AllowAnyOrigin()
+ .AllowAnyHeader()
+ .AllowAnyMethod()
+ |> ignore)
+ |> ignore
+
+ app.UseDefaultFiles(defaultFiles) |> ignore
+ app.UseStaticFiles(StaticFileOptions(FileProvider = fileProvider, RequestPath = PathString.Empty)) |> ignore
+
+ app.MapGet("/api/targets", Func>(fun _ ->
+ task {
+ let targets =
+ lock workspaceLock (fun () ->
+ let _, config = buildConfig workspace [] None
+ config.Targets |> Map.keys |> Seq.sort |> Seq.toList
+ )
+ let json = Json.Serialize targets
+ return Results.Text(json, "application/json")
+ }))
+ |> ignore
+
+ app.MapGet("/api/projects", Func>(fun _ ->
+ task {
+ let projects =
+ lock workspaceLock (fun () ->
+ let _, config = buildConfig workspace [] None
+ config.Projects
+ |> Map.toList
+ |> List.choose (fun (_, project) ->
+ match project.Name with
+ | Some name ->
+ { ProjectInfo.Id = project.Id
+ ProjectInfo.Name = Some name
+ ProjectInfo.Directory = project.Directory
+ ProjectInfo.Hash = project.Hash }
+ |> Some
+ | None -> None)
+ |> List.sortBy (fun project -> project.Name |> Option.defaultValue project.Id)
+ )
+ let json = Json.Serialize projects
+ return Results.Text(json, "application/json")
+ }))
+ |> ignore
+
+ app.MapGet("/api/graph", Func>(fun ctx ->
+ task {
+ let targets = ctx.Request.Query.["targets"] |> parseCsvValues
+ let projects = ctx.Request.Query.["projects"] |> parseCsvValues |> function | [] -> None | values -> Some values
+ if targets.IsEmpty then
+ return Results.BadRequest("At least one target is required.")
+ else
+ let graph =
+ lock workspaceLock (fun () ->
+ buildBatchGraph workspace targets projects
+ )
+ let json = Json.Serialize graph
+ return Results.Text(json, "application/json")
+ }))
+ |> ignore
+
+ app.MapGet("/api/build/status", Func>(fun ctx ->
+ task {
+ let targets = ctx.Request.Query.["targets"] |> parseCsvValues
+ let projects = ctx.Request.Query.["projects"] |> parseCsvValues |> function | [] -> None | values -> Some values
+ if targets.IsEmpty then
+ return Results.BadRequest("At least one target is required.")
+ else
+ let graph =
+ lock workspaceLock (fun () ->
+ buildBatchGraph workspace targets projects
+ )
+ let cache = Cache.Cache(Storages.Factory.create None, None) :> Cache.ICache
+ let statuses =
+ graph.Nodes
+ |> Map.toSeq
+ |> Seq.choose (fun (_, node) ->
+ let cacheKey = GraphDef.buildCacheKey node
+ match cache.TryGetSummary false cacheKey with
+ | Some summary -> Some (node.ProjectId, summary.IsSuccessful)
+ | _ -> None)
+ |> Seq.groupBy fst
+ |> Seq.map (fun (projectId, results) ->
+ let hasFailure = results |> Seq.exists (fun (_, ok) -> ok = false)
+ let status = if hasFailure then "failed" else "success"
+ { ProjectStatus.ProjectId = projectId; ProjectStatus.Status = status })
+ |> Seq.toList
+ let json = Json.Serialize statuses
+ return Results.Text(json, "application/json")
+ }))
+ |> ignore
+
+ app.MapPost("/api/build", Func>(fun ctx ->
+ task {
+ let! body = readBody ctx
+ let request =
+ try
+ Json.Deserialize body |> Ok
+ with ex ->
+ Error ex.Message
+ match request with
+ | Error err -> return Results.BadRequest(err)
+ | Ok request ->
+ let targets = request.Targets |> Option.defaultValue []
+ if targets.IsEmpty then
+ return Results.BadRequest("At least one target is required.")
+ else
+ match startBuildProcess workspace request logState buildState with
+ | Ok pid -> return Results.Json {| pid = pid; startedAt = DateTime.UtcNow |}
+ | Error reason -> return Results.Conflict(reason)
+ }))
+ |> ignore
+
+ app.MapGet("/api/build/log", Func(fun ctx ->
+ task {
+ ctx.Response.Headers.CacheControl <- "no-cache"
+ ctx.Response.Headers.ContentType <- "text/plain"
+ let id = Guid.NewGuid()
+ let channel = Channel.CreateUnbounded()
+ logState.Subscribers.TryAdd(id, channel) |> ignore
+
+ let snapshot = logSnapshot logState
+ if String.IsNullOrEmpty(snapshot) |> not then
+ let bytes = Encoding.UTF8.GetBytes(snapshot)
+ do! ctx.Response.Body.WriteAsync(bytes, 0, bytes.Length, ctx.RequestAborted)
+ do! ctx.Response.Body.FlushAsync(ctx.RequestAborted)
+
+ try
+ let reader = channel.Reader
+ let mutable keepReading = true
+ while keepReading && not ctx.RequestAborted.IsCancellationRequested do
+ let! next = reader.ReadAsync(ctx.RequestAborted)
+ let bytes = Encoding.UTF8.GetBytes(next)
+ do! ctx.Response.Body.WriteAsync(bytes, 0, bytes.Length, ctx.RequestAborted)
+ do! ctx.Response.Body.FlushAsync(ctx.RequestAborted)
+ with
+ | :? OperationCanceledException -> ()
+ | :? ChannelClosedException -> ()
+
+ let mutable removed = Unchecked.defaultof>
+ logState.Subscribers.TryRemove(id, &removed) |> ignore
+ }))
+ |> ignore
+
+ app.MapGet("/api/build/result/{projectHash}/{targetName}/{targetHash}", Func>(fun ctx ->
+ task {
+ let getRouteValue name =
+ match ctx.Request.RouteValues.TryGetValue(name) with
+ | true, null -> None
+ | true, (:? string as value) -> Some value
+ | _ -> None
+
+ match getRouteValue "projectHash", getRouteValue "targetName", getRouteValue "targetHash" with
+ | Some projectHash, Some targetName, Some targetHash ->
+ let cacheKey = $"{projectHash}/{targetName}/{targetHash}"
+ let cache = Cache.Cache(Storages.Factory.create None, None) :> Cache.ICache
+ match cache.TryGetSummary false cacheKey with
+ | Some summary ->
+ let json = Json.Serialize summary
+ return Results.Text(json, "application/json")
+ | None -> return Results.NotFound()
+ | _ ->
+ return Results.BadRequest("Missing route values.")
+ }))
+ |> ignore
+
+ app.MapGet("/api/build/target-log/{projectHash}/{targetName}/{targetHash}", Func>(fun ctx ->
+ task {
+ let getRouteValue name =
+ match ctx.Request.RouteValues.TryGetValue(name) with
+ | true, null -> None
+ | true, (:? string as value) -> Some value
+ | _ -> None
+
+ match getRouteValue "projectHash", getRouteValue "targetName", getRouteValue "targetHash" with
+ | Some projectHash, Some targetName, Some targetHash ->
+ let cacheKey = $"{projectHash}/{targetName}/{targetHash}"
+ let cache = Cache.Cache(Storages.Factory.create None, None) :> Cache.ICache
+ match cache.TryGetSummary false cacheKey with
+ | Some summary ->
+ let builder = StringBuilder()
+ summary.Operations
+ |> List.collect id
+ |> List.iter (fun step ->
+ if IO.exists step.Log then
+ let content = IO.readTextFile step.Log
+ if String.IsNullOrWhiteSpace(content) |> not then
+ if builder.Length > 0 then builder.AppendLine().AppendLine() |> ignore
+ builder.AppendLine(step.MetaCommand).AppendLine(content) |> ignore
+ )
+ let logText = builder.ToString()
+ return Results.Text(logText, "text/plain")
+ | None -> return Results.NotFound()
+ | _ ->
+ return Results.BadRequest("Missing route values.")
+ }))
+ |> ignore
+
+ app.MapFallback(Func(fun ctx ->
+ task {
+ let indexFile = Path.Combine(uiRoot, "index.html")
+ if File.Exists(indexFile) then
+ ctx.Response.ContentType <- "text/html"
+ let! html = File.ReadAllTextAsync(indexFile)
+ do! ctx.Response.WriteAsync(html)
+ else
+ ctx.Response.StatusCode <- 404
+ do! ctx.Response.WriteAsync("UI assets not found. Build Terrabuild.UI first.")
+ }))
+ |> ignore
+
+ let runTask = app.RunAsync()
+ if shouldOpenBrowser then
+ openBrowser url
+ runTask.Wait()
+ 0