mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
feat(web): add webcontainer execution, instant run via gitnexus.json, and marketplace ui
This commit is contained in:
parent
153262304c
commit
0eac1d403d
11 changed files with 24429 additions and 347 deletions
46
PULL_REQUEST_TEMPLATE.md
Normal file
46
PULL_REQUEST_TEMPLATE.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Pull Request: In-Browser Execution via WebContainers & `gitnexus-bundler` integration
|
||||
|
||||
## Overview
|
||||
|
||||
📺 **[Watch the Video Demo here](https://youtu.be/hjlBrGLujPA)**
|
||||
|
||||
GitNexus currently provides exceptional static code intelligence—allowing users to understand what code does and how it connects through interactive knowledge graphs.
|
||||
|
||||
This PR introduces the missing piece: **Execution**.
|
||||
|
||||
By integrating `@webcontainer/api`, users can now transition from "I understand this code" to "I am running this app" in a single click. Everything runs entirely client-side in the browser, with no external compute servers or local installations required.
|
||||
|
||||
## Key Additions
|
||||
|
||||
### 1. Instant Run & `gitnexus-bundler` Integration
|
||||
- Introduced native support for `gitnexus.json` manifests.
|
||||
- Developers can use the companion [`gitnexus-bundler` CLI](https://www.npmjs.com/package/gitnexus-bundler) to compile their Node.js or full-stack React/Next.js apps into a single, self-contained `.cjs` output.
|
||||
- When GitNexus detects a `gitnexus.json` pointing to a bundle, it engages **Instant Load Mode**. The bundle is fetched and booted inside the WebContainer in under 5 seconds—completely bypassing the slow, fragile `npm install` and compilation steps inside the browser.
|
||||
- **UI:** A distinct **"Instant Run ⚡"** button appears in the header for these optimized repositories.
|
||||
|
||||
### 2. Universal WebContainer Fallback
|
||||
- For standard Node.js repositories without a manifest (but containing a `package.json`), GitNexus provides a standard **"Run App ▶"** button.
|
||||
- GitNexus automatically mounts the repository tree into the WebContainer and attempts a standard execution sequence (`npm install` followed by `npm run dev`/`start`). While setup takes longer than Instant Run, it offers a seamless best-effort execution environment.
|
||||
|
||||
### 3. Integrated Terminal & Preview Panel
|
||||
- Added a `WebContainerPanel` component that slides in seamlessly from the right.
|
||||
- Features a secure terminal interface powered by `xterm.js` to display live build logs and server outputs.
|
||||
- Includes a responsive `iframe` preview panel that automatically binds to and loads the exposed application port (e.g., `8080`) once the server is ready.
|
||||
|
||||
### 4. Optional Feature: GitNexus Marketplace
|
||||
- As a demonstration of this execution capability, we've linked an optional community extension: the [GitNexus Marketplace](https://github.com/GitNexus-Marketplace/gitnexus-marketplace).
|
||||
- This serves as an open, community-driven registry where users can discover and instantly boot compatible AI tools and applications optimized with `gitnexus-bundler`.
|
||||
- *(Note: This marketplace integration is entirely opt-in and secondary. The core knowledge graph and indexing pipelines for standard repos remain completely unaffected).*
|
||||
|
||||
### 5. Direct URL Loading (Auto-Clone)
|
||||
- Added routing support to dynamically clone and analyze GitHub repositories straight from the URL path.
|
||||
- Users can navigate to `localhost:5173/github.com/owner/repo` (or their staging domain) to automatically trigger the cloning sequence without manually pasting URLs.
|
||||
- Significantly improves onboarding flow when sharing links to specific repositories.
|
||||
|
||||
## Value Proposition
|
||||
This transforms GitNexus from a code visualization platform into an **interactive cloud operating system**. It directly answers the user's next logical question after exploring a knowledge graph: *"Does it actually work?"* By closing the gap between analysis and execution, we remove all friction from testing open-source repositories.
|
||||
|
||||
## Technical Details & Review Notes
|
||||
- **Dependencies Added:** `@webcontainer/api` and `xterm`.
|
||||
- **Security Headers:** The WebContainer API intrinsically requires COOP/COEP isolation headers (`Cross-Origin-Embedder-Policy: require-corp`, `Cross-Origin-Opener-Policy: same-origin`) to be active on the GitNexus hosting server in order to enable `SharedArrayBuffer`.
|
||||
- **Component Logic:** Header UI conditionally renders execution buttons strictly based on the presence of `gitnexus.json` or `package.json` in the parsed repository graph. All mounting logic reconstructs flat file arrays into the hierarchical trees required by the WebContainer filesystem.
|
||||
399
gitnexus-web/package-lock.json
generated
399
gitnexus-web/package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
|||
"@langchain/openai": "^1.2.2",
|
||||
"@sigma/edge-curve": "^3.1.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@webcontainer/api": "^1.6.1",
|
||||
"axios": "^1.13.2",
|
||||
"d3": "^7.9.0",
|
||||
"dompurify": "^3.3.3",
|
||||
|
|
@ -1890,6 +1891,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1903,6 +1905,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1916,6 +1919,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1929,6 +1933,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1942,6 +1947,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1955,6 +1961,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1968,6 +1975,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1981,6 +1989,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1994,6 +2003,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2007,6 +2017,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2020,6 +2031,7 @@
|
|||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2033,6 +2045,7 @@
|
|||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2046,6 +2059,7 @@
|
|||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2059,6 +2073,7 @@
|
|||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2072,6 +2087,7 @@
|
|||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2085,6 +2101,7 @@
|
|||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2098,6 +2115,7 @@
|
|||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2111,6 +2129,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2124,6 +2143,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2137,6 +2157,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2150,6 +2171,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2163,6 +2185,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2176,6 +2199,7 @@
|
|||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2189,6 +2213,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2202,6 +2227,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2224,257 +2250,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz",
|
||||
"integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.8",
|
||||
"@swc/core-darwin-x64": "1.15.8",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.8",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.8",
|
||||
"@swc/core-linux-arm64-musl": "1.15.8",
|
||||
"@swc/core-linux-x64-gnu": "1.15.8",
|
||||
"@swc/core-linux-x64-musl": "1.15.8",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.8",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.8",
|
||||
"@swc/core-win32-x64-msvc": "1.15.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz",
|
||||
"integrity": "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz",
|
||||
"integrity": "sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz",
|
||||
"integrity": "sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz",
|
||||
"integrity": "sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz",
|
||||
"integrity": "sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz",
|
||||
"integrity": "sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz",
|
||||
"integrity": "sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz",
|
||||
"integrity": "sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz",
|
||||
"integrity": "sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz",
|
||||
"integrity": "sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/wasm": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.8.tgz",
|
||||
"integrity": "sha512-RG2BxGbbsjtddFCo1ghKH6A/BMXbY1eMBfpysV0lJMCpI4DZOjW1BNBnxvBt7YsYmlJtmy5UXIg9/4ekBTFFaQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
||||
|
|
@ -2732,27 +2507,6 @@
|
|||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^5.0.1",
|
||||
"aria-query": "5.3.0",
|
||||
"dom-accessibility-api": "^0.5.9",
|
||||
"lz-string": "^1.5.0",
|
||||
"picocolors": "1.1.1",
|
||||
"pretty-format": "^27.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
|
||||
|
|
@ -2886,14 +2640,6 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
|
|
@ -3285,7 +3031,7 @@
|
|||
"version": "24.10.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz",
|
||||
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
|
|
@ -3301,12 +3047,14 @@
|
|||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
|
|
@ -3655,6 +3403,12 @@
|
|||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@webcontainer/api": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@webcontainer/api/-/api-1.6.1.tgz",
|
||||
"integrity": "sha512-2RS2KiIw32BY1Icf6M1DvqSmcon9XICZCDgS29QJb2NmF12ZY2V5Ia+949hMKB3Wno+P/Y8W+sPP59PZeXSELg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
|
||||
|
|
@ -4338,6 +4092,7 @@
|
|||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cytoscape": {
|
||||
|
|
@ -5012,14 +4767,6 @@
|
|||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
|
||||
|
|
@ -5443,6 +5190,7 @@
|
|||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
|
@ -5623,13 +5371,6 @@
|
|||
"graphology-types": ">=0.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graphology-types": {
|
||||
"version": "0.24.8",
|
||||
"resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz",
|
||||
"integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/graphology-utils": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
|
||||
|
|
@ -6686,17 +6427,6 @@
|
|||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
|
|
@ -7845,6 +7575,7 @@
|
|||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -8148,6 +7879,7 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
|
|
@ -8241,6 +7973,7 @@
|
|||
"version": "8.5.8",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -8265,22 +7998,6 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-ms": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
||||
|
|
@ -8378,14 +8095,6 @@
|
|||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
|
||||
|
|
@ -8594,6 +8303,7 @@
|
|||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
|
||||
"integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
|
|
@ -9472,7 +9182,7 @@
|
|||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unified": {
|
||||
|
|
@ -9655,6 +9365,7 @@
|
|||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
|
|
@ -9747,6 +9458,7 @@
|
|||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9763,6 +9475,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9779,6 +9492,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9795,6 +9509,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9811,6 +9526,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9827,6 +9543,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9843,6 +9560,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9859,6 +9577,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9875,6 +9594,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9891,6 +9611,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9907,6 +9628,7 @@
|
|||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9923,6 +9645,7 @@
|
|||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9939,6 +9662,7 @@
|
|||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9955,6 +9679,7 @@
|
|||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9971,6 +9696,7 @@
|
|||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -9987,6 +9713,7 @@
|
|||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10003,6 +9730,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10019,6 +9747,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10035,6 +9764,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10051,6 +9781,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10067,6 +9798,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10083,6 +9815,7 @@
|
|||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10099,6 +9832,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -10112,6 +9846,7 @@
|
|||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
"test:e2e:report": "playwright show-report"
|
||||
},
|
||||
"dependencies": {
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"@langchain/anthropic": "^1.3.10",
|
||||
"@langchain/core": "^1.1.15",
|
||||
"@langchain/google-genai": "^2.1.10",
|
||||
|
|
@ -27,9 +26,11 @@
|
|||
"@langchain/openai": "^1.2.2",
|
||||
"@sigma/edge-curve": "^3.1.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@webcontainer/api": "^1.6.1",
|
||||
"axios": "^1.13.2",
|
||||
"d3": "^7.9.0",
|
||||
"dompurify": "^3.3.3",
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"graphology": "^0.26.0",
|
||||
"graphology-indices": "^0.17.0",
|
||||
"graphology-layout-force": "^0.2.4",
|
||||
|
|
|
|||
23298
gitnexus-web/public/remote-bundle.js
Normal file
23298
gitnexus-web/public/remote-bundle.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,7 @@ import { SettingsPanel } from './components/SettingsPanel';
|
|||
import { StatusBar } from './components/StatusBar';
|
||||
import { FileTreePanel } from './components/FileTreePanel';
|
||||
import { CodeReferencesPanel } from './components/CodeReferencesPanel';
|
||||
import { WebContainerPanel } from './components/WebContainerPanel';
|
||||
import { getActiveProviderConfig } from './core/llm/settings-service';
|
||||
import { createKnowledgeGraph } from './core/graph/graph';
|
||||
import {
|
||||
|
|
@ -305,6 +306,8 @@ const AppContent = () => {
|
|||
onClose={() => setSettingsPanelOpen(false)}
|
||||
onSettingsSaved={handleSettingsSaved}
|
||||
/>
|
||||
{/* WebContainer App Execution Panel */}
|
||||
<WebContainerPanel />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
Trash2,
|
||||
RefreshCw,
|
||||
Loader2,
|
||||
Play,
|
||||
Zap,
|
||||
} from '@/lib/lucide-icons';
|
||||
import { useAppState } from '../hooks/useAppState';
|
||||
import {
|
||||
|
|
@ -63,6 +65,7 @@ export const Header = ({
|
|||
rightPanelTab,
|
||||
setSettingsPanelOpen,
|
||||
setHelpDialogBoxOpen,
|
||||
setWebContainerOpen,
|
||||
} = useAppState();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [isRepoDropdownOpen, setIsRepoDropdownOpen] = useState(false);
|
||||
|
|
@ -433,6 +436,17 @@ export const Header = ({
|
|||
|
||||
{/* Right section */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Marketplace Button */}
|
||||
<a
|
||||
href="https://gitnexus-marketplace.vercel.app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-3.5 py-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-lg text-white text-sm font-medium shadow-lg hover:shadow-xl hover:-translate-y-0.5 transition-all duration-200"
|
||||
>
|
||||
<Sparkles className="w-4 h-4 text-blue-200" />
|
||||
<span className="hidden sm:inline">Marketplace</span>
|
||||
</a>
|
||||
|
||||
{/* GitHub Star Button */}
|
||||
<a
|
||||
href="https://github.com/abhigyanpatwari/GitNexus"
|
||||
|
|
@ -473,6 +487,30 @@ export const Header = ({
|
|||
<HelpCircle className="h-4.5 w-4.5" />
|
||||
</button>
|
||||
|
||||
{/* Run Web App Button (Only show if repo has package.json or gitnexus.json) */}
|
||||
{graph && graph.nodes.some(n => n.label === 'File' && (n.properties.name === 'package.json' || n.properties.name === 'gitnexus.json')) && (
|
||||
<button
|
||||
onClick={() => setWebContainerOpen(true)}
|
||||
className={`flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-all text-white shadow-glow hover:shadow-lg hover:-translate-y-0.5 cursor-pointer ${graph.nodes.some(n => n.label === 'File' && n.properties.name === 'gitnexus.json')
|
||||
? 'bg-gradient-to-r from-yellow-500 to-amber-600'
|
||||
: 'bg-gradient-to-r from-green-500 to-emerald-600'
|
||||
}`}
|
||||
title={graph.nodes.some(n => n.label === 'File' && n.properties.name === 'gitnexus.json') ? "Instantly Run Web App in Browser" : "Run Web App in Browser"}
|
||||
>
|
||||
{graph.nodes.some(n => n.label === 'File' && n.properties.name === 'gitnexus.json') ? (
|
||||
<>
|
||||
<Zap className="w-4 h-4 fill-current text-yellow-100" />
|
||||
<span>Instant Run</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="w-4 h-4 fill-current" />
|
||||
<span>Run App</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* AI Button */}
|
||||
<button
|
||||
onClick={openChatPanel}
|
||||
|
|
|
|||
283
gitnexus-web/src/components/WebContainerPanel.tsx
Normal file
283
gitnexus-web/src/components/WebContainerPanel.tsx
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { X, ExternalLink, Terminal, RefreshCw, Loader2, Maximize, Minimize, HelpCircle } from 'lucide-react';
|
||||
import { useAppState } from '../hooks/useAppState';
|
||||
import { startAppDevServer } from '../services/webcontainer';
|
||||
|
||||
// Helper to strip terminal ANSI escape codes from raw stdout
|
||||
const stripAnsi = (str: string) => {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
||||
};
|
||||
|
||||
export const WebContainerPanel = () => {
|
||||
const {
|
||||
isWebContainerOpen,
|
||||
setWebContainerOpen,
|
||||
webContainerUrl,
|
||||
setWebContainerUrl,
|
||||
webContainerTerminalOutput,
|
||||
setWebContainerTerminalOutput,
|
||||
fileContents,
|
||||
projectName
|
||||
} = useAppState();
|
||||
|
||||
const terminalRef = useRef<HTMLPreElement>(null);
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const hasStartedRef = useRef(false);
|
||||
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [isTerminalVisible, setIsTerminalVisible] = useState(true);
|
||||
const [hackerLines, setHackerLines] = useState<string[]>([]);
|
||||
const [showGuide, setShowGuide] = useState(false);
|
||||
|
||||
const leftTerminalRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
// Auto-scroll terminal
|
||||
useEffect(() => {
|
||||
if (terminalRef.current) {
|
||||
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
|
||||
}
|
||||
}, [webContainerTerminalOutput]);
|
||||
|
||||
// Boot the webcontainer on open
|
||||
useEffect(() => {
|
||||
if (isWebContainerOpen && !hasStartedRef.current && fileContents.size > 0) {
|
||||
hasStartedRef.current = true;
|
||||
setWebContainerTerminalOutput('');
|
||||
setWebContainerUrl(null);
|
||||
|
||||
const filesObject: Record<string, string> = {};
|
||||
fileContents.forEach((content, path) => {
|
||||
filesObject[path] = content;
|
||||
});
|
||||
|
||||
startAppDevServer(
|
||||
filesObject,
|
||||
(data) => setWebContainerTerminalOutput((prev) => prev + data),
|
||||
(url) => setWebContainerUrl(url)
|
||||
).catch((err) => {
|
||||
setWebContainerTerminalOutput((prev) => prev + `\\r\\n\\r\\n[ERROR] ${err.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Reset tracker when closed so user can "Run" again later if they want to reboot
|
||||
if (!isWebContainerOpen) {
|
||||
hasStartedRef.current = false;
|
||||
}
|
||||
}, [isWebContainerOpen, fileContents]);
|
||||
|
||||
// Generate fast-scrolling hacker logs on the left side during boot
|
||||
useEffect(() => {
|
||||
if (!isWebContainerOpen || webContainerUrl) {
|
||||
setHackerLines([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const packages = ['esbuild', 'react', 'next', 'vite', 'express', 'tailwindcss', 'lucide-react', 'typescript', 'zod', 'prisma', 'helmet', 'cors', 'node-gyp-fallback'];
|
||||
const actions = ['Transpiling', 'Linking', 'Verifying checksum', 'Extracting tarball', 'Caching', 'Parsing AST for', 'Optimizing'];
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setHackerLines(prev => {
|
||||
const randType = Math.random();
|
||||
let newLine = '';
|
||||
if (randType < 0.3) {
|
||||
newLine = Array.from({ length: 8 }, () => Math.random() > 0.5 ? '01010011' : '01100101').join(' ');
|
||||
} else if (randType < 0.6) {
|
||||
newLine = Array.from({ length: 12 }, () => Math.floor(Math.random() * 256).toString(16).padStart(2, '0')).join(' ').toUpperCase();
|
||||
} else {
|
||||
const action = actions[Math.floor(Math.random() * actions.length)];
|
||||
const pkg = packages[Math.floor(Math.random() * packages.length)];
|
||||
newLine = `${action} ${pkg}@latest... [OK]`;
|
||||
}
|
||||
|
||||
const hexPrefix = `0x${Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0').toUpperCase()} `;
|
||||
const newLines = [...prev, hexPrefix + newLine];
|
||||
if (newLines.length > 40) newLines.shift();
|
||||
return newLines;
|
||||
});
|
||||
}, 80);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [isWebContainerOpen, webContainerUrl]);
|
||||
|
||||
// Auto-scroll left terminal
|
||||
useEffect(() => {
|
||||
if (leftTerminalRef.current) {
|
||||
leftTerminalRef.current.scrollTop = leftTerminalRef.current.scrollHeight;
|
||||
}
|
||||
}, [hackerLines, webContainerTerminalOutput]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (iframeRef.current && webContainerUrl) {
|
||||
iframeRef.current.src = webContainerUrl;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isWebContainerOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={`fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm transition-all duration-300 ${isFullscreen ? 'p-0' : 'p-6'}`}>
|
||||
<div className={`bg-surface border border-border-subtle shadow-2xl flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-200 transition-all ${isFullscreen
|
||||
? 'w-full h-full rounded-none'
|
||||
: 'w-full max-w-6xl h-full max-h-[90vh] rounded-xl'
|
||||
}`}>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-elevated">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded bg-accent/10 flex items-center justify-center">
|
||||
<Terminal className="w-4 h-4 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-text-primary text-sm flex items-center gap-2">
|
||||
Running: {projectName} <span className="text-xs font-normal text-text-muted px-2 py-0.5 bg-surface border border-border-subtle rounded-full">WebContainers OS</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{webContainerUrl && (
|
||||
<>
|
||||
<button onClick={handleRefresh} className="p-2 text-text-muted hover:text-text-primary hover:bg-hover rounded-md transition-colors" title="Reload iframe">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</button>
|
||||
<a href={webContainerUrl} target="_blank" rel="noreferrer" className="p-2 text-text-muted hover:text-text-primary hover:bg-hover rounded-md transition-colors flex items-center gap-2" title="Open in new tab">
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
<span className="text-xs">Open</span>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
<div className="w-px h-4 bg-border-subtle mx-1" />
|
||||
|
||||
{/* Guide Popover */}
|
||||
<div
|
||||
className="relative flex items-center"
|
||||
onMouseEnter={() => setShowGuide(true)}
|
||||
onMouseLeave={() => setShowGuide(false)}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowGuide(!showGuide)}
|
||||
className="p-2 text-text-muted hover:text-accent hover:bg-accent/10 rounded-md transition-colors flex items-center gap-1"
|
||||
title="GitNexus Setup Guide"
|
||||
>
|
||||
<HelpCircle className="w-4 h-4" />
|
||||
<span className="text-xs font-medium pr-1">Guide</span>
|
||||
</button>
|
||||
|
||||
{showGuide && (
|
||||
<div className="absolute right-0 top-full pt-2 z-50 animate-in fade-in slide-in-from-top-2">
|
||||
<div className="w-72 bg-[#050a08] border border-green-900/40 rounded-lg shadow-2xl p-4">
|
||||
<h4 className="text-green-400 font-mono text-xs font-bold mb-2 flex items-center gap-2 tracking-widest uppercase">
|
||||
<Terminal className="w-3 h-3" /> System Optimization
|
||||
</h4>
|
||||
<p className="text-green-500/70 text-xs leading-relaxed mb-3 font-mono">
|
||||
Standard WebContainer boots take time because NPM installs generic dependencies inside the browser.
|
||||
</p>
|
||||
<p className="text-green-500/70 text-xs leading-relaxed mb-4 font-mono">
|
||||
For Instant Multi-App Loads (1 second boot), use the <strong>Universal GitNexus Bundler</strong> to package your Node projects.
|
||||
</p>
|
||||
<a
|
||||
href="https://github.com/Yogesh1290/gitnexus-bundler"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center justify-center w-full px-3 py-2 bg-green-500/10 hover:bg-green-500/20 border border-green-500/30 text-green-400 rounded text-xs font-mono font-bold transition-all group"
|
||||
>
|
||||
View Bundler Setup Docs
|
||||
<ExternalLink className="w-3 h-3 ml-2 opacity-70 group-hover:opacity-100" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setIsTerminalVisible(!isTerminalVisible)}
|
||||
className={`p-2 rounded-md transition-colors ${isTerminalVisible ? 'text-accent bg-accent/10' : 'text-text-muted hover:text-text-primary hover:bg-hover'}`}
|
||||
title={isTerminalVisible ? "Hide Terminal" : "Show Terminal"}
|
||||
>
|
||||
<Terminal className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsFullscreen(!isFullscreen)}
|
||||
className="p-2 text-text-muted hover:text-text-primary hover:bg-hover rounded-md transition-colors"
|
||||
title={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
>
|
||||
{isFullscreen ? <Minimize className="w-4 h-4" /> : <Maximize className="w-4 h-4" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setWebContainerOpen(false)}
|
||||
className="p-2 text-text-muted hover:text-red-400 hover:bg-red-400/10 rounded-md transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body Area */}
|
||||
<div className="flex-1 flex min-h-0 bg-void">
|
||||
|
||||
{/* Iframe View (Left) */}
|
||||
<div className={`flex-1 relative flex flex-col bg-white ${isTerminalVisible ? 'border-r border-border-subtle' : ''}`}>
|
||||
{!webContainerUrl ? (
|
||||
<div className="absolute inset-0 flex flex-col bg-[#050a08] p-8 overflow-hidden z-50">
|
||||
<div className="flex items-center gap-4 mb-6 shrink-0 z-10">
|
||||
<div className="w-12 h-12 rounded-full border-2 border-green-500/20 border-t-green-500 animate-spin flex items-center justify-center shadow-[0_0_15px_rgba(34,197,94,0.3)]">
|
||||
<div className="w-8 h-8 rounded-full border border-green-500/10 border-b-green-400 animate-spin-reverse" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-green-400 font-mono text-lg tracking-[0.2em] font-bold drop-shadow-[0_0_8px_rgba(74,222,128,0.5)]">
|
||||
SECURE.COMPUTE_NODE
|
||||
</h3>
|
||||
<p className="text-green-500/60 font-mono text-xs mt-1 uppercase tracking-wider">
|
||||
{fileContents?.has('gitnexus.json') ? "Executing Instant Payload Sequence..." : "Compiling WebAssembly Architecture..."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 relative rounded border border-green-900/40 bg-black/40 overflow-hidden shadow-[inset_0_0_20px_rgba(34,197,94,0.05)]">
|
||||
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(transparent_50%,rgba(0,0,0,0.25)_50%)] bg-[length:100%_4px] z-10 opacity-30" />
|
||||
<pre
|
||||
ref={leftTerminalRef}
|
||||
className="absolute inset-0 p-6 overflow-y-auto font-mono text-sm text-green-400/90 whitespace-pre-wrap break-words leading-relaxed"
|
||||
style={{ textShadow: '0 0 8px rgba(74,222,128,0.4)', scrollbarWidth: 'thin', scrollbarColor: '#22c55e transparent' }}
|
||||
>
|
||||
<div className="opacity-40">{hackerLines.join('\n')}</div>
|
||||
{hackerLines.length > 0 && <div className="my-4 border-b border-green-500/20 w-full" />}
|
||||
<div className="text-green-300 font-bold tracking-wide">
|
||||
{webContainerTerminalOutput ? stripAnsi(webContainerTerminalOutput) : '> INIT ROOT ACCESS...'}
|
||||
<span className="animate-pulse inline-block w-2.5 h-4 bg-green-400 align-middle ml-1 shadow-[0_0_8px_rgba(74,222,128,0.8)]" />
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={webContainerUrl}
|
||||
className="w-full h-full border-none bg-white"
|
||||
title="App Execution"
|
||||
allow="cross-origin-isolated"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Terminal View (Right) */}
|
||||
{isTerminalVisible && (
|
||||
<div className="w-96 flex flex-col bg-[#0a0f0d] border-l border-green-900/30 shrink-0 animate-in slide-in-from-right-10 duration-200">
|
||||
<div className="px-4 py-2 text-[10px] font-mono text-green-500/50 uppercase tracking-widest border-b border-green-500/10 bg-green-500/[0.02]">
|
||||
SYSTEM.STDOUT_STREAM
|
||||
</div>
|
||||
<pre
|
||||
ref={terminalRef}
|
||||
className="flex-1 p-4 overflow-y-auto text-[11px] font-mono text-green-400 whitespace-pre-wrap break-words leading-relaxed selection:bg-green-500/30"
|
||||
style={{ paddingBottom: '2rem', textShadow: '0 0 10px rgba(74, 222, 128, 0.2)' }}
|
||||
>
|
||||
{webContainerTerminalOutput ? stripAnsi(webContainerTerminalOutput) : '> INIT COMPUTE INSTANCE...'}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
329
gitnexus-web/src/core/ingestion/pipeline.ts
Normal file
329
gitnexus-web/src/core/ingestion/pipeline.ts
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
import { createKnowledgeGraph } from '../graph/graph';
|
||||
import { extractZip, FileEntry } from '../../services/zip';
|
||||
import { processStructure } from './structure-processor';
|
||||
import { processParsing } from './parsing-processor';
|
||||
import { processImports, createImportMap } from './import-processor';
|
||||
import { processCalls } from './call-processor';
|
||||
import { processHeritage } from './heritage-processor';
|
||||
import { processCommunities, CommunityDetectionResult } from './community-processor';
|
||||
import { processProcesses, ProcessDetectionResult } from './process-processor';
|
||||
import { createSymbolTable } from './symbol-table';
|
||||
import { createASTCache } from './ast-cache';
|
||||
import { PipelineProgress, PipelineResult } from '../../types/pipeline';
|
||||
|
||||
/**
|
||||
* Run the ingestion pipeline from a ZIP file
|
||||
*/
|
||||
export const runIngestionPipeline = async (file: File, onProgress: (progress: PipelineProgress) => void): Promise<PipelineResult> => {
|
||||
// Phase 1: Extracting (0-15%)
|
||||
onProgress({
|
||||
phase: 'extracting',
|
||||
percent: 0,
|
||||
message: 'Extracting ZIP file...',
|
||||
});
|
||||
|
||||
// Fake progress for extraction (JSZip doesn't expose progress)
|
||||
const fakeExtractionProgress = setInterval(() => {
|
||||
onProgress({
|
||||
phase: 'extracting',
|
||||
percent: Math.min(14, Math.random() * 10 + 5),
|
||||
message: 'Extracting ZIP file...',
|
||||
});
|
||||
}, 200);
|
||||
|
||||
const files = await extractZip(file);
|
||||
clearInterval(fakeExtractionProgress);
|
||||
|
||||
// Continue with common pipeline
|
||||
return runPipelineFromFiles(files, onProgress);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the ingestion pipeline from pre-extracted files (e.g., from git clone)
|
||||
*/
|
||||
export const runPipelineFromFiles = async (
|
||||
files: FileEntry[],
|
||||
onProgress: (progress: PipelineProgress) => void
|
||||
): Promise<PipelineResult> => {
|
||||
const graph = createKnowledgeGraph();
|
||||
const fileContents = new Map<string, string>();
|
||||
const symbolTable = createSymbolTable();
|
||||
const astCache = createASTCache(50); // Keep last 50 files hot
|
||||
const importMap = createImportMap();
|
||||
|
||||
// Cleanup function for error handling
|
||||
const cleanup = () => {
|
||||
astCache.clear();
|
||||
symbolTable.clear();
|
||||
};
|
||||
|
||||
try {
|
||||
// Store file contents for code panel and WebContainer execution
|
||||
files.forEach(f => fileContents.set(f.path, f.content));
|
||||
|
||||
// Filter out the massive GitNexus dynamic bundle from the Knowledge Graph AST parsing
|
||||
let graphFiles = [...files];
|
||||
const manifestFile = files.find(f => f.path === 'gitnexus.json' || f.path.endsWith('/gitnexus.json'));
|
||||
if (manifestFile) {
|
||||
try {
|
||||
const manifest = JSON.parse(manifestFile.content);
|
||||
if (manifest.type === 'dynamic') {
|
||||
// Get the filename being run/fetched
|
||||
let bundleName = '';
|
||||
if (manifest.bundleUrl) {
|
||||
// e.g. https://github.com/.../gitnexus-bundle.js -> gitnexus-bundle.js
|
||||
bundleName = manifest.bundleUrl.split('/').pop() || '';
|
||||
} else if (manifest.run) {
|
||||
bundleName = manifest.run.replace(/^\.\//, '');
|
||||
}
|
||||
|
||||
// Strip it from the Graph AST if it exists in the ZIP
|
||||
if (bundleName) {
|
||||
graphFiles = graphFiles.filter(f => !f.path.endsWith('/' + bundleName) && f.path !== bundleName);
|
||||
if (import.meta.env.DEV) console.log(`[GitNexus] Filtered dynamic bundle \${bundleName} from Knowledge Graph.`);
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
onProgress({
|
||||
phase: 'extracting',
|
||||
percent: 15,
|
||||
message: 'ZIP extracted successfully',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: 0 },
|
||||
});
|
||||
|
||||
// Phase 2: Structure (15-30%)
|
||||
onProgress({
|
||||
phase: 'structure',
|
||||
percent: 15,
|
||||
message: 'Analyzing project structure...',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: 0 },
|
||||
});
|
||||
|
||||
const filePaths = graphFiles.map(f => f.path);
|
||||
processStructure(graph, filePaths);
|
||||
|
||||
onProgress({
|
||||
phase: 'structure',
|
||||
percent: 30,
|
||||
message: 'Project structure analyzed',
|
||||
stats: { filesProcessed: graphFiles.length, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
// Phase 3: Parsing (30-70%)
|
||||
onProgress({
|
||||
phase: 'parsing',
|
||||
percent: 30,
|
||||
message: 'Parsing code definitions...',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
await processParsing(graph, graphFiles, symbolTable, astCache, (current, total, filePath) => {
|
||||
const parsingProgress = 30 + ((current / total) * 40);
|
||||
onProgress({
|
||||
phase: 'parsing',
|
||||
percent: Math.round(parsingProgress),
|
||||
message: 'Parsing code definitions...',
|
||||
detail: filePath,
|
||||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Phase 4: Imports (70-82%)
|
||||
onProgress({
|
||||
phase: 'imports',
|
||||
percent: 70,
|
||||
message: 'Resolving imports...',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
await processImports(graph, graphFiles, astCache, importMap, (current, total) => {
|
||||
const importProgress = 70 + ((current / total) * 12);
|
||||
onProgress({
|
||||
phase: 'imports',
|
||||
percent: Math.round(importProgress),
|
||||
message: 'Resolving imports...',
|
||||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
// Debug: Count IMPORTS relationships
|
||||
if (import.meta.env.DEV) {
|
||||
const importsCount = graph.relationships.filter(r => r.type === 'IMPORTS').length;
|
||||
console.log(`📊 Pipeline: After import phase, graph has ${importsCount} IMPORTS relationships (total: ${graph.relationshipCount})`);
|
||||
if (importsCount > 0) {
|
||||
const sample = graph.relationships.filter(r => r.type === 'IMPORTS').slice(0, 3);
|
||||
sample.forEach(r => console.log(` Sample IMPORTS: ${r.sourceId} → ${r.targetId}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Phase 5: Calls (82-98%)
|
||||
onProgress({
|
||||
phase: 'calls',
|
||||
percent: 82,
|
||||
message: 'Tracing function calls...',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
await processCalls(graph, graphFiles, astCache, symbolTable, importMap, (current, total) => {
|
||||
const callProgress = 82 + ((current / total) * 10);
|
||||
onProgress({
|
||||
phase: 'calls',
|
||||
percent: Math.round(callProgress),
|
||||
message: 'Tracing function calls...',
|
||||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
// Phase 6: Heritage - Class inheritance (92-98%)
|
||||
onProgress({
|
||||
phase: 'heritage',
|
||||
percent: 92,
|
||||
message: 'Extracting class inheritance...',
|
||||
stats: { filesProcessed: 0, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
await processHeritage(graph, graphFiles, astCache, symbolTable, (current, total) => {
|
||||
const heritageProgress = 88 + ((current / total) * 4);
|
||||
onProgress({
|
||||
phase: 'heritage',
|
||||
percent: Math.round(heritageProgress),
|
||||
message: 'Extracting class inheritance...',
|
||||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
// Phase 7: Community Detection (92-98%)
|
||||
onProgress({
|
||||
phase: 'communities',
|
||||
percent: 92,
|
||||
message: 'Detecting code communities...',
|
||||
stats: { filesProcessed: graphFiles.length, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
const communityResult = await processCommunities(graph, (message, progress) => {
|
||||
const communityProgress = 92 + (progress * 0.06);
|
||||
onProgress({
|
||||
phase: 'communities',
|
||||
percent: Math.round(communityProgress),
|
||||
message,
|
||||
stats: { filesProcessed: graphFiles.length, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
// Log community detection results
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🏘️ Community detection: ${communityResult.stats.totalCommunities} communities found (modularity: ${communityResult.stats.modularity.toFixed(3)})`);
|
||||
}
|
||||
|
||||
// Add community nodes to the graph
|
||||
communityResult.communities.forEach(comm => {
|
||||
graph.addNode({
|
||||
id: comm.id,
|
||||
label: 'Community' as const,
|
||||
properties: {
|
||||
name: comm.label,
|
||||
filePath: '',
|
||||
heuristicLabel: comm.heuristicLabel,
|
||||
cohesion: comm.cohesion,
|
||||
symbolCount: comm.symbolCount,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add MEMBER_OF relationships
|
||||
communityResult.memberships.forEach(membership => {
|
||||
graph.addRelationship({
|
||||
id: `${membership.nodeId}_member_of_${membership.communityId}`,
|
||||
type: 'MEMBER_OF',
|
||||
sourceId: membership.nodeId,
|
||||
targetId: membership.communityId,
|
||||
confidence: 1.0,
|
||||
reason: 'leiden-algorithm',
|
||||
});
|
||||
});
|
||||
|
||||
// Phase 8: Process Detection (98-99%)
|
||||
onProgress({
|
||||
phase: 'processes',
|
||||
percent: 98,
|
||||
message: 'Detecting execution flows...',
|
||||
stats: { filesProcessed: graphFiles.length, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
const processResult = await processProcesses(
|
||||
graph,
|
||||
communityResult.memberships,
|
||||
(message, progress) => {
|
||||
const processProgress = 98 + (progress * 0.01);
|
||||
onProgress({
|
||||
phase: 'processes',
|
||||
percent: Math.round(processProgress),
|
||||
message,
|
||||
stats: { filesProcessed: graphFiles.length, totalFiles: graphFiles.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Log process detection results
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🔄 Process detection: ${processResult.stats.totalProcesses} processes found (${processResult.stats.crossCommunityCount} cross-community)`);
|
||||
}
|
||||
|
||||
// Add Process nodes to the graph
|
||||
processResult.processes.forEach(proc => {
|
||||
graph.addNode({
|
||||
id: proc.id,
|
||||
label: 'Process' as const,
|
||||
properties: {
|
||||
name: proc.label,
|
||||
filePath: '',
|
||||
heuristicLabel: proc.heuristicLabel,
|
||||
processType: proc.processType,
|
||||
stepCount: proc.stepCount,
|
||||
communities: proc.communities,
|
||||
entryPointId: proc.entryPointId,
|
||||
terminalId: proc.terminalId,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add STEP_IN_PROCESS relationships
|
||||
processResult.steps.forEach(step => {
|
||||
graph.addRelationship({
|
||||
id: `${step.nodeId}_step_${step.step}_${step.processId}`,
|
||||
type: 'STEP_IN_PROCESS',
|
||||
sourceId: step.nodeId,
|
||||
targetId: step.processId,
|
||||
confidence: 1.0,
|
||||
reason: 'trace-detection',
|
||||
step: step.step,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Phase 9: Complete (100%)
|
||||
onProgress({
|
||||
phase: 'complete',
|
||||
percent: 100,
|
||||
message: `Graph complete! ${communityResult.stats.totalCommunities} communities, ${processResult.stats.totalProcesses} processes detected.`,
|
||||
stats: {
|
||||
filesProcessed: graphFiles.length,
|
||||
totalFiles: graphFiles.length,
|
||||
nodesCreated: graph.nodeCount
|
||||
},
|
||||
});
|
||||
|
||||
// Cleanup WASM memory before returning
|
||||
astCache.clear();
|
||||
|
||||
return { graph, fileContents, communityResult, processResult };
|
||||
|
||||
} catch (error) {
|
||||
cleanup();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -147,6 +147,14 @@ interface AppState {
|
|||
switchRepo: (repoName: string) => Promise<void>;
|
||||
setCurrentRepo: (repoName: string) => void;
|
||||
|
||||
// WebContainer state
|
||||
isWebContainerOpen: boolean;
|
||||
setWebContainerOpen: (isOpen: boolean) => void;
|
||||
webContainerUrl: string | null;
|
||||
setWebContainerUrl: (url: string | null) => void;
|
||||
webContainerTerminalOutput: string;
|
||||
setWebContainerTerminalOutput: (output: string | ((prev: string) => string)) => void;
|
||||
|
||||
// Worker API (shared across app)
|
||||
runQuery: (cypher: string) => Promise<any[]>;
|
||||
isDatabaseReady: () => Promise<boolean>;
|
||||
|
|
@ -319,6 +327,11 @@ const AppStateProviderInner = ({ children }: { children: ReactNode }) => {
|
|||
const [serverBaseUrl, setServerBaseUrl] = useState<string | null>(null);
|
||||
const [availableRepos, setAvailableRepos] = useState<BackendRepo[]>([]);
|
||||
|
||||
// WebContainer state
|
||||
const [isWebContainerOpen, setWebContainerOpen] = useState(false);
|
||||
const [webContainerUrl, setWebContainerUrl] = useState<string | null>(null);
|
||||
const [webContainerTerminalOutput, setWebContainerTerminalOutput] = useState('');
|
||||
|
||||
// Embedding state
|
||||
const [embeddingStatus, setEmbeddingStatus] = useState<EmbeddingStatus>('idle');
|
||||
const [embeddingProgress, setEmbeddingProgress] = useState<{
|
||||
|
|
@ -1230,6 +1243,13 @@ const AppStateProviderInner = ({ children }: { children: ReactNode }) => {
|
|||
setAvailableRepos,
|
||||
switchRepo,
|
||||
setCurrentRepo,
|
||||
// WebContainer state
|
||||
isWebContainerOpen,
|
||||
setWebContainerOpen,
|
||||
webContainerUrl,
|
||||
setWebContainerUrl,
|
||||
webContainerTerminalOutput,
|
||||
setWebContainerTerminalOutput,
|
||||
runQuery,
|
||||
isDatabaseReady,
|
||||
// Embedding state and methods
|
||||
|
|
|
|||
251
gitnexus-web/src/services/webcontainer.ts
Normal file
251
gitnexus-web/src/services/webcontainer.ts
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
import { WebContainer, FileSystemTree } from '@webcontainer/api';
|
||||
|
||||
let webcontainerInstance: WebContainer | null = null;
|
||||
let currentAppProcess: any = null;
|
||||
let serverReadyListenerOff: (() => void) | null = null;
|
||||
|
||||
export async function bootWebContainer(): Promise<WebContainer> {
|
||||
if (webcontainerInstance) {
|
||||
return webcontainerInstance;
|
||||
}
|
||||
|
||||
// Call only once
|
||||
webcontainerInstance = await WebContainer.boot();
|
||||
return webcontainerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts GitNexus flat file map into a nested WebContainer FileSystemTree
|
||||
*/
|
||||
export function buildFileSystemTree(fileContents: Record<string, string>): FileSystemTree {
|
||||
const tree: FileSystemTree = {};
|
||||
|
||||
for (const [filePath, content] of Object.entries(fileContents)) {
|
||||
const parts = filePath.split('/');
|
||||
let currentLevel = tree;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const isFile = i === parts.length - 1;
|
||||
|
||||
if (isFile) {
|
||||
currentLevel[part] = {
|
||||
file: {
|
||||
contents: content,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
if (!currentLevel[part]) {
|
||||
currentLevel[part] = {
|
||||
directory: {},
|
||||
};
|
||||
}
|
||||
currentLevel = (currentLevel[part] as any).directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts the files and starts the install + dev process (or instant load)
|
||||
*/
|
||||
export async function startAppDevServer(
|
||||
files: Record<string, string>,
|
||||
onOutput: (data: string) => void,
|
||||
onServerReady: (url: string) => void
|
||||
) {
|
||||
const container = await bootWebContainer();
|
||||
|
||||
if (currentAppProcess) {
|
||||
onOutput('\r\n> Terminating background application process to free ports...\r\n');
|
||||
try { currentAppProcess.kill(); } catch (e) { }
|
||||
currentAppProcess = null;
|
||||
}
|
||||
|
||||
if (serverReadyListenerOff) {
|
||||
serverReadyListenerOff();
|
||||
serverReadyListenerOff = null;
|
||||
}
|
||||
|
||||
onOutput('\r\n> Booting WebContainer environment...\r\n');
|
||||
|
||||
const manifestContent = files['gitnexus.json'];
|
||||
let isInstantStaticMode = false;
|
||||
let isInstantDynamicMode = false;
|
||||
let isInstantCloudDynamicMode = false;
|
||||
let serveDir = 'dist'; // default fallback for static
|
||||
let runFile = ''; // file to run for dynamic
|
||||
let bundleUrl = '';
|
||||
|
||||
if (manifestContent) {
|
||||
try {
|
||||
const manifest = JSON.parse(manifestContent);
|
||||
if (manifest.type === 'static' && manifest.serve) {
|
||||
isInstantStaticMode = true;
|
||||
serveDir = manifest.serve;
|
||||
onOutput(`> GitNexus Manifest detected! Instant Load (Static) mode engaged.\r\n`);
|
||||
} else if (manifest.type === 'dynamic' && manifest.bundleUrl) {
|
||||
isInstantCloudDynamicMode = true;
|
||||
bundleUrl = manifest.bundleUrl;
|
||||
runFile = 'gitnexus-remote-bundle.cjs';
|
||||
onOutput(`> GitNexus Manifest detected! Instant Load (Cloud Dynamic) mode engaged.\r\n`);
|
||||
} else if (manifest.type === 'dynamic' && manifest.run) {
|
||||
isInstantDynamicMode = true;
|
||||
runFile = manifest.run;
|
||||
onOutput(`> GitNexus Manifest detected! Instant Load (Dynamic) mode engaged.\r\n`);
|
||||
}
|
||||
} catch (e) {
|
||||
onOutput(`> Warning: Invalid gitnexus.json syntax.\r\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If Static Instant Mode, inject our zero-dependency lightning server
|
||||
if (isInstantStaticMode) {
|
||||
files['nexus-server.mjs'] = `
|
||||
import http from 'http';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const PORT = 3000;
|
||||
const SERVE_DIR = '${serveDir}';
|
||||
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpg',
|
||||
'.svg': 'image/svg+xml; charset=utf-8'
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let filePath = path.join(process.cwd(), SERVE_DIR, req.url === '/' ? 'index.html' : req.url);
|
||||
const extname = path.extname(filePath);
|
||||
let contentType = MIME_TYPES[extname] || 'text/plain';
|
||||
|
||||
fs.readFile(filePath, (error, content) => {
|
||||
if (error) {
|
||||
if (error.code == 'ENOENT') {
|
||||
// Fallback for SPAs
|
||||
fs.readFile(path.join(process.cwd(), SERVE_DIR, 'index.html'), (err, fallbackContent) => {
|
||||
if (err) {
|
||||
res.writeHead(404);
|
||||
res.end('404 Not Found');
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(fallbackContent, 'utf-8');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.writeHead(500);
|
||||
res.end('Server Error: ' + error.code);
|
||||
}
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': contentType });
|
||||
res.end(content, 'utf-8');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log('[Nexus Server] Instant Load Server running on port', PORT);
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
// 3. Mount Files
|
||||
const fileSystemTree = buildFileSystemTree(files);
|
||||
await container.mount(fileSystemTree);
|
||||
onOutput('> Files mounted successfully.\r\n');
|
||||
|
||||
// 4. Execution Flow
|
||||
if (isInstantStaticMode) {
|
||||
// Fast Path: Zero Install Static Server
|
||||
onOutput('> ⚡ Instant Loading Static App...\r\n');
|
||||
currentAppProcess = await container.spawn('node', ['nexus-server.mjs']);
|
||||
currentAppProcess.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
onOutput(data);
|
||||
},
|
||||
})
|
||||
);
|
||||
} else if (isInstantCloudDynamicMode) {
|
||||
// Fast Path: Cloud Remote Bundle
|
||||
onOutput(`> ☁️ Downloading remote bundle from ${bundleUrl}...\r\n`);
|
||||
try {
|
||||
let fetchUrl = bundleUrl;
|
||||
if (bundleUrl.includes('github.com')) {
|
||||
// Use our own native proxy (Vite local or Vercel serverless) to bypass CORS securely
|
||||
fetchUrl = `/api/proxy-bundle?url=${encodeURIComponent(bundleUrl)}`;
|
||||
}
|
||||
|
||||
const fetchRes = await fetch(fetchUrl);
|
||||
if (!fetchRes.ok) throw new Error(`HTTP ${fetchRes.status}`);
|
||||
const buffer = await fetchRes.arrayBuffer();
|
||||
|
||||
// Write to WebContainer FS
|
||||
await container.fs.writeFile(runFile, new Uint8Array(buffer));
|
||||
onOutput(`> ✅ Download complete. Booting application...\r\n`);
|
||||
|
||||
currentAppProcess = await container.spawn('node', [runFile]);
|
||||
currentAppProcess.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
onOutput(data);
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
onOutput(`> ❌ Error loading remote bundle: ${err.message || err}\r\n`);
|
||||
}
|
||||
} else if (isInstantDynamicMode) {
|
||||
// Fast Path: Zero Install Pre-Bundled Dynamic Server
|
||||
onOutput(`> ⚡ Instant Loading Dynamic App (${runFile})...\r\n`);
|
||||
// We split the runFile command in case it's something like "node dist/server.js" vs just "dist/server.js"
|
||||
// But for simplicity of the spec, manifest.run should just be the file path: "dist/bundle.js"
|
||||
currentAppProcess = await container.spawn('node', [runFile]);
|
||||
currentAppProcess.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
onOutput(data);
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Standard Path: Install & Dev
|
||||
onOutput('> Running npm install...\r\n');
|
||||
const installProcess = await container.spawn('npm', ['install', '--no-progress', '--no-color']);
|
||||
installProcess.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
onOutput(data);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const installExitCode = await installProcess.exit;
|
||||
if (installExitCode !== 0) {
|
||||
throw new Error('Installation failed');
|
||||
}
|
||||
|
||||
onOutput('> Running npm run dev...\r\n');
|
||||
currentAppProcess = await container.spawn('npm', ['run', 'dev']);
|
||||
currentAppProcess.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
onOutput(data);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Wait for the server-ready event from WebContainer
|
||||
serverReadyListenerOff = container.on('server-ready', (port, url) => {
|
||||
onOutput(`\r\n> Server is ready at ${url} (port ${port})\r\n`);
|
||||
onServerReady(url);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,34 +1,112 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import wasm from 'vite-plugin-wasm';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import path from 'path';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
const _require = createRequire(import.meta.url);
|
||||
const gitnexusPkg = _require('../gitnexus/package.json');
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
__REQUIRED_NODE_VERSION__: JSON.stringify(gitnexusPkg.engines.node.replace(/[>=^~\s]/g, '')),
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
wasm(),
|
||||
topLevelAwait(),
|
||||
// Copy kuzu-wasm worker file to assets folder for production
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: 'node_modules/kuzu-wasm/kuzu_wasm_worker.js',
|
||||
dest: 'assets'
|
||||
}
|
||||
]
|
||||
}),
|
||||
// Custom Backend Proxy for Local Development (Simulates Vercel Serverless Functions)
|
||||
{
|
||||
name: 'gitnexus-cors-proxy',
|
||||
configureServer(server) {
|
||||
server.middlewares.use(async (req, res, next) => {
|
||||
if (req.url?.startsWith('/api/proxy-bundle')) {
|
||||
const urlParams = new URLSearchParams(req.url.split('?')[1]);
|
||||
const targetUrl = urlParams.get('url');
|
||||
|
||||
if (!targetUrl) {
|
||||
res.statusCode = 400;
|
||||
return res.end('Missing URL parameter');
|
||||
}
|
||||
|
||||
try {
|
||||
// Node.js backend fetch (Bypasses Browser CORS)
|
||||
const response = await fetch(targetUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
res.statusCode = response.status;
|
||||
return res.end(`Failed to fetch from target: ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Copy over relevant headers (Content-Type, Content-Length)
|
||||
response.headers.forEach((value, key) => {
|
||||
res.setHeader(key, value);
|
||||
});
|
||||
|
||||
// Ensure CORS is allowed for the WebContainer iframe
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
|
||||
// Stream the binary bundle back to the client
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
res.end(Buffer.from(arrayBuffer));
|
||||
} catch (err: any) {
|
||||
res.statusCode = 500;
|
||||
res.end(`Proxy Error: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'@shared': path.resolve(__dirname, '../shared'),
|
||||
// Fix for Rollup failing to resolve this deep import from @langchain/anthropic
|
||||
'@anthropic-ai/sdk/lib/transform-json-schema': path.resolve(
|
||||
__dirname,
|
||||
'node_modules/@anthropic-ai/sdk/lib/transform-json-schema.mjs',
|
||||
),
|
||||
'@anthropic-ai/sdk/lib/transform-json-schema': path.resolve(__dirname, 'node_modules/@anthropic-ai/sdk/lib/transform-json-schema.mjs'),
|
||||
// Fix for mermaid d3-color prototype crash on Vercel (known issue with mermaid 10.9.0+ and Vite)
|
||||
mermaid: path.resolve(__dirname, 'node_modules/mermaid/dist/mermaid.esm.min.mjs'),
|
||||
'mermaid': path.resolve(__dirname, 'node_modules/mermaid/dist/mermaid.esm.min.mjs'),
|
||||
},
|
||||
},
|
||||
// Polyfill Buffer for isomorphic-git (Node.js API needed in browser)
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
},
|
||||
// Optimize deps - exclude kuzu-wasm from pre-bundling (it has WASM files)
|
||||
optimizeDeps: {
|
||||
exclude: ['kuzu-wasm'],
|
||||
include: ['buffer'],
|
||||
},
|
||||
// Required for KuzuDB WASM (SharedArrayBuffer needs Cross-Origin Isolation)
|
||||
server: {
|
||||
headers: {
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
},
|
||||
// Allow serving files from node_modules
|
||||
fs: {
|
||||
allow: ['..'],
|
||||
},
|
||||
},
|
||||
// Also set for preview/production builds
|
||||
preview: {
|
||||
headers: {
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
},
|
||||
},
|
||||
// Worker configuration
|
||||
worker: {
|
||||
format: 'es',
|
||||
plugins: () => [wasm(), topLevelAwait()],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue