Compare commits
2 Commits
feature/ai
...
feat/docke
Author | SHA1 | Date | |
---|---|---|---|
37c10f86ce | |||
7fd3e3c546 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
frontend/node_modules
|
||||||
|
backend/server/.env
|
||||||
|
backend/server/node_modules
|
@ -29,9 +29,7 @@ npm run dev
|
|||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
The backend consists of a primary Express and Socket.io server, and 3 Cloudflare Workers microservices for the D1 database, R2 storage, and Workers AI. The D1 database also contains a [service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) to the R2 storage worker. Each open sandbox instantiates a secure Linux sandboxes on E2B, which is used for the terminal and live preview.
|
The backend consists of a primary Express and Socket.io server, and 3 Cloudflare Workers microservices for the D1 database, R2 storage, and Workers AI. The D1 database also contains a [service binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) to the R2 storage worker.
|
||||||
|
|
||||||
You will need to make an account on [E2B](https://e2b.dev/) to get an API key.
|
|
||||||
|
|
||||||
#### Socket.io server
|
#### Socket.io server
|
||||||
|
|
||||||
@ -183,4 +181,3 @@ It should be in the form `category(scope or module): message` in your commit mes
|
|||||||
- [Express](https://expressjs.com/)
|
- [Express](https://expressjs.com/)
|
||||||
- [Socket.io](https://socket.io/)
|
- [Socket.io](https://socket.io/)
|
||||||
- [Drizzle ORM](https://orm.drizzle.team/)
|
- [Drizzle ORM](https://orm.drizzle.team/)
|
||||||
- [E2B](https://e2b.dev/)
|
|
||||||
|
216
backend/ai/package-lock.json
generated
216
backend/ai/package-lock.json
generated
@ -7,9 +7,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "ai",
|
"name": "ai",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
|
||||||
"@anthropic-ai/sdk": "^0.27.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||||
"@cloudflare/workers-types": "^4.20240512.0",
|
"@cloudflare/workers-types": "^4.20240512.0",
|
||||||
@ -18,28 +15,6 @@
|
|||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@anthropic-ai/sdk": {
|
|
||||||
"version": "0.27.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.2.tgz",
|
|
||||||
"integrity": "sha512-Q6gOx4fyHQ+NCSaVeXEKFZfoFWCR3ctUA+sK5oGB7RKUkzUvK64aYM7v1T9ekJKwn8TwRq6IGjqS31n9PbjCIA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "^18.11.18",
|
|
||||||
"@types/node-fetch": "^2.6.4",
|
|
||||||
"abort-controller": "^3.0.0",
|
|
||||||
"agentkeepalive": "^4.2.1",
|
|
||||||
"form-data-encoder": "1.7.2",
|
|
||||||
"formdata-node": "^4.3.2",
|
|
||||||
"node-fetch": "^2.6.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
|
|
||||||
"version": "18.19.50",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
|
|
||||||
"integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~5.26.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@cloudflare/kv-asset-handler": {
|
"node_modules/@cloudflare/kv-asset-handler": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.2.tgz",
|
||||||
@ -886,19 +861,11 @@
|
|||||||
"version": "20.12.11",
|
"version": "20.12.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
|
||||||
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
|
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node-fetch": {
|
|
||||||
"version": "2.6.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
|
|
||||||
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*",
|
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node-forge": {
|
"node_modules/@types/node-forge": {
|
||||||
"version": "1.3.11",
|
"version": "1.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
||||||
@ -977,17 +944,6 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/abort-controller": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
|
||||||
"dependencies": {
|
|
||||||
"event-target-shim": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.11.3",
|
"version": "8.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||||
@ -1009,17 +965,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/agentkeepalive": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
|
|
||||||
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
|
|
||||||
"dependencies": {
|
|
||||||
"humanize-ms": "^1.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
@ -1063,11 +1008,6 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -1201,17 +1141,6 @@
|
|||||||
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/combined-stream": {
|
|
||||||
"version": "1.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
||||||
"dependencies": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||||
@ -1285,14 +1214,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/devalue": {
|
"node_modules/devalue": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.3.tgz",
|
||||||
@ -1366,14 +1287,6 @@
|
|||||||
"@types/estree": "^1.0.0"
|
"@types/estree": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/event-target-shim": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||||
@ -1421,36 +1334,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/form-data-encoder": {
|
|
||||||
"version": "1.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
|
|
||||||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
|
|
||||||
},
|
|
||||||
"node_modules/formdata-node": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"node-domexception": "1.0.0",
|
|
||||||
"web-streams-polyfill": "4.0.0-beta.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@ -1569,14 +1452,6 @@
|
|||||||
"node": ">=16.17.0"
|
"node": ">=16.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/humanize-ms": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
@ -1735,25 +1610,6 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||||
@ -1819,7 +1675,8 @@
|
|||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/mustache": {
|
"node_modules/mustache": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@ -1848,43 +1705,6 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-domexception": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/jimmywarting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://paypal.me/jimmywarting"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-fetch": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
|
||||||
"dependencies": {
|
|
||||||
"whatwg-url": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "4.x || >=6.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"encoding": "^0.1.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"encoding": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||||
@ -2402,11 +2222,6 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tr46": {
|
|
||||||
"version": "0.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
|
||||||
},
|
|
||||||
"node_modules/ts-json-schema-generator": {
|
"node_modules/ts-json-schema-generator": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz",
|
||||||
@ -2477,7 +2292,8 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.11",
|
"version": "5.2.11",
|
||||||
@ -3011,28 +2827,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/web-streams-polyfill": {
|
|
||||||
"version": "4.0.0-beta.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
|
||||||
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webidl-conversions": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-url": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
|
||||||
"dependencies": {
|
|
||||||
"tr46": "~0.0.3",
|
|
||||||
"webidl-conversions": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@ -15,8 +15,5 @@
|
|||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"vitest": "1.3.0",
|
"vitest": "1.3.0",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@anthropic-ai/sdk": "^0.27.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,121 +1,43 @@
|
|||||||
import { Anthropic } from "@anthropic-ai/sdk";
|
|
||||||
import { MessageParam } from "@anthropic-ai/sdk/src/resources/messages.js";
|
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
ANTHROPIC_API_KEY: string;
|
AI: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request, env: Env): Promise<Response> {
|
async fetch(request, env): Promise<Response> {
|
||||||
// Handle CORS preflight requests
|
if (request.method !== "GET") {
|
||||||
if (request.method === "OPTIONS") {
|
return new Response("Method Not Allowed", { status: 405 })
|
||||||
return new Response(null, {
|
}
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
const url = new URL(request.url)
|
||||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
const fileName = url.searchParams.get("fileName")
|
||||||
"Access-Control-Allow-Headers": "Content-Type",
|
const instructions = url.searchParams.get("instructions")
|
||||||
|
const line = url.searchParams.get("line")
|
||||||
|
const code = url.searchParams.get("code")
|
||||||
|
|
||||||
|
const response = await env.AI.run("@cf/meta/llama-3-8b-instruct", {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
"You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is CORRECT, efficient, and follows best practices. You may generate multiple lines of code if necessary. When you generate code, you should ONLY return the code, and nothing else. You MUST NOT include backticks in the code you generate.",
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
}
|
role: "user",
|
||||||
|
content: `The file is called ${fileName}.`,
|
||||||
if (request.method !== "GET" && request.method !== "POST") {
|
|
||||||
return new Response("Method Not Allowed", { status: 405 });
|
|
||||||
}
|
|
||||||
|
|
||||||
let body;
|
|
||||||
let isEditCodeWidget = false;
|
|
||||||
if (request.method === "POST") {
|
|
||||||
body = await request.json() as { messages: unknown; context: unknown; activeFileContent: string };
|
|
||||||
} else {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const fileName = url.searchParams.get("fileName") || "";
|
|
||||||
const code = url.searchParams.get("code") || "";
|
|
||||||
const line = url.searchParams.get("line") || "";
|
|
||||||
const instructions = url.searchParams.get("instructions") || "";
|
|
||||||
|
|
||||||
body = {
|
|
||||||
messages: [{ role: "human", content: instructions }],
|
|
||||||
context: `File: ${fileName}\nLine: ${line}\nCode:\n${code}`,
|
|
||||||
activeFileContent: code,
|
|
||||||
};
|
|
||||||
isEditCodeWidget = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = body.messages;
|
|
||||||
const context = body.context;
|
|
||||||
const activeFileContent = body.activeFileContent;
|
|
||||||
|
|
||||||
if (!Array.isArray(messages) || messages.length === 0) {
|
|
||||||
return new Response("Invalid or empty messages", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
let systemMessage;
|
|
||||||
if (isEditCodeWidget) {
|
|
||||||
systemMessage = `You are an AI code editor. Your task is to modify the given code based on the user's instructions. Only output the modified code, without any explanations or markdown formatting. The code should be a direct replacement for the existing code.
|
|
||||||
|
|
||||||
Context:
|
|
||||||
${context}
|
|
||||||
|
|
||||||
Active File Content:
|
|
||||||
${activeFileContent}
|
|
||||||
|
|
||||||
Instructions: ${messages[0].content}
|
|
||||||
|
|
||||||
Respond only with the modified code that can directly replace the existing code.`;
|
|
||||||
} else {
|
|
||||||
systemMessage = `You are an intelligent programming assistant. Please respond to the following request concisely. If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example:
|
|
||||||
|
|
||||||
\`\`\`python
|
|
||||||
print("Hello, World!")
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point.
|
|
||||||
|
|
||||||
${context ? `Context:\n${context}\n` : ''}
|
|
||||||
${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const anthropicMessages = messages.map(msg => ({
|
|
||||||
role: msg.role === 'human' ? 'user' : 'assistant',
|
|
||||||
content: msg.content
|
|
||||||
})) as MessageParam[];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const anthropic = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
|
|
||||||
|
|
||||||
const stream = await anthropic.messages.create({
|
|
||||||
model: "claude-3-5-sonnet-20240620",
|
|
||||||
max_tokens: 1024,
|
|
||||||
system: systemMessage,
|
|
||||||
messages: anthropicMessages,
|
|
||||||
stream: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
|
|
||||||
const streamResponse = new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
|
|
||||||
const bytes = encoder.encode(chunk.delta.text);
|
|
||||||
controller.enqueue(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controller.close();
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
role: "user",
|
||||||
|
content: `Here are my instructions on what to generate: ${instructions}.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: `Suggest me code to insert at line ${line} in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows
|
||||||
|
|
||||||
return new Response(streamResponse, {
|
${code}`,
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/plain; charset=utf-8",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Cache-Control": "no-cache",
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
},
|
},
|
||||||
});
|
],
|
||||||
} catch (error) {
|
})
|
||||||
console.error("Error:", error);
|
|
||||||
return new Response("Internal Server Error", { status: 500 });
|
return new Response(JSON.stringify(response))
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
} satisfies ExportedHandler<Env>
|
||||||
|
@ -101,7 +101,7 @@ export default {
|
|||||||
return success
|
return success
|
||||||
} else if (method === "PUT") {
|
} else if (method === "PUT") {
|
||||||
const initSchema = z.object({
|
const initSchema = z.object({
|
||||||
type: z.string(),
|
type: z.enum(["react", "node"]),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
visibility: z.enum(["public", "private"]),
|
visibility: z.enum(["public", "private"]),
|
||||||
@ -110,13 +110,8 @@ export default {
|
|||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { type, name, userId, visibility } = initSchema.parse(body)
|
const { type, name, userId, visibility } = initSchema.parse(body)
|
||||||
|
|
||||||
const userSandboxes = await db
|
const allSandboxes = await db.select().from(sandbox).all()
|
||||||
.select()
|
if (allSandboxes.length >= 8) {
|
||||||
.from(sandbox)
|
|
||||||
.where(eq(sandbox.userId, userId))
|
|
||||||
.all()
|
|
||||||
|
|
||||||
if (userSandboxes.length >= 8) {
|
|
||||||
return new Response("You reached the maximum # of sandboxes.", {
|
return new Response("You reached the maximum # of sandboxes.", {
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
})
|
||||||
|
@ -26,7 +26,7 @@ export const sandbox = sqliteTable("sandbox", {
|
|||||||
.primaryKey()
|
.primaryKey()
|
||||||
.unique(),
|
.unique(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
type: text("type").notNull(),
|
type: text("type", { enum: ["react", "node"] }).notNull(),
|
||||||
visibility: text("visibility", { enum: ["public", "private"] }),
|
visibility: text("visibility", { enum: ["public", "private"] }),
|
||||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }),
|
createdAt: integer("createdAt", { mode: "timestamp_ms" }),
|
||||||
userId: text("user_id")
|
userId: text("user_id")
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
# Set WORKERS_KEY to be the same as KEY in /backend/storage/wrangler.toml.
|
# Set WORKERS_KEY to be the same as KEY in /backend/storage/wrangler.toml.
|
||||||
# Set DATABASE_WORKER_URL and STORAGE_WORKER_URL after deploying the workers.
|
# Set DATABASE_WORKER_URL and STORAGE_WORKER_URL after deploying the workers.
|
||||||
# DOKKU_HOST and DOKKU_USERNAME are used to authenticate via SSH with the Dokku server
|
|
||||||
# DOKKU_KEY is the path to an SSH (.pem) key on the local machine
|
|
||||||
|
|
||||||
PORT=4000
|
PORT=4000
|
||||||
WORKERS_KEY=
|
WORKERS_KEY=
|
||||||
DATABASE_WORKER_URL=
|
DATABASE_WORKER_URL=
|
||||||
STORAGE_WORKER_URL=
|
STORAGE_WORKER_URL=
|
||||||
E2B_API_KEY=
|
|
||||||
DOKKU_HOST=
|
|
||||||
DOKKU_USERNAME=
|
|
||||||
DOKKU_KEY=
|
|
587
backend/server/package-lock.json
generated
587
backend/server/package-lock.json
generated
@ -12,28 +12,25 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.2-beta.47",
|
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"ssh2": "^1.15.0",
|
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"@types/ssh2": "^1.15.0",
|
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.25.0",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
||||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
"integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -41,28 +38,6 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@bufbuild/protobuf": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="
|
|
||||||
},
|
|
||||||
"node_modules/@connectrpc/connect": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@bufbuild/protobuf": "^1.4.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@connectrpc/connect-web": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@bufbuild/protobuf": "^1.4.2",
|
|
||||||
"@connectrpc/connect": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@ -85,9 +60,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.0",
|
"version": "1.4.15",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
@ -100,44 +75,10 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@kwsites/file-exists": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@kwsites/file-exists/node_modules/debug": {
|
|
||||||
"version": "4.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@kwsites/file-exists/node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"node_modules/@kwsites/promise-deferred": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
|
|
||||||
},
|
|
||||||
"node_modules/@socket.io/component-emitter": {
|
"node_modules/@socket.io/component-emitter": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz",
|
||||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
"integrity": "sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg=="
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
@ -208,9 +149,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express-serve-static-core": {
|
"node_modules/@types/express-serve-static-core": {
|
||||||
"version": "4.19.5",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz",
|
||||||
"integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
|
"integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@ -232,9 +173,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.15",
|
"version": "20.12.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||||
"integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==",
|
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@ -272,23 +213,11 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/ssh2": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.15.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
"integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==",
|
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "^18.11.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/ssh2/node_modules/@types/node": {
|
|
||||||
"version": "18.19.44",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.44.tgz",
|
|
||||||
"integrity": "sha512-ZsbGerYg72WMXUIE9fYxtvfzLEuq6q8mKERdWFnqTmOvudMxnz+CBNRoOwJ2kNpFOncrKjT1hZwxjlFgQ9qvQA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~5.26.4"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
@ -303,9 +232,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.12.1",
|
"version": "8.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@ -315,13 +244,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn-walk": {
|
"node_modules/acorn-walk": {
|
||||||
"version": "8.3.3",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
|
||||||
"acorn": "^8.11.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@ -372,14 +298,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
},
|
},
|
||||||
"node_modules/asn1": {
|
|
||||||
"version": "0.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
|
||||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": "~2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -394,14 +312,6 @@
|
|||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bcrypt-pbkdf": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
|
||||||
"dependencies": {
|
|
||||||
"tweetnacl": "^0.14.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@ -448,40 +358,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bufferutil": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
|
|
||||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"node-gyp-build": "^4.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.14.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/buildcheck": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@ -523,6 +410,14 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chalk/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk/node_modules/supports-color": {
|
"node_modules/chalk/node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
@ -587,11 +482,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
"node_modules/compare-versions": {
|
|
||||||
"version": "6.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
|
||||||
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -624,6 +514,28 @@
|
|||||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/concurrently/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/supports-color": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@ -668,20 +580,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cpu-features": {
|
|
||||||
"version": "0.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
|
||||||
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"buildcheck": "~0.0.6",
|
|
||||||
"nan": "^2.19.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/create-require": {
|
"node_modules/create-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
@ -764,22 +662,6 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/e2b": {
|
|
||||||
"version": "0.16.2-beta.47",
|
|
||||||
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.2-beta.47.tgz",
|
|
||||||
"integrity": "sha512-tMPDYLMD+8+JyLPrsWft3NHBhK5YKOFOXzKMwpOKR5KvXOkd1silkArDwplmBUzN/eG/uRzWdtHZs9mHUQ5b9g==",
|
|
||||||
"dependencies": {
|
|
||||||
"@bufbuild/protobuf": "^1.10.0",
|
|
||||||
"@connectrpc/connect": "^1.4.0",
|
|
||||||
"@connectrpc/connect-web": "^1.4.0",
|
|
||||||
"compare-versions": "^6.1.0",
|
|
||||||
"openapi-fetch": "^0.9.7",
|
|
||||||
"platform": "^1.3.6"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -799,9 +681,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io": {
|
"node_modules/engine.io": {
|
||||||
"version": "6.5.5",
|
"version": "6.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
|
||||||
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
|
"integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/cookie": "^0.4.1",
|
"@types/cookie": "^0.4.1",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
@ -812,16 +694,16 @@
|
|||||||
"cors": "~2.8.5",
|
"cors": "~2.8.5",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io-parser": "~5.2.1",
|
"engine.io-parser": "~5.2.1",
|
||||||
"ws": "~8.17.1"
|
"ws": "~8.11.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.2.0"
|
"node": ">=10.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-parser": {
|
"node_modules/engine.io-parser": {
|
||||||
"version": "5.2.3",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
|
||||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
@ -835,9 +717,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io/node_modules/debug": {
|
"node_modules/engine.io/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@ -855,26 +737,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/engine.io/node_modules/ws": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
@ -957,9 +819,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@ -1073,11 +935,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-property-descriptors": {
|
"node_modules/has-property-descriptors": {
|
||||||
@ -1224,6 +1087,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lru-cache": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/make-error": {
|
"node_modules/make-error": {
|
||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
@ -1299,10 +1174,9 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
"node_modules/nan": {
|
"node_modules/nan": {
|
||||||
"version": "2.20.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
||||||
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
|
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw=="
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
@ -1312,22 +1186,19 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-gyp-build": {
|
"node_modules/node-pty": {
|
||||||
"version": "4.8.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||||
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||||
"optional": true,
|
"hasInstallScript": true,
|
||||||
"peer": true,
|
"dependencies": {
|
||||||
"bin": {
|
"nan": "^2.17.0"
|
||||||
"node-gyp-build": "bin.js",
|
|
||||||
"node-gyp-build-optional": "optional.js",
|
|
||||||
"node-gyp-build-test": "build-test.js"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.4",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
|
||||||
"integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
|
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^3.5.2",
|
"chokidar": "^3.5.2",
|
||||||
@ -1353,9 +1224,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon/node_modules/debug": {
|
"node_modules/nodemon/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
@ -1369,31 +1240,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon/node_modules/has-flag": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/nodemon/node_modules/ms": {
|
"node_modules/nodemon/node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/nodemon/node_modules/supports-color": {
|
"node_modules/nopt": {
|
||||||
"version": "5.5.0",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^3.0.0"
|
"abbrev": "1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nopt": "bin/nopt.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
@ -1414,12 +1279,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.2",
|
"version": "1.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||||
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
|
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@ -1435,19 +1297,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-fetch": {
|
|
||||||
"version": "0.9.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz",
|
|
||||||
"integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==",
|
|
||||||
"dependencies": {
|
|
||||||
"openapi-typescript-helpers": "^0.0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/openapi-typescript-helpers": {
|
|
||||||
"version": "0.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz",
|
|
||||||
"integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="
|
|
||||||
},
|
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@ -1473,11 +1322,6 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/platform": {
|
|
||||||
"version": "1.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
|
||||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
|
|
||||||
},
|
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@ -1595,10 +1439,13 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.6.3",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
@ -1694,41 +1541,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/simple-git": {
|
|
||||||
"version": "3.25.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.25.0.tgz",
|
|
||||||
"integrity": "sha512-KIY5sBnzc4yEcJXW7Tdv4viEz8KyG+nU0hay+DWZasvdFOYKeUZ6Xc25LUHHjw0tinPT7O1eY6pzX7pRT1K8rw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@kwsites/file-exists": "^1.1.1",
|
|
||||||
"@kwsites/promise-deferred": "^1.1.1",
|
|
||||||
"debug": "^4.3.5"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/steveukx/git-js?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/simple-git/node_modules/debug": {
|
|
||||||
"version": "4.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/simple-git/node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"node_modules/simple-update-notifier": {
|
"node_modules/simple-update-notifier": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||||
@ -1759,18 +1571,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-adapter": {
|
"node_modules/socket.io-adapter": {
|
||||||
"version": "2.5.5",
|
"version": "2.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz",
|
||||||
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
"integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "~4.3.4",
|
"debug": "~4.3.4",
|
||||||
"ws": "~8.17.1"
|
"ws": "~8.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-adapter/node_modules/debug": {
|
"node_modules/socket.io-adapter/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@ -1788,26 +1600,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-adapter/node_modules/ws": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-parser": {
|
"node_modules/socket.io-parser": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
@ -1821,9 +1613,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-parser/node_modules/debug": {
|
"node_modules/socket.io-parser/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@ -1842,9 +1634,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/socket.io/node_modules/debug": {
|
"node_modules/socket.io/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@ -1867,23 +1659,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
|
||||||
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ=="
|
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ssh2": {
|
|
||||||
"version": "1.15.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz",
|
|
||||||
"integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"asn1": "^0.2.6",
|
|
||||||
"bcrypt-pbkdf": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.16.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"cpu-features": "~0.0.9",
|
|
||||||
"nan": "^2.18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@ -1917,17 +1692,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "8.1.1",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=4"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
@ -1951,10 +1724,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/touch": {
|
"node_modules/touch": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||||
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"nopt": "~1.0.10"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"nodetouch": "bin/nodetouch.js"
|
"nodetouch": "bin/nodetouch.js"
|
||||||
}
|
}
|
||||||
@ -2011,14 +1787,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
},
|
|
||||||
"node_modules/tweetnacl": {
|
|
||||||
"version": "0.14.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
|
||||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
|
|
||||||
},
|
},
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "1.6.18",
|
"version": "1.6.18",
|
||||||
@ -2033,9 +1804,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.4",
|
"version": "5.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -2064,20 +1835,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/utf-8-validate": {
|
|
||||||
"version": "6.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz",
|
|
||||||
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"node-gyp-build": "^4.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.14.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@ -2116,6 +1873,26 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||||
|
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
@ -2124,6 +1901,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
@ -2159,9 +1942,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.23.8",
|
"version": "3.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,16 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.2-beta.47",
|
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"ssh2": "^1.15.0",
|
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"@types/ssh2": "^1.15.0",
|
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { SSHSocketClient, SSHConfig } from "./SSHSocketClient"
|
|
||||||
|
|
||||||
export interface DokkuResponse {
|
|
||||||
ok: boolean;
|
|
||||||
output: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DokkuClient extends SSHSocketClient {
|
|
||||||
|
|
||||||
constructor(config: SSHConfig) {
|
|
||||||
super(
|
|
||||||
config,
|
|
||||||
"/var/run/dokku-daemon/dokku-daemon.sock"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendCommand(command: string): Promise<DokkuResponse> {
|
|
||||||
try {
|
|
||||||
const response = await this.sendData(command);
|
|
||||||
|
|
||||||
if (typeof response !== "string") {
|
|
||||||
throw new Error("Received data is not a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.parse(response);
|
|
||||||
} catch (error: any) {
|
|
||||||
throw new Error(`Failed to send command: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async listApps(): Promise<string[]> {
|
|
||||||
const response = await this.sendCommand("apps:list");
|
|
||||||
return response.output.split("\n").slice(1); // Split by newline and ignore the first line (header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { SSHConfig };
|
|
@ -1,90 +0,0 @@
|
|||||||
import { Client } from "ssh2";
|
|
||||||
|
|
||||||
export interface SSHConfig {
|
|
||||||
host: string;
|
|
||||||
port?: number;
|
|
||||||
username: string;
|
|
||||||
privateKey: Buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SSHSocketClient {
|
|
||||||
private conn: Client;
|
|
||||||
private config: SSHConfig;
|
|
||||||
private socketPath: string;
|
|
||||||
private isConnected: boolean = false;
|
|
||||||
|
|
||||||
constructor(config: SSHConfig, socketPath: string) {
|
|
||||||
this.conn = new Client();
|
|
||||||
this.config = { ...config, port: 22};
|
|
||||||
this.socketPath = socketPath;
|
|
||||||
|
|
||||||
this.setupTerminationHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupTerminationHandlers() {
|
|
||||||
process.on("SIGINT", this.closeConnection.bind(this));
|
|
||||||
process.on("SIGTERM", this.closeConnection.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeConnection() {
|
|
||||||
console.log("Closing SSH connection...");
|
|
||||||
this.conn.end();
|
|
||||||
this.isConnected = false;
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.conn
|
|
||||||
.on("ready", () => {
|
|
||||||
console.log("SSH connection established");
|
|
||||||
this.isConnected = true;
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.on("error", (err) => {
|
|
||||||
console.error("SSH connection error:", err);
|
|
||||||
this.isConnected = false;
|
|
||||||
reject(err);
|
|
||||||
})
|
|
||||||
.on("close", () => {
|
|
||||||
console.log("SSH connection closed");
|
|
||||||
this.isConnected = false;
|
|
||||||
})
|
|
||||||
.connect(this.config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendData(data: string): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
reject(new Error("SSH connection is not established"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conn.exec(
|
|
||||||
`echo "${data}" | nc -U ${this.socketPath}`,
|
|
||||||
(err, stream) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream
|
|
||||||
.on("close", (code: number, signal: string) => {
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`Stream closed with code ${code} and signal ${signal}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.on("data", (data: Buffer) => {
|
|
||||||
resolve(data.toString());
|
|
||||||
})
|
|
||||||
.stderr.on("data", (data: Buffer) => {
|
|
||||||
reject(new Error(data.toString()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
import simpleGit, { SimpleGit } from "simple-git";
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs";
|
|
||||||
import os from "os";
|
|
||||||
|
|
||||||
export type FileData = {
|
|
||||||
id: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SecureGitClient {
|
|
||||||
private gitUrl: string;
|
|
||||||
private sshKeyPath: string;
|
|
||||||
|
|
||||||
constructor(gitUrl: string, sshKeyPath: string) {
|
|
||||||
this.gitUrl = gitUrl;
|
|
||||||
this.sshKeyPath = sshKeyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushFiles(fileData: FileData[], repository: string): Promise<void> {
|
|
||||||
let tempDir: string | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a temporary directory
|
|
||||||
tempDir = fs.mkdtempSync(path.posix.join(os.tmpdir(), 'git-push-'));
|
|
||||||
console.log(`Temporary directory created: ${tempDir}`);
|
|
||||||
|
|
||||||
// Write files to the temporary directory
|
|
||||||
console.log(`Writing ${fileData.length} files.`);
|
|
||||||
for (const { id, data } of fileData) {
|
|
||||||
const filePath = path.posix.join(tempDir, id);
|
|
||||||
const dirPath = path.dirname(filePath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(dirPath)) {
|
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
|
||||||
}
|
|
||||||
fs.writeFileSync(filePath, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the simple-git instance with the temporary directory and custom SSH command
|
|
||||||
const git: SimpleGit = simpleGit(tempDir, {
|
|
||||||
config: [
|
|
||||||
'core.sshCommand=ssh -i ' + this.sshKeyPath + ' -o IdentitiesOnly=yes'
|
|
||||||
]
|
|
||||||
}).outputHandler((_command, stdout, stderr) => {
|
|
||||||
stdout.pipe(process.stdout);
|
|
||||||
stderr.pipe(process.stderr);
|
|
||||||
});;
|
|
||||||
|
|
||||||
// Initialize a new Git repository
|
|
||||||
await git.init();
|
|
||||||
|
|
||||||
// Add remote repository
|
|
||||||
await git.addRemote("origin", `${this.gitUrl}:${repository}`);
|
|
||||||
|
|
||||||
// Add files to the repository
|
|
||||||
for (const {id, data} of fileData) {
|
|
||||||
await git.add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the changes
|
|
||||||
await git.commit("Add files.");
|
|
||||||
|
|
||||||
// Push the changes to the remote repository
|
|
||||||
await git.push("origin", "master", {'--force': null});
|
|
||||||
|
|
||||||
console.log("Files successfully pushed to the repository");
|
|
||||||
|
|
||||||
if (tempDir) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
console.log(`Temporary directory removed: ${tempDir}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (tempDir) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
console.log(`Temporary directory removed: ${tempDir}`);
|
|
||||||
}
|
|
||||||
console.error("Error pushing files to the repository:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
import { Sandbox, ProcessHandle } from "e2b";
|
|
||||||
|
|
||||||
// Terminal class to manage a pseudo-terminal (PTY) in a sandbox environment
|
|
||||||
export class Terminal {
|
|
||||||
private pty: ProcessHandle | undefined; // Holds the PTY process handle
|
|
||||||
private sandbox: Sandbox; // Reference to the sandbox environment
|
|
||||||
|
|
||||||
// Constructor initializes the Terminal with a sandbox
|
|
||||||
constructor(sandbox: Sandbox) {
|
|
||||||
this.sandbox = sandbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the terminal with specified rows, columns, and data handler
|
|
||||||
async init({
|
|
||||||
rows = 20,
|
|
||||||
cols = 80,
|
|
||||||
onData,
|
|
||||||
}: {
|
|
||||||
rows?: number;
|
|
||||||
cols?: number;
|
|
||||||
onData: (responseData: string) => void;
|
|
||||||
}): Promise<void> {
|
|
||||||
// Create a new PTY process
|
|
||||||
this.pty = await this.sandbox.pty.create({
|
|
||||||
rows,
|
|
||||||
cols,
|
|
||||||
timeout: 0,
|
|
||||||
onData: (data: Uint8Array) => {
|
|
||||||
onData(new TextDecoder().decode(data)); // Convert received data to string and pass to handler
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send data to the terminal
|
|
||||||
async sendData(data: string) {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.sandbox.pty.sendInput(this.pty.pid, new TextEncoder().encode(data));
|
|
||||||
} else {
|
|
||||||
console.log("Cannot send data because pty is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize the terminal
|
|
||||||
async resize(size: { cols: number; rows: number }): Promise<void> {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.sandbox.pty.resize(this.pty.pid, size);
|
|
||||||
} else {
|
|
||||||
console.log("Cannot resize terminal because pty is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the terminal, killing the PTY process and stopping the input stream
|
|
||||||
async close(): Promise<void> {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.pty.kill();
|
|
||||||
} else {
|
|
||||||
console.log("Cannot kill pty because it is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage example:
|
|
||||||
// const terminal = new Terminal(sandbox);
|
|
||||||
// await terminal.init();
|
|
||||||
// terminal.sendData('ls -la');
|
|
||||||
// await terminal.resize({ cols: 100, rows: 30 });
|
|
||||||
// await terminal.close();
|
|
@ -1,177 +0,0 @@
|
|||||||
import * as dotenv from "dotenv";
|
|
||||||
import {
|
|
||||||
R2FileBody,
|
|
||||||
R2Files,
|
|
||||||
Sandbox,
|
|
||||||
TFile,
|
|
||||||
TFileData,
|
|
||||||
TFolder,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
export const getSandboxFiles = async (id: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?sandboxId=${id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const data: R2Files = await res.json();
|
|
||||||
|
|
||||||
const paths = data.objects.map((obj) => obj.key);
|
|
||||||
const processedFiles = await processFiles(paths, id);
|
|
||||||
return processedFiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFolder = async (folderId: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?folderId=${folderId}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const data: R2Files = await res.json();
|
|
||||||
|
|
||||||
return data.objects.map((obj) => obj.key);
|
|
||||||
};
|
|
||||||
|
|
||||||
const processFiles = async (paths: string[], id: string) => {
|
|
||||||
const root: TFolder = { id: "/", type: "folder", name: "/", children: [] };
|
|
||||||
const fileData: TFileData[] = [];
|
|
||||||
|
|
||||||
paths.forEach((path) => {
|
|
||||||
const allParts = path.split("/");
|
|
||||||
if (allParts[1] !== id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = allParts.slice(2);
|
|
||||||
let current: TFolder = root;
|
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
|
||||||
const part = parts[i];
|
|
||||||
const isFile = i === parts.length - 1 && part.length;
|
|
||||||
const existing = current.children.find((child) => child.name === part);
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
if (!isFile) {
|
|
||||||
current = existing as TFolder;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isFile) {
|
|
||||||
const file: TFile = { id: path, type: "file", name: part };
|
|
||||||
current.children.push(file);
|
|
||||||
fileData.push({ id: path, data: "" });
|
|
||||||
} else {
|
|
||||||
const folder: TFolder = {
|
|
||||||
// id: path, // todo: wrong id. for example, folder "src" ID is: projects/a7vgttfqbgy403ratp7du3ln/src/App.css
|
|
||||||
id: `projects/${id}/${parts.slice(0, i + 1).join("/")}`,
|
|
||||||
type: "folder",
|
|
||||||
name: part,
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
current.children.push(folder);
|
|
||||||
current = folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
fileData.map(async (file) => {
|
|
||||||
const data = await fetchFileContent(file.id);
|
|
||||||
file.data = data;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
files: root.children,
|
|
||||||
fileData,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFileContent = async (fileId: string): Promise<string> => {
|
|
||||||
try {
|
|
||||||
const fileRes = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?fileId=${fileId}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return await fileRes.text();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("ERROR fetching file:", error);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createFile = async (fileId: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId }),
|
|
||||||
});
|
|
||||||
return res.ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renameFile = async (
|
|
||||||
fileId: string,
|
|
||||||
newFileId: string,
|
|
||||||
data: string
|
|
||||||
) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/rename`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId, newFileId, data }),
|
|
||||||
});
|
|
||||||
return res.ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const saveFile = async (fileId: string, data: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/save`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId, data }),
|
|
||||||
});
|
|
||||||
return res.ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteFile = async (fileId: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId }),
|
|
||||||
});
|
|
||||||
return res.ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProjectSize = async (id: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api/size?sandboxId=${id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return (await res.json()).size;
|
|
||||||
};
|
|
@ -1,20 +1,14 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import express, { Express } from "express";
|
import express, { Express } from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { createServer } from "http";
|
import { createServer } from "http";
|
||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
import { DokkuClient } from "./DokkuClient";
|
|
||||||
import { SecureGitClient, FileData } from "./SecureGitClient";
|
|
||||||
import fs, { readFile } from "fs";
|
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { User } from "./types";
|
||||||
TFile,
|
|
||||||
TFileData,
|
|
||||||
TFolder,
|
|
||||||
User
|
|
||||||
} from "./types";
|
|
||||||
import {
|
import {
|
||||||
createFile,
|
createFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
@ -23,13 +17,8 @@ import {
|
|||||||
getSandboxFiles,
|
getSandboxFiles,
|
||||||
renameFile,
|
renameFile,
|
||||||
saveFile,
|
saveFile,
|
||||||
} from "./fileoperations";
|
} from "./utils";
|
||||||
import { LockManager } from "./utils";
|
import { IDisposable, IPty, spawn } from "node-pty";
|
||||||
|
|
||||||
import { Sandbox, Filesystem, FilesystemEvent, EntryInfo, WatchHandle } from "e2b";
|
|
||||||
|
|
||||||
import { Terminal } from "./Terminal"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MAX_BODY_SIZE,
|
MAX_BODY_SIZE,
|
||||||
createFileRL,
|
createFileRL,
|
||||||
@ -39,21 +28,6 @@ import {
|
|||||||
saveFileRL,
|
saveFileRL,
|
||||||
} from "./ratelimit";
|
} from "./ratelimit";
|
||||||
|
|
||||||
process.on('uncaughtException', (error) => {
|
|
||||||
console.error('Uncaught Exception:', error);
|
|
||||||
// Do not exit the process
|
|
||||||
// You can add additional logging or recovery logic here
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
|
||||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
||||||
// Do not exit the process
|
|
||||||
// You can also handle the rejected promise here if needed
|
|
||||||
});
|
|
||||||
|
|
||||||
// The amount of time in ms that a container will stay alive without a hearbeat.
|
|
||||||
const CONTAINER_TIMEOUT = 60_000;
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
@ -69,21 +43,11 @@ const io = new Server(httpServer, {
|
|||||||
let inactivityTimeout: NodeJS.Timeout | null = null;
|
let inactivityTimeout: NodeJS.Timeout | null = null;
|
||||||
let isOwnerConnected = false;
|
let isOwnerConnected = false;
|
||||||
|
|
||||||
const containers: Record<string, Sandbox> = {};
|
const terminals: {
|
||||||
const connections: Record<string, number> = {};
|
[id: string]: { terminal: IPty; onData: IDisposable; onExit: IDisposable };
|
||||||
const terminals: Record<string, Terminal> = {};
|
} = {};
|
||||||
|
|
||||||
const dirName = "/home/user";
|
const dirName = path.join(__dirname, "..");
|
||||||
|
|
||||||
const moveFile = async (filesystem: Filesystem, filePath: string, newFilePath: string) => {
|
|
||||||
try {
|
|
||||||
const fileContents = await filesystem.read(filePath);
|
|
||||||
await filesystem.write(newFilePath, fileContents);
|
|
||||||
await filesystem.remove(filePath);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error moving file from ${filePath} to ${newFilePath}:`, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
io.use(async (socket, next) => {
|
io.use(async (socket, next) => {
|
||||||
const handshakeSchema = z.object({
|
const handshakeSchema = z.object({
|
||||||
@ -136,29 +100,7 @@ io.use(async (socket, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
const lockManager = new LockManager();
|
|
||||||
|
|
||||||
if (!process.env.DOKKU_HOST) console.error('Environment variable DOKKU_HOST is not defined');
|
|
||||||
if (!process.env.DOKKU_USERNAME) console.error('Environment variable DOKKU_USERNAME is not defined');
|
|
||||||
if (!process.env.DOKKU_KEY) console.error('Environment variable DOKKU_KEY is not defined');
|
|
||||||
|
|
||||||
const client =
|
|
||||||
process.env.DOKKU_HOST && process.env.DOKKU_KEY && process.env.DOKKU_USERNAME
|
|
||||||
? new DokkuClient({
|
|
||||||
host: process.env.DOKKU_HOST,
|
|
||||||
username: process.env.DOKKU_USERNAME,
|
|
||||||
privateKey: fs.readFileSync(process.env.DOKKU_KEY),
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
client?.connect();
|
|
||||||
|
|
||||||
const git = process.env.DOKKU_HOST && process.env.DOKKU_KEY ? new SecureGitClient(
|
|
||||||
`dokku@${process.env.DOKKU_HOST}`,
|
|
||||||
process.env.DOKKU_KEY
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
io.on("connection", async (socket) => {
|
io.on("connection", async (socket) => {
|
||||||
try {
|
|
||||||
if (inactivityTimeout) clearTimeout(inactivityTimeout);
|
if (inactivityTimeout) clearTimeout(inactivityTimeout);
|
||||||
|
|
||||||
const data = socket.data as {
|
const data = socket.data as {
|
||||||
@ -169,7 +111,6 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
if (data.isOwner) {
|
if (data.isOwner) {
|
||||||
isOwnerConnected = true;
|
isOwnerConnected = true;
|
||||||
connections[data.sandboxId] = (connections[data.sandboxId] ?? 0) + 1;
|
|
||||||
} else {
|
} else {
|
||||||
if (!isOwnerConnected) {
|
if (!isOwnerConnected) {
|
||||||
socket.emit("disableAccess", "The sandbox owner is not connected.");
|
socket.emit("disableAccess", "The sandbox owner is not connected.");
|
||||||
@ -177,243 +118,39 @@ io.on("connection", async (socket) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdContainer = await lockManager.acquireLock(data.sandboxId, async () => {
|
|
||||||
try {
|
|
||||||
// Start a new container if the container doesn't exist or it timed out.
|
|
||||||
if (!containers[data.sandboxId] || !(await containers[data.sandboxId].isRunning())) {
|
|
||||||
containers[data.sandboxId] = await Sandbox.create({ timeoutMs: CONTAINER_TIMEOUT });
|
|
||||||
console.log("Created container ", data.sandboxId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(`Error creating container ${data.sandboxId}:`, e);
|
|
||||||
io.emit("error", `Error: container creation. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
||||||
const projectDirectory = path.posix.join(dirName, "projects", data.sandboxId);
|
sandboxFiles.fileData.forEach((file) => {
|
||||||
const containerFiles = containers[data.sandboxId].files;
|
const filePath = path.join(dirName, file.id);
|
||||||
const fileWatchers: WatchHandle[] = [];
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFile(filePath, file.data, function (err) {
|
||||||
// Change the owner of the project directory to user
|
if (err) throw err;
|
||||||
const fixPermissions = async (projectDirectory: string) => {
|
});
|
||||||
try {
|
|
||||||
await containers[data.sandboxId].commands.run(
|
|
||||||
`sudo chown -R user "${projectDirectory}"`
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Failed to fix permissions: " + e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the given path is a directory
|
|
||||||
const isDirectory = async (projectDirectory: string): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const result = await containers[data.sandboxId].commands.run(
|
|
||||||
`[ -d "${projectDirectory}" ] && echo "true" || echo "false"`
|
|
||||||
);
|
|
||||||
return result.stdout.trim() === "true";
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Failed to check if directory: " + e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only continue to container setup if a new container was created
|
|
||||||
if (createdContainer) {
|
|
||||||
|
|
||||||
// Copy all files from the project to the container
|
|
||||||
const promises = sandboxFiles.fileData.map(async (file) => {
|
|
||||||
try {
|
|
||||||
const filePath = path.posix.join(dirName, file.id);
|
|
||||||
const parentDirectory = path.dirname(filePath);
|
|
||||||
if (!containerFiles.exists(parentDirectory)) {
|
|
||||||
await containerFiles.makeDir(parentDirectory);
|
|
||||||
}
|
|
||||||
await containerFiles.write(filePath, file.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Failed to create file: " + e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
// Make the logged in user the owner of all project files
|
|
||||||
fixPermissions(projectDirectory);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start filesystem watcher for the project directory
|
|
||||||
const watchDirectory = async (directory: string): Promise<WatchHandle | undefined> => {
|
|
||||||
try {
|
|
||||||
return await containerFiles.watch(directory, async (event: FilesystemEvent) => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
function removeDirName(path : string, dirName : string) {
|
|
||||||
return path.startsWith(dirName) ? path.slice(dirName.length) : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the absolute file path in the container
|
|
||||||
const containerFilePath = path.posix.join(directory, event.name);
|
|
||||||
// This is the file path relative to the home directory
|
|
||||||
const sandboxFilePath = removeDirName(containerFilePath, dirName + "/");
|
|
||||||
// This is the directory being watched relative to the home directory
|
|
||||||
const sandboxDirectory = removeDirName(directory, dirName + "/");
|
|
||||||
|
|
||||||
// Helper function to find a folder by id
|
|
||||||
function findFolderById(files: (TFolder | TFile)[], folderId : string) {
|
|
||||||
return files.find((file : TFolder | TFile) => file.type === "folder" && file.id === folderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A new file or directory was created.
|
|
||||||
if (event.type === "create") {
|
|
||||||
const folder = findFolderById(sandboxFiles.files, sandboxDirectory) as TFolder;
|
|
||||||
const isDir = await isDirectory(containerFilePath);
|
|
||||||
|
|
||||||
const newItem = isDir
|
|
||||||
? { id: sandboxFilePath, name: event.name, type: "folder", children: [] } as TFolder
|
|
||||||
: { id: sandboxFilePath, name: event.name, type: "file" } as TFile;
|
|
||||||
|
|
||||||
if (folder) {
|
|
||||||
// If the folder exists, add the new item (file/folder) as a child
|
|
||||||
folder.children.push(newItem);
|
|
||||||
} else {
|
|
||||||
// If folder doesn't exist, add the new item to the root
|
|
||||||
sandboxFiles.files.push(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDir) {
|
|
||||||
const fileData = await containers[data.sandboxId].files.read(containerFilePath);
|
|
||||||
const fileContents = typeof fileData === "string" ? fileData : "";
|
|
||||||
sandboxFiles.fileData.push({ id: sandboxFilePath, data: fileContents });
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Create ${sandboxFilePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A file or directory was removed or renamed.
|
|
||||||
else if (event.type === "remove" || event.type == "rename") {
|
|
||||||
const folder = findFolderById(sandboxFiles.files, sandboxDirectory) as TFolder;
|
|
||||||
const isDir = await isDirectory(containerFilePath);
|
|
||||||
|
|
||||||
const isFileMatch = (file: TFolder | TFile | TFileData) => file.id === sandboxFilePath || file.id.startsWith(containerFilePath + '/');
|
|
||||||
|
|
||||||
if (folder) {
|
|
||||||
// Remove item from its parent folder
|
|
||||||
folder.children = folder.children.filter((file: TFolder | TFile) => !isFileMatch(file));
|
|
||||||
} else {
|
|
||||||
// Remove from the root if it's not inside a folder
|
|
||||||
sandboxFiles.files = sandboxFiles.files.filter((file: TFolder | TFile) => !isFileMatch(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also remove any corresponding file data
|
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter((file: TFileData) => !isFileMatch(file));
|
|
||||||
|
|
||||||
console.log(`Removed: ${sandboxFilePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The contents of a file were changed.
|
|
||||||
else if (event.type === "write") {
|
|
||||||
const folder = findFolderById(sandboxFiles.files, sandboxDirectory) as TFolder;
|
|
||||||
const fileToWrite = sandboxFiles.fileData.find(file => file.id === sandboxFilePath);
|
|
||||||
|
|
||||||
if (fileToWrite) {
|
|
||||||
fileToWrite.data = await containers[data.sandboxId].files.read(containerFilePath);
|
|
||||||
console.log(`Write to ${sandboxFilePath}`);
|
|
||||||
} else {
|
|
||||||
// If the file is part of a folder structure, locate it and update its data
|
|
||||||
const fileInFolder = folder?.children.find(file => file.id === sandboxFilePath);
|
|
||||||
if (fileInFolder) {
|
|
||||||
const fileData = await containers[data.sandboxId].files.read(containerFilePath);
|
|
||||||
const fileContents = typeof fileData === "string" ? fileData : "";
|
|
||||||
sandboxFiles.fileData.push({ id: sandboxFilePath, data: fileContents });
|
|
||||||
console.log(`Write to ${sandboxFilePath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the client to reload the file list
|
|
||||||
socket.emit("loaded", sandboxFiles.files);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error handling ${event.type} event for ${event.name}:`, error);
|
|
||||||
}
|
|
||||||
}, { "timeout": 0 } )
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error watching filesystem:`, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch the project directory
|
|
||||||
const handle = await watchDirectory(projectDirectory);
|
|
||||||
// Keep track of watch handlers to close later
|
|
||||||
if (handle) fileWatchers.push(handle);
|
|
||||||
|
|
||||||
// Watch all subdirectories of the project directory, but not deeper
|
|
||||||
// This also means directories created after the container is created won't be watched
|
|
||||||
const dirContent = await containerFiles.list(projectDirectory);
|
|
||||||
await Promise.all(dirContent.map(async (item : EntryInfo) => {
|
|
||||||
if (item.type === "dir") {
|
|
||||||
console.log("Watching " + item.path);
|
|
||||||
// Keep track of watch handlers to close later
|
|
||||||
const handle = await watchDirectory(item.path);
|
|
||||||
if (handle) fileWatchers.push(handle);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
socket.emit("loaded", sandboxFiles.files);
|
socket.emit("loaded", sandboxFiles.files);
|
||||||
|
|
||||||
socket.on("heartbeat", async () => {
|
|
||||||
try {
|
|
||||||
// This keeps the container alive for another CONTAINER_TIMEOUT seconds.
|
|
||||||
// The E2B docs are unclear, but the timeout is relative to the time of this method call.
|
|
||||||
await containers[data.sandboxId].setTimeout(CONTAINER_TIMEOUT);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error setting timeout:", e);
|
|
||||||
io.emit("error", `Error: set timeout. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("getFile", (fileId: string, callback) => {
|
socket.on("getFile", (fileId: string, callback) => {
|
||||||
console.log(fileId);
|
|
||||||
try {
|
|
||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
callback(file.data);
|
callback(file.data);
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error getting file:", e);
|
|
||||||
io.emit("error", `Error: get file. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getFolder", async (folderId: string, callback) => {
|
socket.on("getFolder", async (folderId: string, callback) => {
|
||||||
try {
|
|
||||||
const files = await getFolder(folderId);
|
const files = await getFolder(folderId);
|
||||||
callback(files);
|
callback(files);
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error getting folder:", e);
|
|
||||||
io.emit("error", `Error: get folder. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo: send diffs + debounce for efficiency
|
// todo: send diffs + debounce for efficiency
|
||||||
socket.on("saveFile", async (fileId: string, body: string) => {
|
socket.on("saveFile", async (fileId: string, body: string) => {
|
||||||
if (!fileId) return; // handles saving when no file is open
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
|
||||||
socket.emit(
|
|
||||||
"error",
|
|
||||||
"Error: file size too large. Please reduce the file size."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await saveFileRL.consume(data.userId, 1);
|
await saveFileRL.consume(data.userId, 1);
|
||||||
await saveFile(fileId, body);
|
|
||||||
} catch (e) {
|
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
||||||
io.emit("error", "Rate limited: file saving. Please slow down.");
|
socket.emit(
|
||||||
|
"rateLimit",
|
||||||
|
"Rate limited: file size too large. Please reduce the file size."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,98 +158,37 @@ io.on("connection", async (socket) => {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
file.data = body;
|
file.data = body;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.write(
|
fs.writeFile(path.join(dirName, file.id), body, function (err) {
|
||||||
path.posix.join(dirName, file.id),
|
if (err) throw err;
|
||||||
body
|
});
|
||||||
);
|
await saveFile(fileId, body);
|
||||||
fixPermissions(projectDirectory);
|
} catch (e) {
|
||||||
} catch (e: any) {
|
io.emit("rateLimit", "Rate limited: file saving. Please slow down.");
|
||||||
console.error("Error saving file:", e);
|
|
||||||
io.emit("error", `Error: file saving. ${e.message ?? e}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(
|
socket.on("moveFile", async (fileId: string, folderId: string, callback) => {
|
||||||
"moveFile",
|
|
||||||
async (fileId: string, folderId: string, callback) => {
|
|
||||||
try {
|
|
||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const parts = fileId.split("/");
|
const parts = fileId.split("/");
|
||||||
const newFileId = folderId + "/" + parts.pop();
|
const newFileId = folderId + "/" + parts.pop();
|
||||||
|
|
||||||
await moveFile(
|
fs.rename(
|
||||||
containers[data.sandboxId].files,
|
path.join(dirName, fileId),
|
||||||
path.posix.join(dirName, fileId),
|
path.join(dirName, newFileId),
|
||||||
path.posix.join(dirName, newFileId)
|
function (err) {
|
||||||
|
if (err) throw err;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
fixPermissions(projectDirectory);
|
|
||||||
|
|
||||||
file.id = newFileId;
|
file.id = newFileId;
|
||||||
|
|
||||||
await renameFile(fileId, newFileId, file.data);
|
await renameFile(fileId, newFileId, file.data);
|
||||||
const newFiles = await getSandboxFiles(data.sandboxId);
|
const newFiles = await getSandboxFiles(data.sandboxId);
|
||||||
|
|
||||||
callback(newFiles.files);
|
callback(newFiles.files);
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error moving file:", e);
|
|
||||||
io.emit("error", `Error: file moving. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
interface CallbackResponse {
|
|
||||||
success: boolean;
|
|
||||||
apps?: string[];
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on(
|
|
||||||
"list",
|
|
||||||
async (callback: (response: CallbackResponse) => void) => {
|
|
||||||
console.log("Retrieving apps list...");
|
|
||||||
try {
|
|
||||||
if (!client) throw Error("Failed to retrieve apps list: No Dokku client")
|
|
||||||
callback({
|
|
||||||
success: true,
|
|
||||||
apps: await client.listApps()
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
callback({
|
|
||||||
success: false,
|
|
||||||
message: "Failed to retrieve apps list",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.on(
|
|
||||||
"deploy",
|
|
||||||
async (callback: (response: CallbackResponse) => void) => {
|
|
||||||
try {
|
|
||||||
// Push the project files to the Dokku server
|
|
||||||
console.log("Deploying project ${data.sandboxId}...");
|
|
||||||
if (!git) throw Error("Failed to retrieve apps list: No git client")
|
|
||||||
// Remove the /project/[id]/ component of each file path:
|
|
||||||
const fixedFilePaths = sandboxFiles.fileData.map((file) => {
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
id: file.id.split("/").slice(2).join("/"),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// Push all files to Dokku.
|
|
||||||
await git.pushFiles(fixedFilePaths, data.sandboxId);
|
|
||||||
callback({
|
|
||||||
success: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
callback({
|
|
||||||
success: false,
|
|
||||||
message: "Failed to deploy project: " + error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.on("createFile", async (name: string, callback) => {
|
socket.on("createFile", async (name: string, callback) => {
|
||||||
try {
|
try {
|
||||||
@ -520,27 +196,19 @@ io.on("connection", async (socket) => {
|
|||||||
// limit is 200mb
|
// limit is 200mb
|
||||||
if (size > 200 * 1024 * 1024) {
|
if (size > 200 * 1024 * 1024) {
|
||||||
io.emit(
|
io.emit(
|
||||||
"error",
|
"rateLimit",
|
||||||
"Rate limited: project size exceeded. Please delete some files."
|
"Rate limited: project size exceeded. Please delete some files."
|
||||||
);
|
);
|
||||||
callback({ success: false });
|
callback({ success: false });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await createFileRL.consume(data.userId, 1);
|
await createFileRL.consume(data.userId, 1);
|
||||||
} catch (e) {
|
|
||||||
io.emit("error", "Rate limited: file creation. Please slow down.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.write(
|
fs.writeFile(path.join(dirName, id), "", function (err) {
|
||||||
path.posix.join(dirName, id),
|
if (err) throw err;
|
||||||
""
|
});
|
||||||
);
|
|
||||||
fixPermissions(projectDirectory);
|
|
||||||
|
|
||||||
sandboxFiles.files.push({
|
sandboxFiles.files.push({
|
||||||
id,
|
id,
|
||||||
@ -556,42 +224,30 @@ io.on("connection", async (socket) => {
|
|||||||
await createFile(id);
|
await createFile(id);
|
||||||
|
|
||||||
callback({ success: true });
|
callback({ success: true });
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
console.error("Error creating file:", e);
|
io.emit("rateLimit", "Rate limited: file creation. Please slow down.");
|
||||||
io.emit("error", `Error: file creation. ${e.message ?? e}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("createFolder", async (name: string, callback) => {
|
socket.on("createFolder", async (name: string, callback) => {
|
||||||
try {
|
|
||||||
try {
|
try {
|
||||||
await createFolderRL.consume(data.userId, 1);
|
await createFolderRL.consume(data.userId, 1);
|
||||||
} catch (e) {
|
|
||||||
io.emit("error", "Rate limited: folder creation. Please slow down.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.makeDir(
|
fs.mkdir(path.join(dirName, id), { recursive: true }, function (err) {
|
||||||
path.posix.join(dirName, id)
|
if (err) throw err;
|
||||||
);
|
});
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
console.error("Error creating folder:", e);
|
io.emit("rateLimit", "Rate limited: folder creation. Please slow down.");
|
||||||
io.emit("error", `Error: folder creation. ${e.message ?? e}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("renameFile", async (fileId: string, newName: string) => {
|
socket.on("renameFile", async (fileId: string, newName: string) => {
|
||||||
try {
|
|
||||||
try {
|
try {
|
||||||
await renameFileRL.consume(data.userId, 1);
|
await renameFileRL.consume(data.userId, 1);
|
||||||
} catch (e) {
|
|
||||||
io.emit("error", "Rate limited: file renaming. Please slow down.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@ -601,33 +257,29 @@ io.on("connection", async (socket) => {
|
|||||||
const newFileId =
|
const newFileId =
|
||||||
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
||||||
|
|
||||||
await moveFile(
|
fs.rename(
|
||||||
containers[data.sandboxId].files,
|
path.join(dirName, fileId),
|
||||||
path.posix.join(dirName, fileId),
|
path.join(dirName, newFileId),
|
||||||
path.posix.join(dirName, newFileId)
|
function (err) {
|
||||||
|
if (err) throw err;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
fixPermissions(projectDirectory);
|
|
||||||
await renameFile(fileId, newFileId, file.data);
|
await renameFile(fileId, newFileId, file.data);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
console.error("Error renaming folder:", e);
|
io.emit("rateLimit", "Rate limited: file renaming. Please slow down.");
|
||||||
io.emit("error", `Error: folder renaming. ${e.message ?? e}`);
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("deleteFile", async (fileId: string, callback) => {
|
socket.on("deleteFile", async (fileId: string, callback) => {
|
||||||
try {
|
|
||||||
try {
|
try {
|
||||||
await deleteFileRL.consume(data.userId, 1);
|
await deleteFileRL.consume(data.userId, 1);
|
||||||
} catch (e) {
|
|
||||||
io.emit("error", "Rate limited: file deletion. Please slow down.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.remove(
|
fs.unlink(path.join(dirName, fileId), function (err) {
|
||||||
path.posix.join(dirName, fileId)
|
if (err) throw err;
|
||||||
);
|
});
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||||
(f) => f.id !== fileId
|
(f) => f.id !== fileId
|
||||||
);
|
);
|
||||||
@ -636,9 +288,8 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
const newFiles = await getSandboxFiles(data.sandboxId);
|
const newFiles = await getSandboxFiles(data.sandboxId);
|
||||||
callback(newFiles.files);
|
callback(newFiles.files);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
console.error("Error deleting file:", e);
|
io.emit("rateLimit", "Rate limited: file deletion. Please slow down.");
|
||||||
io.emit("error", `Error: file deletion. ${e.message ?? e}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -647,14 +298,13 @@ io.on("connection", async (socket) => {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
socket.on("deleteFolder", async (folderId: string, callback) => {
|
socket.on("deleteFolder", async (folderId: string, callback) => {
|
||||||
try {
|
|
||||||
const files = await getFolder(folderId);
|
const files = await getFolder(folderId);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
await containers[data.sandboxId].files.remove(
|
fs.unlink(path.join(dirName, file), function (err) {
|
||||||
path.posix.join(dirName, file)
|
if (err) throw err;
|
||||||
);
|
});
|
||||||
|
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||||
(f) => f.id !== file
|
(f) => f.id !== file
|
||||||
@ -667,112 +317,68 @@ io.on("connection", async (socket) => {
|
|||||||
const newFiles = await getSandboxFiles(data.sandboxId);
|
const newFiles = await getSandboxFiles(data.sandboxId);
|
||||||
|
|
||||||
callback(newFiles.files);
|
callback(newFiles.files);
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error deleting folder:", e);
|
|
||||||
io.emit("error", `Error: folder deletion. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("createTerminal", async (id: string, callback) => {
|
socket.on("createTerminal", (id: string, callback) => {
|
||||||
try {
|
if (terminals[id] || Object.keys(terminals).length >= 4) {
|
||||||
// Note: The number of terminals per window is limited on the frontend, but not backend
|
|
||||||
if (terminals[id]) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], {
|
||||||
try {
|
name: "xterm",
|
||||||
terminals[id] = new Terminal(containers[data.sandboxId])
|
cols: 100,
|
||||||
await terminals[id].init({
|
cwd: path.join(dirName, "projects", data.sandboxId),
|
||||||
onData: (responseString: string) => {
|
|
||||||
io.emit("terminalResponse", { id, data: responseString });
|
|
||||||
|
|
||||||
function extractPortNumber(inputString: string) {
|
|
||||||
// Remove ANSI escape codes
|
|
||||||
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, '');
|
|
||||||
|
|
||||||
// Regular expression to match port number
|
|
||||||
const regex = /http:\/\/localhost:(\d+)/;
|
|
||||||
// If a match is found, return the port number
|
|
||||||
const match = cleanedString.match(regex);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
const port = parseInt(extractPortNumber(responseString) ?? "");
|
|
||||||
if (port) {
|
|
||||||
io.emit(
|
|
||||||
"previewURL",
|
|
||||||
"https://" + containers[data.sandboxId].getHost(port)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cols: 80,
|
|
||||||
rows: 20,
|
|
||||||
//onExit: () => console.log("Terminal exited", id),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultDirectory = path.posix.join(dirName, "projects", data.sandboxId);
|
const onData = pty.onData((data) => {
|
||||||
const defaultCommands = [
|
io.emit("terminalResponse", {
|
||||||
`cd "${defaultDirectory}"`,
|
id,
|
||||||
"export PS1='user> '",
|
data,
|
||||||
"clear"
|
|
||||||
]
|
|
||||||
for (const command of defaultCommands) await terminals[id].sendData(command + "\r");
|
|
||||||
|
|
||||||
console.log("Created terminal", id);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(`Error creating terminal ${id}:`, e);
|
|
||||||
io.emit("error", `Error: terminal creation. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const onExit = pty.onExit((code) => console.log("exit :(", code));
|
||||||
|
|
||||||
|
pty.write("export PS1='\\u > '\r");
|
||||||
|
pty.write("clear\r");
|
||||||
|
|
||||||
|
terminals[id] = {
|
||||||
|
terminal: pty,
|
||||||
|
onData,
|
||||||
|
onExit,
|
||||||
|
};
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
} catch (e: any) {
|
|
||||||
console.error(`Error creating terminal ${id}:`, e);
|
|
||||||
io.emit("error", `Error: terminal creation. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(
|
socket.on("resizeTerminal", (dimensions: { cols: number; rows: number }) => {
|
||||||
"resizeTerminal",
|
|
||||||
(dimensions: { cols: number; rows: number }) => {
|
|
||||||
try {
|
|
||||||
Object.values(terminals).forEach((t) => {
|
Object.values(terminals).forEach((t) => {
|
||||||
t.resize(dimensions);
|
t.terminal.resize(dimensions.cols, dimensions.rows);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error resizing terminal:", e);
|
|
||||||
io.emit("error", `Error: terminal resizing. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.on("terminalData", async (id: string, data: string) => {
|
socket.on("terminalData", (id: string, data: string) => {
|
||||||
try {
|
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await terminals[id].sendData(data);
|
try {
|
||||||
} catch (e: any) {
|
terminals[id].terminal.write(data);
|
||||||
console.error("Error writing to terminal:", e);
|
} catch (e) {
|
||||||
io.emit("error", `Error: writing to terminal. ${e.message ?? e}`);
|
console.log("Error writing to terminal", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("closeTerminal", async (id: string, callback) => {
|
socket.on("closeTerminal", (id: string, callback) => {
|
||||||
try {
|
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await terminals[id].close();
|
terminals[id].onData.dispose();
|
||||||
|
terminals[id].onExit.dispose();
|
||||||
delete terminals[id];
|
delete terminals[id];
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error closing terminal:", e);
|
|
||||||
io.emit("error", `Error: closing terminal. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(
|
socket.on(
|
||||||
@ -784,7 +390,6 @@ io.on("connection", async (socket) => {
|
|||||||
instructions: string,
|
instructions: string,
|
||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
try {
|
|
||||||
const fetchPromise = fetch(
|
const fetchPromise = fetch(
|
||||||
`${process.env.DATABASE_WORKER_URL}/api/sandbox/generate`,
|
`${process.env.DATABASE_WORKER_URL}/api/sandbox/generate`,
|
||||||
{
|
{
|
||||||
@ -801,7 +406,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
// Generate code from cloudflare workers AI
|
// Generate code from cloudflare workers AI
|
||||||
const generateCodePromise = fetch(
|
const generateCodePromise = fetch(
|
||||||
`${process.env.AI_WORKER_URL}/api?fileName=${encodeURIComponent(fileName)}&code=${encodeURIComponent(code)}&line=${encodeURIComponent(line)}&instructions=${encodeURIComponent(instructions)}`,
|
`${process.env.AI_WORKER_URL}/api?fileName=${fileName}&code=${code}&line=${line}&instructions=${instructions}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -815,44 +420,21 @@ io.on("connection", async (socket) => {
|
|||||||
generateCodePromise,
|
generateCodePromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!generateCodeResponse.ok) {
|
const json = await generateCodeResponse.json();
|
||||||
throw new Error(`HTTP error! status: ${generateCodeResponse.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = generateCodeResponse.body?.getReader();
|
callback({ response: json.response, success: true });
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let result = '';
|
|
||||||
|
|
||||||
if (reader) {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
result += decoder.decode(value, { stream: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The result should now contain only the modified code
|
|
||||||
callback({ response: result.trim(), success: true });
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error generating code:", e);
|
|
||||||
io.emit("error", `Error: code generation. ${e.message ?? e}`);
|
|
||||||
callback({ response: "Error generating code. Please try again.", success: false });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.on("disconnect", async () => {
|
socket.on("disconnect", async () => {
|
||||||
try {
|
|
||||||
if (data.isOwner) {
|
if (data.isOwner) {
|
||||||
connections[data.sandboxId]--;
|
Object.entries(terminals).forEach((t) => {
|
||||||
}
|
const { terminal, onData, onExit } = t[1];
|
||||||
|
onData.dispose();
|
||||||
|
onExit.dispose();
|
||||||
|
delete terminals[t[0]];
|
||||||
|
});
|
||||||
|
|
||||||
// Stop watching file changes in the container
|
|
||||||
Promise.all(fileWatchers.map(async (handle : WatchHandle) => {
|
|
||||||
await handle.close();
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (data.isOwner && connections[data.sandboxId] <= 0) {
|
|
||||||
socket.broadcast.emit(
|
socket.broadcast.emit(
|
||||||
"disableAccess",
|
"disableAccess",
|
||||||
"The sandbox owner has disconnected."
|
"The sandbox owner has disconnected."
|
||||||
@ -875,15 +457,7 @@ io.on("connection", async (socket) => {
|
|||||||
// } else {
|
// } else {
|
||||||
// console.log("number of sockets", sockets.length);
|
// console.log("number of sockets", sockets.length);
|
||||||
// }
|
// }
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Error disconnecting:", e);
|
|
||||||
io.emit("error", `Error: disconnecting. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Error connecting:", e);
|
|
||||||
io.emit("error", `Error: connection. ${e.message ?? e}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
httpServer.listen(port, () => {
|
httpServer.listen(port, () => {
|
||||||
|
@ -1,23 +1,177 @@
|
|||||||
export class LockManager {
|
import * as dotenv from "dotenv";
|
||||||
private locks: { [key: string]: Promise<any> };
|
import {
|
||||||
|
R2FileBody,
|
||||||
|
R2Files,
|
||||||
|
Sandbox,
|
||||||
|
TFile,
|
||||||
|
TFileData,
|
||||||
|
TFolder,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
constructor() {
|
dotenv.config();
|
||||||
this.locks = {};
|
|
||||||
|
export const getSandboxFiles = async (id: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?sandboxId=${id}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const data: R2Files = await res.json();
|
||||||
|
|
||||||
|
const paths = data.objects.map((obj) => obj.key);
|
||||||
|
const processedFiles = await processFiles(paths, id);
|
||||||
|
return processedFiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFolder = async (folderId: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?folderId=${folderId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const data: R2Files = await res.json();
|
||||||
|
|
||||||
|
return data.objects.map((obj) => obj.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const processFiles = async (paths: string[], id: string) => {
|
||||||
|
const root: TFolder = { id: "/", type: "folder", name: "/", children: [] };
|
||||||
|
const fileData: TFileData[] = [];
|
||||||
|
|
||||||
|
paths.forEach((path) => {
|
||||||
|
const allParts = path.split("/");
|
||||||
|
if (allParts[1] !== id) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async acquireLock<T>(key: string, task: () => Promise<T>): Promise<T> {
|
const parts = allParts.slice(2);
|
||||||
if (!this.locks[key]) {
|
let current: TFolder = root;
|
||||||
this.locks[key] = new Promise<T>(async (resolve, reject) => {
|
|
||||||
try {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
const result = await task();
|
const part = parts[i];
|
||||||
resolve(result);
|
const isFile = i === parts.length - 1 && part.includes(".");
|
||||||
} catch (error) {
|
const existing = current.children.find((child) => child.name === part);
|
||||||
reject(error);
|
|
||||||
} finally {
|
if (existing) {
|
||||||
delete this.locks[key];
|
if (!isFile) {
|
||||||
|
current = existing as TFolder;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isFile) {
|
||||||
|
const file: TFile = { id: path, type: "file", name: part };
|
||||||
|
current.children.push(file);
|
||||||
|
fileData.push({ id: path, data: "" });
|
||||||
|
} else {
|
||||||
|
const folder: TFolder = {
|
||||||
|
// id: path, // todo: wrong id. for example, folder "src" ID is: projects/a7vgttfqbgy403ratp7du3ln/src/App.css
|
||||||
|
id: `projects/${id}/${parts.slice(0, i + 1).join("/")}`,
|
||||||
|
type: "folder",
|
||||||
|
name: part,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
current.children.push(folder);
|
||||||
|
current = folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
fileData.map(async (file) => {
|
||||||
|
const data = await fetchFileContent(file.id);
|
||||||
|
file.data = data;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
files: root.children,
|
||||||
|
fileData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFileContent = async (fileId: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const fileRes = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?fileId=${fileId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return await this.locks[key];
|
);
|
||||||
|
return await fileRes.text();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("ERROR fetching file:", error);
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFile = async (fileId: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId }),
|
||||||
|
});
|
||||||
|
return res.ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renameFile = async (
|
||||||
|
fileId: string,
|
||||||
|
newFileId: string,
|
||||||
|
data: string
|
||||||
|
) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/rename`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId, newFileId, data }),
|
||||||
|
});
|
||||||
|
return res.ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveFile = async (fileId: string, data: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/save`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId, data }),
|
||||||
|
});
|
||||||
|
return res.ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFile = async (fileId: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId }),
|
||||||
|
});
|
||||||
|
return res.ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProjectSize = async (id: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api/size?sandboxId=${id}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
return (await res.json()).size;
|
||||||
|
};
|
||||||
|
32
backend/storage/package-lock.json
generated
32
backend/storage/package-lock.json
generated
@ -8,7 +8,6 @@
|
|||||||
"name": "storage",
|
"name": "storage",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"p-limit": "^6.1.0",
|
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.23.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -895,21 +894,6 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner/node_modules/p-limit": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"yocto-queue": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz",
|
||||||
@ -1782,11 +1766,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "6.1.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
|
||||||
"integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==",
|
"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yocto-queue": "^1.1.1"
|
"yocto-queue": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@ -2985,9 +2970,10 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "1.1.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||||
"integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==",
|
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20"
|
"node": ">=12.20"
|
||||||
},
|
},
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"p-limit": "^6.1.0",
|
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.23.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import pLimit from 'p-limit';
|
import startercode from "./startercode"
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
R2: R2Bucket
|
R2: R2Bucket
|
||||||
Templates: R2Bucket
|
|
||||||
KEY: string
|
KEY: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,26 +137,19 @@ export default {
|
|||||||
} else if (path === "/api/init" && method === "POST") {
|
} else if (path === "/api/init" && method === "POST") {
|
||||||
const initSchema = z.object({
|
const initSchema = z.object({
|
||||||
sandboxId: z.string(),
|
sandboxId: z.string(),
|
||||||
type: z.string(),
|
type: z.enum(["react", "node"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { sandboxId, type } = initSchema.parse(body)
|
const { sandboxId, type } = initSchema.parse(body)
|
||||||
|
|
||||||
console.log(`Copying template: ${type}`);
|
console.log(startercode[type])
|
||||||
|
|
||||||
// List all objects under the directory
|
await Promise.all(
|
||||||
const { objects } = await env.Templates.list({ prefix: type });
|
startercode[type].map(async (file) => {
|
||||||
|
await env.R2.put(`projects/${sandboxId}/${file.name}`, file.body)
|
||||||
// Copy each object to the new directory with a 5 concurrency limit
|
|
||||||
const limit = pLimit(5);
|
|
||||||
await Promise.all(objects.map(({ key }) =>
|
|
||||||
limit(async () => {
|
|
||||||
const destinationKey = key.replace(type, `projects/${sandboxId}`);
|
|
||||||
const fileBody = await env.Templates.get(key).then(res => res?.body ?? "");
|
|
||||||
await env.R2.put(destinationKey, fileBody);
|
|
||||||
})
|
})
|
||||||
));
|
)
|
||||||
|
|
||||||
return success
|
return success
|
||||||
} else {
|
} else {
|
||||||
|
151
backend/storage/src/startercode.ts
Normal file
151
backend/storage/src/startercode.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
const startercode = {
|
||||||
|
node: [
|
||||||
|
{ name: "index.js", body: `console.log("Hello World!")` },
|
||||||
|
{
|
||||||
|
name: "package.json",
|
||||||
|
body: `{
|
||||||
|
"name": "nodejs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^18.0.6"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
react: [
|
||||||
|
{
|
||||||
|
name: "package.json",
|
||||||
|
body: `{
|
||||||
|
"name": "react",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.66",
|
||||||
|
"@types/react-dom": "^18.2.22",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-plugin-react": "^7.34.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
|
"vite": "^5.2.0"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vite.config.js",
|
||||||
|
body: `import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
host: "0.0.0.0",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "index.html",
|
||||||
|
body: `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>React Starter Code</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "src/App.css",
|
||||||
|
body: `div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #777;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "src/App.jsx",
|
||||||
|
body: `import './App.css'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>React Starter Code</h1>
|
||||||
|
<p>
|
||||||
|
Edit App.jsx to get started.
|
||||||
|
</p>
|
||||||
|
<button onClick={() => setCount(count => count + 1)}>
|
||||||
|
Clicked {count} times
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "src/main.jsx",
|
||||||
|
body: `import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.jsx'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default startercode
|
41
dockerfile
Normal file
41
dockerfile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# docker build -t myimage .
|
||||||
|
# docker run --env-file backend/server/.env -p 3000:3000 -p 4000:4000 myimage
|
||||||
|
|
||||||
|
FROM node:20
|
||||||
|
|
||||||
|
# Security: Drop all capabilities
|
||||||
|
USER root
|
||||||
|
RUN apt-get update && apt-get install -y libcap2-bin
|
||||||
|
RUN setcap cap_net_bind_service=+ep /usr/local/bin/node
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
WORKDIR /backend
|
||||||
|
COPY backend/server/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY backend/server/ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
WORKDIR /frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY frontend .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Set working directory to the root directory
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
# Security: Create non-root user and assign ownership
|
||||||
|
RUN useradd -m appuser
|
||||||
|
RUN mkdir -p /backend/projects && chown -R appuser:appuser /backend/projects
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Start both backend and frontend
|
||||||
|
ENV BACKEND_PORT=4000
|
||||||
|
ENV FRONTEND_PORT=3000
|
||||||
|
|
||||||
|
EXPOSE 5173
|
||||||
|
EXPOSE $BACKEND_PORT
|
||||||
|
EXPOSE $FRONTEND_PORT
|
||||||
|
|
||||||
|
CMD ["sh", "-c", "cd /backend && PORT=$BACKEND_PORT npm run start & cd /frontend && PORT=$FRONTEND_PORT npm run start"]
|
@ -3,7 +3,7 @@ CLERK_SECRET_KEY=
|
|||||||
NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY=
|
NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY=
|
||||||
LIVEBLOCKS_SECRET_KEY=
|
LIVEBLOCKS_SECRET_KEY=
|
||||||
|
|
||||||
NEXT_PUBLIC_SERVER_URL=http://localhost:4000
|
NEXT_PUBLIC_SERVER_PORT=4000
|
||||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
|
|
||||||
# Set WORKER_URLs after deploying the workers.
|
# Set WORKER_URLs after deploying the workers.
|
||||||
|
@ -6,7 +6,6 @@ import { notFound, redirect } from "next/navigation"
|
|||||||
import Loading from "@/components/editor/loading"
|
import Loading from "@/components/editor/loading"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { TerminalProvider } from "@/context/TerminalContext"
|
|
||||||
|
|
||||||
export const revalidate = 0
|
export const revalidate = 0
|
||||||
|
|
||||||
@ -64,6 +63,14 @@ const CodeEditor = dynamic(() => import("@/components/editor"), {
|
|||||||
loading: () => <Loading />,
|
loading: () => <Loading />,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getReactDefinitionFile() {
|
||||||
|
const reactDefinitionFile = fs.readFileSync(
|
||||||
|
"node_modules/@types/react/index.d.ts",
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
return reactDefinitionFile
|
||||||
|
}
|
||||||
|
|
||||||
export default async function CodePage({ params }: { params: { id: string } }) {
|
export default async function CodePage({ params }: { params: { id: string } }) {
|
||||||
const user = await currentUser()
|
const user = await currentUser()
|
||||||
const sandboxId = params.id
|
const sandboxId = params.id
|
||||||
@ -87,21 +94,20 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reactDefinitionFile = getReactDefinitionFile()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
||||||
<Room id={sandboxId}>
|
<Room id={sandboxId}>
|
||||||
<TerminalProvider>
|
|
||||||
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
||||||
<div className="w-screen flex grow">
|
<div className="w-screen flex grow">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
userData={userData}
|
userData={userData}
|
||||||
sandboxData={sandboxData}
|
sandboxData={sandboxData}
|
||||||
|
reactDefinitionFile={reactDefinitionFile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TerminalProvider>
|
|
||||||
</Room>
|
</Room>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import { ThemeProvider } from "@/components/layout/themeProvider"
|
|||||||
import { ClerkProvider } from "@clerk/nextjs"
|
import { ClerkProvider } from "@clerk/nextjs"
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
import { PreviewProvider } from "@/context/PreviewContext";
|
|
||||||
import { SocketProvider } from '@/context/SocketContext'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
@ -15,7 +13,7 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}>) {
|
}>) {
|
||||||
@ -29,11 +27,7 @@ export default function RootLayout({
|
|||||||
forcedTheme="dark"
|
forcedTheme="dark"
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SocketProvider>
|
|
||||||
<PreviewProvider>
|
|
||||||
{children}
|
{children}
|
||||||
</PreviewProvider>
|
|
||||||
</SocketProvider>
|
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<Toaster position="bottom-left" richColors />
|
<Toaster position="bottom-left" richColors />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import posthog from "posthog-js"
|
|
||||||
import { PostHogProvider } from "posthog-js/react"
|
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
||||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PHProvider({ children }) {
|
|
||||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
|
||||||
}
|
|
@ -49,9 +49,11 @@ export default function Dashboard({
|
|||||||
const q = searchParams.get("q")
|
const q = searchParams.get("q")
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => { // update the dashboard to show a new project
|
useEffect(() => {
|
||||||
|
if (!sandboxes) {
|
||||||
router.refresh()
|
router.refresh()
|
||||||
}, [])
|
}
|
||||||
|
}, [sandboxes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -36,7 +36,45 @@ import { createSandbox } from "@/lib/actions"
|
|||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
import { projectTemplates } from "@/lib/data"
|
|
||||||
|
type TOptions = "react" | "node" | "python" | "more"
|
||||||
|
|
||||||
|
const data: {
|
||||||
|
id: TOptions
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
description: string
|
||||||
|
disabled: boolean
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
id: "react",
|
||||||
|
name: "React",
|
||||||
|
icon: "/project-icons/react.svg",
|
||||||
|
description: "A JavaScript library for building user interfaces",
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "node",
|
||||||
|
name: "Node",
|
||||||
|
icon: "/project-icons/node.svg",
|
||||||
|
description: "A JavaScript runtime built on the V8 JavaScript engine",
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "python",
|
||||||
|
name: "Python",
|
||||||
|
icon: "/project-icons/python.svg",
|
||||||
|
description: "A high-level, general-purpose language, coming soon",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "more",
|
||||||
|
name: "More Languages",
|
||||||
|
icon: "/project-icons/more.svg",
|
||||||
|
description: "More coming soon, feel free to contribute on GitHub",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
@ -57,7 +95,7 @@ export default function NewProjectModal({
|
|||||||
open: boolean
|
open: boolean
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const [selected, setSelected] = useState("reactjs")
|
const [selected, setSelected] = useState<TOptions>("react")
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -88,12 +126,12 @@ export default function NewProjectModal({
|
|||||||
if (!loading) setOpen(open)
|
if (!loading) setOpen(open)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="max-h-[95vh] overflow-y-auto">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create A Sandbox</DialogTitle>
|
<DialogTitle>Create A Sandbox</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid grid-cols-2 w-full gap-2 mt-2">
|
<div className="grid grid-cols-2 w-full gap-2 mt-2">
|
||||||
{projectTemplates.map((item) => (
|
{data.map((item) => (
|
||||||
<button
|
<button
|
||||||
disabled={item.disabled || loading}
|
disabled={item.disabled || loading}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
@ -8,7 +8,6 @@ import { Clock, Globe, Lock } from "lucide-react"
|
|||||||
import { Sandbox } from "@/lib/types"
|
import { Sandbox } from "@/lib/types"
|
||||||
import { Card } from "@/components/ui/card"
|
import { Card } from "@/components/ui/card"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { projectTemplates } from "@/lib/data"
|
|
||||||
|
|
||||||
export default function ProjectCard({
|
export default function ProjectCard({
|
||||||
children,
|
children,
|
||||||
@ -44,9 +43,7 @@ export default function ProjectCard({
|
|||||||
setDate(`${Math.floor(diffInMinutes / 1440)}d ago`)
|
setDate(`${Math.floor(diffInMinutes / 1440)}d ago`)
|
||||||
}
|
}
|
||||||
}, [sandbox])
|
}, [sandbox])
|
||||||
const projectIcon =
|
|
||||||
projectTemplates.find((p) => p.id === sandbox.type)?.icon ??
|
|
||||||
"/project-icons/node.svg"
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@ -68,7 +65,16 @@ export default function ProjectCard({
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
<div className="space-x-2 flex items-center justify-start w-full z-10">
|
<div className="space-x-2 flex items-center justify-start w-full z-10">
|
||||||
<Image alt="" src={projectIcon} width={20} height={20} />
|
<Image
|
||||||
|
alt=""
|
||||||
|
src={
|
||||||
|
sandbox.type === "react"
|
||||||
|
? "/project-icons/react.svg"
|
||||||
|
: "/project-icons/node.svg"
|
||||||
|
}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
<div className="font-medium static whitespace-nowrap w-full text-ellipsis overflow-hidden">
|
<div className="font-medium static whitespace-nowrap w-full text-ellipsis overflow-hidden">
|
||||||
{sandbox.name}
|
{sandbox.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@ import { toast } from "sonner";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { CanvasRevealEffect } from "./projectCard/revealEffect";
|
import { CanvasRevealEffect } from "./projectCard/revealEffect";
|
||||||
|
|
||||||
const colors: { [key: string]: number[][] } = {
|
const colors = {
|
||||||
react: [
|
react: [
|
||||||
[71, 207, 237],
|
[71, 207, 237],
|
||||||
[30, 126, 148],
|
[30, 126, 148],
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Button } from '../../ui/button';
|
|
||||||
import { Send, StopCircle } from 'lucide-react';
|
|
||||||
|
|
||||||
interface ChatInputProps {
|
|
||||||
input: string;
|
|
||||||
setInput: (input: string) => void;
|
|
||||||
isGenerating: boolean;
|
|
||||||
handleSend: () => void;
|
|
||||||
handleStopGeneration: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatInput({ input, setInput, isGenerating, handleSend, handleStopGeneration }: ChatInputProps) {
|
|
||||||
return (
|
|
||||||
<div className="flex space-x-2 min-w-0">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={input}
|
|
||||||
onChange={(e) => setInput(e.target.value)}
|
|
||||||
onKeyPress={(e) => e.key === 'Enter' && !isGenerating && handleSend()}
|
|
||||||
className="flex-grow p-2 border rounded-lg min-w-0 bg-input"
|
|
||||||
placeholder="Type your message..."
|
|
||||||
disabled={isGenerating}
|
|
||||||
/>
|
|
||||||
{isGenerating ? (
|
|
||||||
<Button onClick={handleStopGeneration} variant="destructive" size="icon" className="h-10 w-10">
|
|
||||||
<StopCircle className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button onClick={handleSend} disabled={isGenerating} size="icon" className="h-10 w-10">
|
|
||||||
<Send className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Button } from '../../ui/button';
|
|
||||||
import { ChevronUp, ChevronDown, Copy, Check, CornerUpLeft } from 'lucide-react';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
||||||
import remarkGfm from 'remark-gfm';
|
|
||||||
import { copyToClipboard, stringifyContent } from './lib/chatUtils';
|
|
||||||
|
|
||||||
interface MessageProps {
|
|
||||||
message: {
|
|
||||||
role: 'user' | 'assistant';
|
|
||||||
content: string;
|
|
||||||
context?: string;
|
|
||||||
};
|
|
||||||
setContext: (context: string | null) => void;
|
|
||||||
setIsContextExpanded: (isExpanded: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatMessage({ message, setContext, setIsContextExpanded }: MessageProps) {
|
|
||||||
const [expandedMessageIndex, setExpandedMessageIndex] = useState<number | null>(null);
|
|
||||||
const [copiedText, setCopiedText] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const renderCopyButton = (text: any) => (
|
|
||||||
<Button
|
|
||||||
onClick={() => copyToClipboard(stringifyContent(text), setCopiedText)}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="p-1 h-6"
|
|
||||||
>
|
|
||||||
{copiedText === stringifyContent(text) ? (
|
|
||||||
<Check className="w-4 h-4 text-green-500" />
|
|
||||||
) : (
|
|
||||||
<Copy className="w-4 h-4" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
const askAboutCode = (code: any) => {
|
|
||||||
const contextString = stringifyContent(code);
|
|
||||||
setContext(`Regarding this code:\n${contextString}`);
|
|
||||||
setIsContextExpanded(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderMarkdownElement = (props: any) => {
|
|
||||||
const { node, children } = props;
|
|
||||||
const content = stringifyContent(children);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute top-0 right-0 flex opacity-0 group-hover:opacity-30 transition-opacity">
|
|
||||||
{renderCopyButton(content)}
|
|
||||||
<Button
|
|
||||||
onClick={() => askAboutCode(content)}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="p-1 h-6"
|
|
||||||
>
|
|
||||||
<CornerUpLeft className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{React.createElement(node.tagName, {
|
|
||||||
...props,
|
|
||||||
className: `${props.className || ''} hover:bg-transparent rounded p-1 transition-colors`
|
|
||||||
}, children)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-left relative">
|
|
||||||
<div className={`relative p-2 rounded-lg ${
|
|
||||||
message.role === 'user'
|
|
||||||
? 'bg-[#262626] text-white'
|
|
||||||
: 'bg-transparent text-white'
|
|
||||||
} max-w-full`}>
|
|
||||||
{message.role === 'user' && (
|
|
||||||
<div className="absolute top-0 right-0 flex opacity-0 group-hover:opacity-30 transition-opacity">
|
|
||||||
{renderCopyButton(message.content)}
|
|
||||||
<Button
|
|
||||||
onClick={() => askAboutCode(message.content)}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="p-1 h-6"
|
|
||||||
>
|
|
||||||
<CornerUpLeft className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{message.context && (
|
|
||||||
<div className="mb-2 bg-input rounded-lg">
|
|
||||||
<div
|
|
||||||
className="flex justify-between items-center cursor-pointer"
|
|
||||||
onClick={() => setExpandedMessageIndex(expandedMessageIndex === 0 ? null : 0)}
|
|
||||||
>
|
|
||||||
<span className="text-sm text-gray-300">
|
|
||||||
Context
|
|
||||||
</span>
|
|
||||||
{expandedMessageIndex === 0 ? (
|
|
||||||
<ChevronUp size={16} />
|
|
||||||
) : (
|
|
||||||
<ChevronDown size={16} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{expandedMessageIndex === 0 && (
|
|
||||||
<div className="relative">
|
|
||||||
<div className="absolute top-0 right-0 flex p-1">
|
|
||||||
{renderCopyButton(message.context.replace(/^Regarding this code:\n/, ''))}
|
|
||||||
</div>
|
|
||||||
{(() => {
|
|
||||||
const code = message.context.replace(/^Regarding this code:\n/, '');
|
|
||||||
const match = /language-(\w+)/.exec(code);
|
|
||||||
const language = match ? match[1] : 'typescript';
|
|
||||||
return (
|
|
||||||
<div className="pt-6">
|
|
||||||
<textarea
|
|
||||||
value={code}
|
|
||||||
onChange={(e) => {
|
|
||||||
const updatedContext = `Regarding this code:\n${e.target.value}`;
|
|
||||||
setContext(updatedContext);
|
|
||||||
}}
|
|
||||||
className="w-full p-2 bg-[#1e1e1e] text-white font-mono text-sm rounded"
|
|
||||||
rows={code.split('\n').length}
|
|
||||||
style={{
|
|
||||||
resize: 'vertical',
|
|
||||||
minHeight: '100px',
|
|
||||||
maxHeight: '400px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{message.role === 'assistant' ? (
|
|
||||||
<ReactMarkdown
|
|
||||||
remarkPlugins={[remarkGfm]}
|
|
||||||
components={{
|
|
||||||
code({node, className, children, ...props}) {
|
|
||||||
const match = /language-(\w+)/.exec(className || '');
|
|
||||||
return match ? (
|
|
||||||
<div className="relative border border-input rounded-md my-4">
|
|
||||||
<div className="absolute top-0 left-0 px-2 py-1 text-xs font-semibold text-gray-200 bg-#1e1e1e rounded-tl">
|
|
||||||
{match[1]}
|
|
||||||
</div>
|
|
||||||
<div className="absolute top-0 right-0 flex">
|
|
||||||
{renderCopyButton(children)}
|
|
||||||
<Button
|
|
||||||
onClick={() => askAboutCode(children)}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="p-1 h-6"
|
|
||||||
>
|
|
||||||
<CornerUpLeft className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="pt-6">
|
|
||||||
<SyntaxHighlighter
|
|
||||||
style={vscDarkPlus as any}
|
|
||||||
language={match[1]}
|
|
||||||
PreTag="div"
|
|
||||||
customStyle={{
|
|
||||||
margin: 0,
|
|
||||||
padding: '0.5rem',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{stringifyContent(children)}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<code className={className} {...props}>
|
|
||||||
{children}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
p: renderMarkdownElement,
|
|
||||||
h1: renderMarkdownElement,
|
|
||||||
h2: renderMarkdownElement,
|
|
||||||
h3: renderMarkdownElement,
|
|
||||||
h4: renderMarkdownElement,
|
|
||||||
h5: renderMarkdownElement,
|
|
||||||
h6: renderMarkdownElement,
|
|
||||||
ul: (props) => <ul className="list-disc pl-6 mb-4 space-y-2">{props.children}</ul>,
|
|
||||||
ol: (props) => <ol className="list-decimal pl-6 mb-4 space-y-2">{props.children}</ol>,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message.content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
) : (
|
|
||||||
<div className="whitespace-pre-wrap group">
|
|
||||||
{message.content}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ChevronUp, ChevronDown, X } from 'lucide-react';
|
|
||||||
|
|
||||||
interface ContextDisplayProps {
|
|
||||||
context: string | null;
|
|
||||||
isContextExpanded: boolean;
|
|
||||||
setIsContextExpanded: (isExpanded: boolean) => void;
|
|
||||||
setContext: (context: string | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ContextDisplay({ context, isContextExpanded, setIsContextExpanded, setContext }: ContextDisplayProps) {
|
|
||||||
if (!context) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mb-2 bg-input p-2 rounded-lg">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div
|
|
||||||
className="flex-grow cursor-pointer"
|
|
||||||
onClick={() => setIsContextExpanded(!isContextExpanded)}
|
|
||||||
>
|
|
||||||
<span className="text-sm text-gray-300">
|
|
||||||
Context
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
{isContextExpanded ? (
|
|
||||||
<ChevronUp size={16} className="cursor-pointer" onClick={() => setIsContextExpanded(false)} />
|
|
||||||
) : (
|
|
||||||
<ChevronDown size={16} className="cursor-pointer" onClick={() => setIsContextExpanded(true)} />
|
|
||||||
)}
|
|
||||||
<X
|
|
||||||
size={16}
|
|
||||||
className="ml-2 cursor-pointer text-gray-400 hover:text-gray-200"
|
|
||||||
onClick={() => setContext(null)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isContextExpanded && (
|
|
||||||
<textarea
|
|
||||||
value={context.replace(/^Regarding this code:\n/, '')}
|
|
||||||
onChange={(e) => setContext(`Regarding this code:\n${e.target.value}`)}
|
|
||||||
className="w-full mt-2 p-2 bg-#1e1e1e text-white rounded"
|
|
||||||
rows={5}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
|
||||||
import LoadingDots from '../../ui/LoadingDots';
|
|
||||||
import ChatMessage from './ChatMessage';
|
|
||||||
import ChatInput from './ChatInput';
|
|
||||||
import ContextDisplay from './ContextDisplay';
|
|
||||||
import { handleSend, handleStopGeneration } from './lib/chatUtils';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
|
|
||||||
interface Message {
|
|
||||||
role: 'user' | 'assistant';
|
|
||||||
content: string;
|
|
||||||
context?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AIChat({ activeFileContent, activeFileName, onClose }: { activeFileContent: string, activeFileName: string, onClose: () => void }) {
|
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
|
||||||
const [input, setInput] = useState('');
|
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
|
||||||
const chatContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
|
||||||
const [context, setContext] = useState<string | null>(null);
|
|
||||||
const [isContextExpanded, setIsContextExpanded] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, [messages]);
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
if (chatContainerRef.current) {
|
|
||||||
setTimeout(() => {
|
|
||||||
chatContainerRef.current?.scrollTo({
|
|
||||||
top: chatContainerRef.current.scrollHeight,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-screen w-full">
|
|
||||||
<div className="flex justify-between items-center p-2 border-b">
|
|
||||||
<span className="text-muted-foreground/50 font-medium">CHAT</span>
|
|
||||||
<div className="flex items-center h-full">
|
|
||||||
<span className="text-muted-foreground/50 font-medium">{activeFileName}</span>
|
|
||||||
<div className="mx-2 h-full w-px bg-muted-foreground/20"></div>
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="text-muted-foreground/50 hover:text-muted-foreground focus:outline-none"
|
|
||||||
aria-label="Close AI Chat"
|
|
||||||
>
|
|
||||||
<X size={18} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ref={chatContainerRef} className="flex-grow overflow-y-auto p-4 space-y-4">
|
|
||||||
{messages.map((message, messageIndex) => (
|
|
||||||
<ChatMessage
|
|
||||||
key={messageIndex}
|
|
||||||
message={message}
|
|
||||||
setContext={setContext}
|
|
||||||
setIsContextExpanded={setIsContextExpanded}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{isLoading && <LoadingDots />}
|
|
||||||
</div>
|
|
||||||
<div className="p-4 border-t mb-14">
|
|
||||||
<ContextDisplay
|
|
||||||
context={context}
|
|
||||||
isContextExpanded={isContextExpanded}
|
|
||||||
setIsContextExpanded={setIsContextExpanded}
|
|
||||||
setContext={setContext}
|
|
||||||
/>
|
|
||||||
<ChatInput
|
|
||||||
input={input}
|
|
||||||
setInput={setInput}
|
|
||||||
isGenerating={isGenerating}
|
|
||||||
handleSend={() => handleSend(input, context, messages, setMessages, setInput, setIsContextExpanded, setIsGenerating, setIsLoading, abortControllerRef, activeFileContent)}
|
|
||||||
handleStopGeneration={() => handleStopGeneration(abortControllerRef)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export const stringifyContent = (content: any, seen = new WeakSet()): string => {
|
|
||||||
if (typeof content === 'string') {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
if (content === null) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
if (content === undefined) {
|
|
||||||
return 'undefined';
|
|
||||||
}
|
|
||||||
if (typeof content === 'number' || typeof content === 'boolean') {
|
|
||||||
return content.toString();
|
|
||||||
}
|
|
||||||
if (typeof content === 'function') {
|
|
||||||
return content.toString();
|
|
||||||
}
|
|
||||||
if (typeof content === 'symbol') {
|
|
||||||
return content.toString();
|
|
||||||
}
|
|
||||||
if (typeof content === 'bigint') {
|
|
||||||
return content.toString() + 'n';
|
|
||||||
}
|
|
||||||
if (React.isValidElement(content)) {
|
|
||||||
return React.Children.toArray((content as React.ReactElement).props.children)
|
|
||||||
.map(child => stringifyContent(child, seen))
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
if (Array.isArray(content)) {
|
|
||||||
return '[' + content.map(item => stringifyContent(item, seen)).join(', ') + ']';
|
|
||||||
}
|
|
||||||
if (typeof content === 'object') {
|
|
||||||
if (seen.has(content)) {
|
|
||||||
return '[Circular]';
|
|
||||||
}
|
|
||||||
seen.add(content);
|
|
||||||
try {
|
|
||||||
const pairs = Object.entries(content).map(
|
|
||||||
([key, value]) => `${key}: ${stringifyContent(value, seen)}`
|
|
||||||
);
|
|
||||||
return '{' + pairs.join(', ') + '}';
|
|
||||||
} catch (error) {
|
|
||||||
return Object.prototype.toString.call(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return String(content);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const copyToClipboard = (text: string, setCopiedText: (text: string | null) => void) => {
|
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
|
||||||
setCopiedText(text);
|
|
||||||
setTimeout(() => setCopiedText(null), 2000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleSend = async (
|
|
||||||
input: string,
|
|
||||||
context: string | null,
|
|
||||||
messages: any[],
|
|
||||||
setMessages: React.Dispatch<React.SetStateAction<any[]>>,
|
|
||||||
setInput: React.Dispatch<React.SetStateAction<string>>,
|
|
||||||
setIsContextExpanded: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>,
|
|
||||||
abortControllerRef: React.MutableRefObject<AbortController | null>,
|
|
||||||
activeFileContent: string
|
|
||||||
) => {
|
|
||||||
if (input.trim() === '' && !context) return;
|
|
||||||
|
|
||||||
const newMessage = {
|
|
||||||
role: 'user' as const,
|
|
||||||
content: input,
|
|
||||||
context: context || undefined
|
|
||||||
};
|
|
||||||
const updatedMessages = [...messages, newMessage];
|
|
||||||
setMessages(updatedMessages);
|
|
||||||
setInput('');
|
|
||||||
setIsContextExpanded(false);
|
|
||||||
setIsGenerating(true);
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
abortControllerRef.current = new AbortController();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const anthropicMessages = updatedMessages.map(msg => ({
|
|
||||||
role: msg.role === 'user' ? 'human' : 'assistant',
|
|
||||||
content: msg.content
|
|
||||||
}));
|
|
||||||
|
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
messages: anthropicMessages,
|
|
||||||
context: context || undefined,
|
|
||||||
activeFileContent: activeFileContent,
|
|
||||||
}),
|
|
||||||
signal: abortControllerRef.current.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to get AI response');
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const assistantMessage = { role: 'assistant' as const, content: '' };
|
|
||||||
setMessages([...updatedMessages, assistantMessage]);
|
|
||||||
setIsLoading(false);
|
|
||||||
|
|
||||||
let buffer = '';
|
|
||||||
const updateInterval = 100;
|
|
||||||
let lastUpdateTime = Date.now();
|
|
||||||
|
|
||||||
if (reader) {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
|
|
||||||
const currentTime = Date.now();
|
|
||||||
if (currentTime - lastUpdateTime > updateInterval) {
|
|
||||||
setMessages(prev => {
|
|
||||||
const updatedMessages = [...prev];
|
|
||||||
const lastMessage = updatedMessages[updatedMessages.length - 1];
|
|
||||||
lastMessage.content = buffer;
|
|
||||||
return updatedMessages;
|
|
||||||
});
|
|
||||||
lastUpdateTime = currentTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessages(prev => {
|
|
||||||
const updatedMessages = [...prev];
|
|
||||||
const lastMessage = updatedMessages[updatedMessages.length - 1];
|
|
||||||
lastMessage.content = buffer;
|
|
||||||
return updatedMessages;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.name === 'AbortError') {
|
|
||||||
console.log('Generation aborted');
|
|
||||||
} else {
|
|
||||||
console.error('Error fetching AI response:', error);
|
|
||||||
const errorMessage = { role: 'assistant' as const, content: 'Sorry, I encountered an error. Please try again.' };
|
|
||||||
setMessages(prev => [...prev, errorMessage]);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsGenerating(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
abortControllerRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleStopGeneration = (abortControllerRef: React.MutableRefObject<AbortController | null>) => {
|
|
||||||
if (abortControllerRef.current) {
|
|
||||||
abortControllerRef.current.abort();
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react"
|
import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react"
|
||||||
import { Socket } from "socket.io-client"
|
import { Socket } from "socket.io-client"
|
||||||
@ -59,7 +59,7 @@ export default function GenerateInput({
|
|||||||
}: {
|
}: {
|
||||||
regenerate?: boolean
|
regenerate?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
if (user.generations >= 1000) {
|
if (user.generations >= 10) {
|
||||||
toast.error("You reached the maximum # of generations.")
|
toast.error("You reached the maximum # of generations.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -84,13 +84,6 @@ export default function GenerateInput({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const handleGenerateForm = useCallback(
|
|
||||||
(e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault()
|
|
||||||
handleGenerate({ regenerate: false })
|
|
||||||
},
|
|
||||||
[input, currentPrompt]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (code) {
|
if (code) {
|
||||||
@ -100,23 +93,9 @@ export default function GenerateInput({
|
|||||||
}
|
}
|
||||||
}, [code])
|
}, [code])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//listen to when Esc key is pressed and close the modal
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.addEventListener("keydown", handleKeyDown)
|
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full pr-4 space-y-2">
|
<div className="w-full pr-4 space-y-2">
|
||||||
<form
|
<div className="flex items-center font-sans space-x-2">
|
||||||
onSubmit={handleGenerateForm}
|
|
||||||
className="flex items-center font-sans space-x-2"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{
|
style={{
|
||||||
@ -130,8 +109,8 @@ export default function GenerateInput({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
type="submit"
|
|
||||||
disabled={loading.generate || loading.regenerate || input === ""}
|
disabled={loading.generate || loading.regenerate || input === ""}
|
||||||
|
onClick={() => handleGenerate({})}
|
||||||
>
|
>
|
||||||
{loading.generate ? (
|
{loading.generate ? (
|
||||||
<>
|
<>
|
||||||
@ -147,14 +126,13 @@ export default function GenerateInput({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
type="button"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="smIcon"
|
size="smIcon"
|
||||||
className="bg-transparent shrink-0 border-muted-foreground"
|
className="bg-transparent shrink-0 border-muted-foreground"
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</div>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<>
|
<>
|
||||||
<div className="rounded-md border border-muted-foreground w-full h-28 overflow-y-scroll p-2">
|
<div className="rounded-md border border-muted-foreground w-full h-28 overflow-y-scroll p-2">
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,78 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
|
||||||
import { Play, Pause, Globe, Globe2 } from "lucide-react";
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
||||||
import { Sandbox, User } from "@/lib/types";
|
|
||||||
|
|
||||||
export default function DeployButtonModal({
|
|
||||||
userData,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
userData: User;
|
|
||||||
data: Sandbox;
|
|
||||||
}) {
|
|
||||||
const { deploy } = useTerminal();
|
|
||||||
const [isDeploying, setIsDeploying] = useState(false);
|
|
||||||
|
|
||||||
const handleDeploy = () => {
|
|
||||||
if (isDeploying) {
|
|
||||||
console.log("Stopping deployment...");
|
|
||||||
setIsDeploying(false);
|
|
||||||
} else {
|
|
||||||
console.log("Starting deployment...");
|
|
||||||
setIsDeploying(true);
|
|
||||||
deploy(() => {
|
|
||||||
setIsDeploying(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline">
|
|
||||||
<Globe className="w-4 h-4 mr-2" />
|
|
||||||
Deploy
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="p-4 w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl rounded-lg shadow-lg" style={{ backgroundColor: 'rgb(10,10,10)', color: 'white' }}>
|
|
||||||
<h3 className="font-semibold text-gray-300 mb-2">Domains</h3>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<DeploymentOption
|
|
||||||
icon={<Globe className="text-gray-500 w-5 h-5" />}
|
|
||||||
domain={`${data.id}.gitwit.app`}
|
|
||||||
timestamp="Deployed 1h ago"
|
|
||||||
user={userData.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" className="mt-4 w-full bg-[#0a0a0a] text-white hover:bg-[#262626]" onClick={handleDeploy}>
|
|
||||||
{isDeploying ? "Deploying..." : "Update"}
|
|
||||||
</Button>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DeploymentOption({ icon, domain, timestamp, user }: { icon: React.ReactNode; domain: string; timestamp: string; user: string }) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-2 w-full text-left p-2 rounded-md border border-gray-700 bg-gray-900">
|
|
||||||
<div className="flex items-start gap-2 relative">
|
|
||||||
<div className="flex-shrink-0">{icon}</div>
|
|
||||||
<a
|
|
||||||
href={`https://${domain}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="font-semibold text-gray-300 hover:underline"
|
|
||||||
>
|
|
||||||
{domain}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-gray-400 mt-0 ml-7">{timestamp} • {user}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -11,8 +11,6 @@ import { useState } from "react";
|
|||||||
import EditSandboxModal from "./edit";
|
import EditSandboxModal from "./edit";
|
||||||
import ShareSandboxModal from "./share";
|
import ShareSandboxModal from "./share";
|
||||||
import { Avatars } from "../live/avatars";
|
import { Avatars } from "../live/avatars";
|
||||||
import RunButtonModal from "./run";
|
|
||||||
import DeployButtonModal from "./deploy";
|
|
||||||
|
|
||||||
export default function Navbar({
|
export default function Navbar({
|
||||||
userData,
|
userData,
|
||||||
@ -21,13 +19,15 @@ export default function Navbar({
|
|||||||
}: {
|
}: {
|
||||||
userData: User;
|
userData: User;
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
shared: { id: string; name: string }[];
|
shared: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
}) {
|
}) {
|
||||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||||
const [isShareOpen, setIsShareOpen] = useState(false);
|
const [isShareOpen, setIsShareOpen] = useState(false);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
|
||||||
|
|
||||||
const isOwner = sandboxData.userId === userData.id;;
|
const isOwner = sandboxData.userId === userData.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -62,25 +62,14 @@ export default function Navbar({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<RunButtonModal
|
|
||||||
isRunning={isRunning}
|
|
||||||
setIsRunning={setIsRunning}
|
|
||||||
sandboxData={sandboxData}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center h-full space-x-4">
|
<div className="flex items-center h-full space-x-4">
|
||||||
<Avatars />
|
<Avatars />
|
||||||
|
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<>
|
|
||||||
<DeployButtonModal
|
|
||||||
data={sandboxData}
|
|
||||||
userData={userData}
|
|
||||||
/>
|
|
||||||
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
||||||
<Users className="w-4 h-4 mr-2" />
|
<Users className="w-4 h-4 mr-2" />
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
|
||||||
) : null}
|
) : null}
|
||||||
<UserButton userData={userData} />
|
<UserButton userData={userData} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
|
||||||
import { Play, StopCircle } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
|
||||||
import { usePreview } from "@/context/PreviewContext";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Sandbox } from "@/lib/types";
|
|
||||||
|
|
||||||
export default function RunButtonModal({
|
|
||||||
isRunning,
|
|
||||||
setIsRunning,
|
|
||||||
sandboxData,
|
|
||||||
}: {
|
|
||||||
isRunning: boolean;
|
|
||||||
setIsRunning: (running: boolean) => void;
|
|
||||||
sandboxData: Sandbox;
|
|
||||||
}) {
|
|
||||||
const { createNewTerminal, closeTerminal, terminals } = useTerminal();
|
|
||||||
const { setIsPreviewCollapsed, previewPanelRef } = usePreview();
|
|
||||||
// Ref to keep track of the last created terminal's ID
|
|
||||||
const lastCreatedTerminalRef = useRef<string | null>(null);
|
|
||||||
|
|
||||||
// Effect to update the lastCreatedTerminalRef when a new terminal is added
|
|
||||||
useEffect(() => {
|
|
||||||
if (terminals.length > 0 && !isRunning) {
|
|
||||||
const latestTerminal = terminals[terminals.length - 1];
|
|
||||||
if (latestTerminal && latestTerminal.id !== lastCreatedTerminalRef.current) {
|
|
||||||
lastCreatedTerminalRef.current = latestTerminal.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [terminals, isRunning]);
|
|
||||||
|
|
||||||
const handleRun = async () => {
|
|
||||||
if (isRunning && lastCreatedTerminalRef.current)
|
|
||||||
{
|
|
||||||
await closeTerminal(lastCreatedTerminalRef.current);
|
|
||||||
lastCreatedTerminalRef.current = null;
|
|
||||||
setIsPreviewCollapsed(true);
|
|
||||||
previewPanelRef.current?.collapse();
|
|
||||||
}
|
|
||||||
else if (!isRunning && terminals.length < 4)
|
|
||||||
{
|
|
||||||
const command = sandboxData.type === "streamlit"
|
|
||||||
? "pip install -r requirements.txt && streamlit run main.py --server.runOnSave true"
|
|
||||||
: "yarn install && yarn dev";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a new terminal with the appropriate command
|
|
||||||
await createNewTerminal(command);
|
|
||||||
setIsPreviewCollapsed(false);
|
|
||||||
previewPanelRef.current?.expand();
|
|
||||||
} catch (error) {
|
|
||||||
toast.error("Failed to create new terminal.");
|
|
||||||
console.error("Error creating new terminal:", error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (!isRunning) {
|
|
||||||
toast.error("You've reached the maximum number of terminals.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsRunning(!isRunning);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button variant="outline" onClick={handleRun}>
|
|
||||||
{isRunning ? <StopCircle className="w-4 h-4 mr-2" /> : <Play className="w-4 h-4 mr-2" />}
|
|
||||||
{isRunning ? 'Stop' : 'Run'}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,38 +1,34 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Globe,
|
||||||
Link,
|
Link,
|
||||||
RotateCw,
|
RotateCw,
|
||||||
TerminalSquare,
|
TerminalSquare,
|
||||||
UnfoldVertical,
|
UnfoldVertical,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from "react"
|
import { useRef, useState } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export default forwardRef(function PreviewWindow({
|
export default function PreviewWindow({
|
||||||
collapsed,
|
collapsed,
|
||||||
open,
|
open,
|
||||||
src
|
|
||||||
}: {
|
}: {
|
||||||
collapsed: boolean
|
collapsed: boolean
|
||||||
open: () => void
|
open: () => void
|
||||||
src: string
|
}) {
|
||||||
},
|
const ref = useRef<HTMLIFrameElement>(null)
|
||||||
ref: React.Ref<{
|
|
||||||
refreshIframe: () => void
|
|
||||||
}>) {
|
|
||||||
const frameRef = useRef<HTMLIFrameElement>(null)
|
|
||||||
const [iframeKey, setIframeKey] = useState(0)
|
const [iframeKey, setIframeKey] = useState(0)
|
||||||
const refreshIframe = () => {
|
|
||||||
setIframeKey(prev => prev + 1)
|
|
||||||
}
|
|
||||||
// Refresh the preview when the URL changes.
|
|
||||||
useEffect(refreshIframe, [src])
|
|
||||||
// Expose refreshIframe method to the parent.
|
|
||||||
useImperativeHandle(ref, () => ({ refreshIframe }))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
collapsed ? "h-full" : "h-10"
|
||||||
|
} select-none w-full flex gap-2`}
|
||||||
|
>
|
||||||
<div className="h-8 rounded-md px-3 bg-secondary flex items-center w-full justify-between">
|
<div className="h-8 rounded-md px-3 bg-secondary flex items-center w-full justify-between">
|
||||||
<div className="text-xs">Preview</div>
|
<div className="text-xs">Preview</div>
|
||||||
<div className="flex space-x-1 translate-x-1">
|
<div className="flex space-x-1 translate-x-1">
|
||||||
@ -42,28 +38,48 @@ ref: React.Ref<{
|
|||||||
</PreviewButton>
|
</PreviewButton>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<PreviewButton onClick={open}>
|
{/* Todo, make this open inspector */}
|
||||||
<UnfoldVertical className="w-4 h-4" />
|
{/* <PreviewButton disabled onClick={() => {}}>
|
||||||
</PreviewButton>
|
<TerminalSquare className="w-4 h-4" />
|
||||||
|
</PreviewButton> */}
|
||||||
|
|
||||||
<PreviewButton
|
<PreviewButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(src)
|
navigator.clipboard.writeText(`http://localhost:5173`)
|
||||||
toast.info("Copied preview link to clipboard")
|
toast.info("Copied preview link to clipboard")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link className="w-4 h-4" />
|
<Link className="w-4 h-4" />
|
||||||
</PreviewButton>
|
</PreviewButton>
|
||||||
<PreviewButton onClick={refreshIframe}>
|
<PreviewButton
|
||||||
|
onClick={() => {
|
||||||
|
// if (ref.current) {
|
||||||
|
// ref.current.contentWindow?.location.reload();
|
||||||
|
// }
|
||||||
|
setIframeKey((prev) => prev + 1)
|
||||||
|
}}
|
||||||
|
>
|
||||||
<RotateCw className="w-3 h-3" />
|
<RotateCw className="w-3 h-3" />
|
||||||
</PreviewButton>
|
</PreviewButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{collapsed ? null : (
|
||||||
|
<div className="w-full grow rounded-md overflow-hidden bg-foreground">
|
||||||
|
<iframe
|
||||||
|
key={iframeKey}
|
||||||
|
ref={ref}
|
||||||
|
width={"100%"}
|
||||||
|
height={"100%"}
|
||||||
|
src={`http://localhost:5173`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
function PreviewButton({
|
function PreviewButton({
|
||||||
children,
|
children,
|
||||||
@ -76,7 +92,8 @@ function PreviewButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${disabled ? "pointer-events-none opacity-50" : ""
|
className={`${
|
||||||
|
disabled ? "pointer-events-none opacity-50" : ""
|
||||||
} p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm`}
|
} p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
|
@ -90,9 +90,9 @@ export default function SidebarFile({
|
|||||||
if (!editing && !pendingDelete && !isMoving)
|
if (!editing && !pendingDelete && !isMoving)
|
||||||
selectFile({ ...data, saved: true });
|
selectFile({ ...data, saved: true });
|
||||||
}}
|
}}
|
||||||
onDoubleClick={() => {
|
// onDoubleClick={() => {
|
||||||
setEditing(true)
|
// setEditing(true)
|
||||||
}}
|
// }}
|
||||||
className={`${
|
className={`${
|
||||||
dragging ? "opacity-50 hover:!bg-background" : ""
|
dragging ? "opacity-50 hover:!bg-background" : ""
|
||||||
} data-[state=open]:bg-secondary/50 w-full flex items-center h-7 px-1 hover:bg-secondary rounded-sm cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring`}
|
} data-[state=open]:bg-secondary/50 w-full flex items-center h-7 px-1 hover:bg-secondary rounded-sm cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring`}
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image"
|
import Image from "next/image";
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js"
|
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js";
|
||||||
import { TFile, TFolder, TTab } from "@/lib/types"
|
import { TFile, TFolder, TTab } from "@/lib/types";
|
||||||
import SidebarFile from "./file"
|
import SidebarFile from "./file";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from "@/components/ui/context-menu"
|
} from "@/components/ui/context-menu";
|
||||||
import { ChevronRight, Loader2, Pencil, Trash2 } from "lucide-react"
|
import { Loader2, Pencil, Trash2 } from "lucide-react";
|
||||||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"
|
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { motion, AnimatePresence } from "framer-motion"
|
|
||||||
|
|
||||||
// Note: Renaming has not been implemented in the backend yet, so UI relating to renaming is commented out
|
// Note: Renaming has not been implemented in the backend yet, so UI relating to renaming is commented out
|
||||||
|
|
||||||
@ -27,27 +25,27 @@ export default function SidebarFolder({
|
|||||||
movingId,
|
movingId,
|
||||||
deletingFolderId,
|
deletingFolderId,
|
||||||
}: {
|
}: {
|
||||||
data: TFolder
|
data: TFolder;
|
||||||
selectFile: (file: TTab) => void
|
selectFile: (file: TTab) => void;
|
||||||
handleRename: (
|
handleRename: (
|
||||||
id: string,
|
id: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
oldName: string,
|
oldName: string,
|
||||||
type: "file" | "folder"
|
type: "file" | "folder"
|
||||||
) => boolean
|
) => boolean;
|
||||||
handleDeleteFile: (file: TFile) => void
|
handleDeleteFile: (file: TFile) => void;
|
||||||
handleDeleteFolder: (folder: TFolder) => void
|
handleDeleteFolder: (folder: TFolder) => void;
|
||||||
movingId: string
|
movingId: string;
|
||||||
deletingFolderId: string
|
deletingFolderId: string;
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef(null) // drop target
|
const ref = useRef(null); // drop target
|
||||||
const [isDraggedOver, setIsDraggedOver] = useState(false)
|
const [isDraggedOver, setIsDraggedOver] = useState(false);
|
||||||
|
|
||||||
const isDeleting =
|
const isDeleting =
|
||||||
deletingFolderId.length > 0 && data.id.startsWith(deletingFolderId)
|
deletingFolderId.length > 0 && data.id.startsWith(deletingFolderId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = ref.current
|
const el = ref.current;
|
||||||
|
|
||||||
if (el)
|
if (el)
|
||||||
return dropTargetForElements({
|
return dropTargetForElements({
|
||||||
@ -69,17 +67,17 @@ export default function SidebarFolder({
|
|||||||
|
|
||||||
// no dropping while awaiting move
|
// no dropping while awaiting move
|
||||||
canDrop: () => {
|
canDrop: () => {
|
||||||
return !movingId
|
return !movingId;
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const folder = isOpen
|
const folder = isOpen
|
||||||
? getIconForOpenFolder(data.name)
|
? getIconForOpenFolder(data.name)
|
||||||
: getIconForFolder(data.name)
|
: getIconForFolder(data.name);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
// const [editing, setEditing] = useState(false);
|
// const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@ -98,12 +96,6 @@ export default function SidebarFolder({
|
|||||||
isDraggedOver ? "bg-secondary/50 rounded-t-sm" : "rounded-sm"
|
isDraggedOver ? "bg-secondary/50 rounded-t-sm" : "rounded-sm"
|
||||||
} w-full flex items-center h-7 px-1 transition-colors hover:bg-secondary cursor-pointer`}
|
} w-full flex items-center h-7 px-1 transition-colors hover:bg-secondary cursor-pointer`}
|
||||||
>
|
>
|
||||||
<ChevronRight
|
|
||||||
className={cn(
|
|
||||||
"min-w-3 min-h-3 mr-1 ml-auto transition-all duration-300",
|
|
||||||
isOpen ? "transform rotate-90" : ""
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Image
|
<Image
|
||||||
src={`/icons/${folder}`}
|
src={`/icons/${folder}`}
|
||||||
alt="Folder icon"
|
alt="Folder icon"
|
||||||
@ -157,36 +149,21 @@ export default function SidebarFolder({
|
|||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteFolder(data)
|
handleDeleteFolder(data);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
Delete
|
Delete
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
<AnimatePresence>
|
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<motion.div
|
|
||||||
className="overflow-y-hidden"
|
|
||||||
initial={{
|
|
||||||
height: 0,
|
|
||||||
opacity: 0,
|
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
height: "auto",
|
|
||||||
opacity: 1,
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
height: 0,
|
|
||||||
opacity: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={`flex w-full items-stretch ${
|
||||||
isDraggedOver ? "rounded-b-sm bg-secondary/50" : ""
|
isDraggedOver ? "rounded-b-sm bg-secondary/50" : ""
|
||||||
)}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col grow ml-2 pl-2 border-l border-border">
|
<div className="w-[1px] bg-border mx-2 h-full"></div>
|
||||||
|
<div className="flex flex-col grow">
|
||||||
{data.children.map((child) =>
|
{data.children.map((child) =>
|
||||||
child.type === "file" ? (
|
child.type === "file" ? (
|
||||||
<SidebarFile
|
<SidebarFile
|
||||||
@ -213,9 +190,7 @@ export default function SidebarFolder({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</AnimatePresence>
|
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,9 @@ import {
|
|||||||
FilePlus,
|
FilePlus,
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
MonitorPlay,
|
||||||
|
Search,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
MessageSquareMore,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import SidebarFile from "./file";
|
import SidebarFile from "./file";
|
||||||
import SidebarFolder from "./folder";
|
import SidebarFolder from "./folder";
|
||||||
@ -13,12 +14,13 @@ import { Sandbox, TFile, TFolder, TTab } from "@/lib/types";
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import New from "./new";
|
import New from "./new";
|
||||||
import { Socket } from "socket.io-client";
|
import { Socket } from "socket.io-client";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dropTargetForElements,
|
dropTargetForElements,
|
||||||
monitorForElements,
|
monitorForElements,
|
||||||
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
|
import Button from "@/components/ui/customButton";
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
sandboxData,
|
sandboxData,
|
||||||
@ -30,6 +32,8 @@ export default function Sidebar({
|
|||||||
socket,
|
socket,
|
||||||
setFiles,
|
setFiles,
|
||||||
addNew,
|
addNew,
|
||||||
|
ai,
|
||||||
|
setAi,
|
||||||
deletingFolderId,
|
deletingFolderId,
|
||||||
}: {
|
}: {
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
@ -46,6 +50,8 @@ export default function Sidebar({
|
|||||||
socket: Socket;
|
socket: Socket;
|
||||||
setFiles: (files: (TFile | TFolder)[]) => void;
|
setFiles: (files: (TFile | TFolder)[]) => void;
|
||||||
addNew: (name: string, type: "file" | "folder") => void;
|
addNew: (name: string, type: "file" | "folder") => void;
|
||||||
|
ai: boolean;
|
||||||
|
setAi: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
deletingFolderId: string;
|
deletingFolderId: string;
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef(null); // drop target
|
const ref = useRef(null); // drop target
|
||||||
@ -103,8 +109,8 @@ export default function Sidebar({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-56 select-none flex flex-col text-sm">
|
<div className="h-full w-56 select-none flex flex-col text-sm items-start justify-between p-2">
|
||||||
<div className="flex-grow overflow-auto p-2 pb-[84px]">
|
<div className="w-full flex flex-col items-start">
|
||||||
<div className="flex w-full items-center justify-between h-8 mb-1 ">
|
<div className="flex w-full items-center justify-between h-8 mb-1 ">
|
||||||
<div className="text-muted-foreground">Explorer</div>
|
<div className="text-muted-foreground">Explorer</div>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
@ -179,25 +185,24 @@ export default function Sidebar({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="fixed bottom-0 w-48 flex flex-col p-2 bg-background">
|
<div className="w-full space-y-4">
|
||||||
<Button variant="ghost" className="w-full justify-start text-sm text-muted-foreground font-normal h-8 px-2 mb-2" disabled aria-disabled="true" style={{ opacity: 1}}>
|
<div className="flex items-center justify-between w-full">
|
||||||
<Sparkles className="h-4 w-4 mr-2 text-indigo-500 opacity-70" />
|
<div className="flex items-center">
|
||||||
Copilot
|
<Sparkles
|
||||||
<div className="ml-auto">
|
className={`h-4 w-4 mr-2 ${
|
||||||
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
|
ai ? "text-indigo-500" : "text-muted-foreground"
|
||||||
<span className="text-xs">⌘</span>G
|
}`}
|
||||||
</kbd>
|
/>
|
||||||
|
Copilot{" "}
|
||||||
|
<span className="font-mono text-muted-foreground inline-block ml-1.5 text-xs leading-none border border-b-2 border-muted-foreground py-1 px-1.5 rounded-md">
|
||||||
|
⌘G
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
<Switch checked={ai} onCheckedChange={setAi} />
|
||||||
<Button variant="ghost" className="w-full justify-start text-sm text-muted-foreground font-normal h-8 px-2 mb-2" disabled aria-disabled="true" style={{ opacity: 1 }}>
|
|
||||||
<MessageSquareMore className="h-4 w-4 mr-2 text-indigo-500 opacity-70" />
|
|
||||||
AI Chat
|
|
||||||
<div className="ml-auto">
|
|
||||||
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
|
|
||||||
<span className="text-xs">⌘</span>L
|
|
||||||
</kbd>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
{/* <Button className="w-full">
|
||||||
|
<MonitorPlay className="w-4 h-4 mr-2" /> Run
|
||||||
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,45 +2,35 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Tab from "@/components/ui/tab";
|
import Tab from "@/components/ui/tab";
|
||||||
|
import { closeTerminal, createTerminal } from "@/lib/terminal";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react";
|
import { Loader2, Plus, SquareTerminal, TerminalSquare } from "lucide-react";
|
||||||
|
import { Socket } from "socket.io-client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import EditorTerminal from "./terminal";
|
import EditorTerminal from "./terminal";
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
import { useState } from "react";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useSocket } from "@/context/SocketContext"
|
|
||||||
|
|
||||||
export default function Terminals() {
|
export default function Terminals({
|
||||||
|
|
||||||
const { socket } = useSocket();
|
|
||||||
|
|
||||||
const {
|
|
||||||
terminals,
|
terminals,
|
||||||
setTerminals,
|
setTerminals,
|
||||||
createNewTerminal,
|
socket,
|
||||||
closeTerminal,
|
}: {
|
||||||
activeTerminalId,
|
terminals: { id: string; terminal: Terminal | null }[];
|
||||||
setActiveTerminalId,
|
setTerminals: React.Dispatch<
|
||||||
creatingTerminal,
|
React.SetStateAction<
|
||||||
} = useTerminal();
|
{
|
||||||
|
id: string;
|
||||||
|
terminal: Terminal | null;
|
||||||
|
}[]
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
socket: Socket;
|
||||||
|
}) {
|
||||||
|
const [activeTerminalId, setActiveTerminalId] = useState("");
|
||||||
|
const [creatingTerminal, setCreatingTerminal] = useState(false);
|
||||||
|
const [closingTerminal, setClosingTerminal] = useState("");
|
||||||
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
|
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
|
||||||
|
|
||||||
// Effect to set the active terminal when a new one is created
|
|
||||||
useEffect(() => {
|
|
||||||
if (terminals.length > 0 && !activeTerminalId) {
|
|
||||||
setActiveTerminalId(terminals[terminals.length - 1].id);
|
|
||||||
}
|
|
||||||
}, [terminals, activeTerminalId, setActiveTerminalId]);
|
|
||||||
|
|
||||||
const handleCreateTerminal = () => {
|
|
||||||
if (terminals.length >= 4) {
|
|
||||||
toast.error("You reached the maximum # of terminals.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createNewTerminal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-10 w-full overflow-auto flex gap-2 shrink-0 tab-scroll">
|
<div className="h-10 w-full overflow-auto flex gap-2 shrink-0 tab-scroll">
|
||||||
@ -49,7 +39,18 @@ export default function Terminals() {
|
|||||||
key={term.id}
|
key={term.id}
|
||||||
creating={creatingTerminal}
|
creating={creatingTerminal}
|
||||||
onClick={() => setActiveTerminalId(term.id)}
|
onClick={() => setActiveTerminalId(term.id)}
|
||||||
onClose={() => closeTerminal(term.id)}
|
onClose={() =>
|
||||||
|
closeTerminal({
|
||||||
|
term,
|
||||||
|
terminals,
|
||||||
|
setTerminals,
|
||||||
|
setActiveTerminalId,
|
||||||
|
setClosingTerminal,
|
||||||
|
socket,
|
||||||
|
activeTerminalId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
closing={closingTerminal === term.id}
|
||||||
selected={activeTerminalId === term.id}
|
selected={activeTerminalId === term.id}
|
||||||
>
|
>
|
||||||
<SquareTerminal className="w-4 h-4 mr-2" />
|
<SquareTerminal className="w-4 h-4 mr-2" />
|
||||||
@ -58,7 +59,18 @@ export default function Terminals() {
|
|||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
disabled={creatingTerminal}
|
disabled={creatingTerminal}
|
||||||
onClick={handleCreateTerminal}
|
onClick={() => {
|
||||||
|
if (terminals.length >= 4) {
|
||||||
|
toast.error("You reached the maximum # of terminals.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createTerminal({
|
||||||
|
setTerminals,
|
||||||
|
setActiveTerminalId,
|
||||||
|
setCreatingTerminal,
|
||||||
|
socket,
|
||||||
|
});
|
||||||
|
}}
|
||||||
size="smIcon"
|
size="smIcon"
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
className={`font-normal shrink-0 select-none text-muted-foreground disabled:opacity-50`}
|
className={`font-normal shrink-0 select-none text-muted-foreground disabled:opacity-50`}
|
||||||
|
@ -55,6 +55,7 @@ export default function EditorTerminal({
|
|||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
|
|
||||||
const disposableOnData = term.onData((data) => {
|
const disposableOnData = term.onData((data) => {
|
||||||
|
console.log("terminalData", id, data);
|
||||||
socket.emit("terminalData", id, data);
|
socket.emit("terminalData", id, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,20 +74,6 @@ export default function EditorTerminal({
|
|||||||
};
|
};
|
||||||
}, [term, terminalRef.current]);
|
}, [term, terminalRef.current]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!term) return;
|
|
||||||
const handleTerminalResponse = (response: { id: string; data: string }) => {
|
|
||||||
if (response.id === id) {
|
|
||||||
term.write(response.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.on("terminalResponse", handleTerminalResponse);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.off("terminalResponse", handleTerminalResponse);
|
|
||||||
};
|
|
||||||
}, [term, id, socket]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const LoadingDots: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<span className="loading-dots">
|
|
||||||
<span className="dot">.</span>
|
|
||||||
<span className="dot">.</span>
|
|
||||||
<span className="dot">.</span>
|
|
||||||
<style jsx>{`
|
|
||||||
.loading-dots {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.dot {
|
|
||||||
opacity: 0;
|
|
||||||
animation: showHideDot 1.5s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
.dot:nth-child(1) { animation-delay: 0s; }
|
|
||||||
.dot:nth-child(2) { animation-delay: 0.5s; }
|
|
||||||
.dot:nth-child(3) { animation-delay: 1s; }
|
|
||||||
@keyframes showHideDot {
|
|
||||||
0% { opacity: 0; }
|
|
||||||
50% { opacity: 1; }
|
|
||||||
100% { opacity: 0; }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoadingDots;
|
|
||||||
|
|
@ -22,7 +22,6 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2",
|
default: "h-9 px-4 py-2",
|
||||||
xs: "h-6 px-2.5 py-1.5 rounded-sm text-[0.7rem]",
|
|
||||||
sm: "h-8 rounded-md px-3 text-xs",
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
lg: "h-10 rounded-md px-8",
|
lg: "h-10 rounded-md px-8",
|
||||||
icon: "h-9 w-9",
|
icon: "h-9 w-9",
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
|
||||||
|
|
||||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
||||||
|
|
||||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
||||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
|
||||||
<PopoverPrimitive.Portal>
|
|
||||||
<PopoverPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
align={align}
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</PopoverPrimitive.Portal>
|
|
||||||
))
|
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
|
@ -42,13 +42,13 @@ export default function UserButton({ userData }: { userData: User }) {
|
|||||||
<div className="py-1.5 px-2 w-full flex flex-col items-start text-sm">
|
<div className="py-1.5 px-2 w-full flex flex-col items-start text-sm">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Sparkles className={`h-4 w-4 mr-2 text-indigo-500`} />
|
<Sparkles className={`h-4 w-4 mr-2 text-indigo-500`} />
|
||||||
AI Usage: {userData.generations}/1000
|
AI Usage: {userData.generations}/10
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full w-full mt-2 h-2 overflow-hidden bg-secondary">
|
<div className="rounded-full w-full mt-2 h-2 overflow-hidden bg-secondary">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-indigo-500 rounded-full"
|
className="h-full bg-indigo-500 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
width: `${(userData.generations * 100) / 1000}%`,
|
width: `${(userData.generations * 100) / 10}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useRef } from 'react';
|
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
|
||||||
|
|
||||||
interface PreviewContextType {
|
|
||||||
isPreviewCollapsed: boolean;
|
|
||||||
setIsPreviewCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
previewURL: string;
|
|
||||||
setPreviewURL: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
previewPanelRef: React.RefObject<ImperativePanelHandle>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreviewContext = createContext<PreviewContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const PreviewProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true);
|
|
||||||
const [previewURL, setPreviewURL] = useState<string>("");
|
|
||||||
const previewPanelRef = useRef<ImperativePanelHandle>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PreviewContext.Provider value={{ isPreviewCollapsed, setIsPreviewCollapsed, previewURL, setPreviewURL, previewPanelRef }}>
|
|
||||||
{children}
|
|
||||||
</PreviewContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePreview = () => {
|
|
||||||
const context = useContext(PreviewContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('usePreview must be used within a PreviewProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
@ -1,63 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
import { io, Socket } from 'socket.io-client';
|
|
||||||
|
|
||||||
interface SocketContextType {
|
|
||||||
socket: Socket | null;
|
|
||||||
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
|
||||||
const [userId, setUserId] = useState<string | null>(null);
|
|
||||||
const [sandboxId, setSandboxId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (userId && sandboxId) {
|
|
||||||
console.log("Initializing socket connection...");
|
|
||||||
const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
|
|
||||||
console.log("Socket instance:", newSocket);
|
|
||||||
setSocket(newSocket);
|
|
||||||
|
|
||||||
newSocket.on('connect', () => {
|
|
||||||
console.log("Socket connected:", newSocket.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
newSocket.on('disconnect', () => {
|
|
||||||
console.log("Socket disconnected");
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log("Disconnecting socket...");
|
|
||||||
newSocket.disconnect();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [userId, sandboxId]);
|
|
||||||
|
|
||||||
const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
|
|
||||||
setUserId(newUserId);
|
|
||||||
setSandboxId(newSandboxId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
socket,
|
|
||||||
setUserAndSandboxId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SocketContext.Provider value={ value }>
|
|
||||||
{children}
|
|
||||||
</SocketContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSocket = (): SocketContextType => {
|
|
||||||
const context = useContext(SocketContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useSocket must be used within a SocketProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
@ -1,95 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { createContext, useContext, useState } from 'react';
|
|
||||||
import { Terminal } from '@xterm/xterm';
|
|
||||||
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
|
|
||||||
import { useSocket } from '@/context/SocketContext';
|
|
||||||
|
|
||||||
interface TerminalContextType {
|
|
||||||
terminals: { id: string; terminal: Terminal | null }[];
|
|
||||||
setTerminals: React.Dispatch<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>;
|
|
||||||
activeTerminalId: string;
|
|
||||||
setActiveTerminalId: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
creatingTerminal: boolean;
|
|
||||||
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
createNewTerminal: (command?: string) => Promise<void>;
|
|
||||||
closeTerminal: (id: string) => void;
|
|
||||||
deploy: (callback: () => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const { socket } = useSocket();
|
|
||||||
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
|
|
||||||
const [activeTerminalId, setActiveTerminalId] = useState<string>('');
|
|
||||||
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const createNewTerminal = async (command?: string): Promise<void> => {
|
|
||||||
if (!socket) return;
|
|
||||||
setCreatingTerminal(true);
|
|
||||||
try {
|
|
||||||
createTerminalHelper({
|
|
||||||
setTerminals,
|
|
||||||
setActiveTerminalId,
|
|
||||||
setCreatingTerminal,
|
|
||||||
command,
|
|
||||||
socket,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating terminal:", error);
|
|
||||||
} finally {
|
|
||||||
setCreatingTerminal(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeTerminal = (id: string) => {
|
|
||||||
if (!socket) return;
|
|
||||||
const terminalToClose = terminals.find(term => term.id === id);
|
|
||||||
if (terminalToClose) {
|
|
||||||
closeTerminalHelper({
|
|
||||||
term: terminalToClose,
|
|
||||||
terminals,
|
|
||||||
setTerminals,
|
|
||||||
setActiveTerminalId,
|
|
||||||
setClosingTerminal: () => {},
|
|
||||||
socket,
|
|
||||||
activeTerminalId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deploy = (callback: () => void) => {
|
|
||||||
if (!socket) console.error("Couldn't deploy: No socket");
|
|
||||||
console.log("Deploying...")
|
|
||||||
socket?.emit("deploy", () => {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
terminals,
|
|
||||||
setTerminals,
|
|
||||||
activeTerminalId,
|
|
||||||
setActiveTerminalId,
|
|
||||||
creatingTerminal,
|
|
||||||
setCreatingTerminal,
|
|
||||||
createNewTerminal,
|
|
||||||
closeTerminal,
|
|
||||||
deploy
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TerminalContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</TerminalContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTerminal = (): TerminalContextType => {
|
|
||||||
const context = useContext(TerminalContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useTerminal must be used within a TerminalProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
export const projectTemplates: {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
icon: string
|
|
||||||
description: string
|
|
||||||
disabled: boolean
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
id: "reactjs",
|
|
||||||
name: "React",
|
|
||||||
icon: "/project-icons/react.svg",
|
|
||||||
description: "A JavaScript library for building user interfaces",
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "vanillajs",
|
|
||||||
name: "HTML/JS",
|
|
||||||
icon: "/project-icons/more.svg",
|
|
||||||
description: "More coming soon, feel free to contribute on GitHub",
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nextjs",
|
|
||||||
name: "NextJS",
|
|
||||||
icon: "/project-icons/node.svg",
|
|
||||||
description: "A JavaScript runtime built on the V8 JavaScript engine",
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "streamlit",
|
|
||||||
name: "Streamlit",
|
|
||||||
icon: "/project-icons/python.svg",
|
|
||||||
description: "A JavaScript runtime built on the V8 JavaScript engine",
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
]
|
|
@ -1,295 +0,0 @@
|
|||||||
{
|
|
||||||
"_coffee": "coffeescript",
|
|
||||||
"_js": "javascript",
|
|
||||||
"adp": "tcl",
|
|
||||||
"al": "perl",
|
|
||||||
"ant": "xml",
|
|
||||||
"aw": "php",
|
|
||||||
"axml": "xml",
|
|
||||||
"bash": "shell",
|
|
||||||
"bats": "shell",
|
|
||||||
"bones": "javascript",
|
|
||||||
"boot": "clojure",
|
|
||||||
"builder": "ruby",
|
|
||||||
"bzl": "python",
|
|
||||||
"c": "c",
|
|
||||||
"c++": "cpp",
|
|
||||||
"cake": "coffeescript",
|
|
||||||
"cats": "c",
|
|
||||||
"cc": "cpp",
|
|
||||||
"ccxml": "xml",
|
|
||||||
"cfg": "ini",
|
|
||||||
"cgi": "shell",
|
|
||||||
"cjsx": "coffeescript",
|
|
||||||
"cl2": "clojure",
|
|
||||||
"clixml": "xml",
|
|
||||||
"clj": "clojure",
|
|
||||||
"cljc": "clojure",
|
|
||||||
"cljs.hl": "clojure",
|
|
||||||
"cljs": "clojure",
|
|
||||||
"cljscm": "clojure",
|
|
||||||
"cljx": "clojure",
|
|
||||||
"coffee": "coffeescript",
|
|
||||||
"command": "shell",
|
|
||||||
"cp": "cpp",
|
|
||||||
"cpp": "cpp",
|
|
||||||
"cproject": "xml",
|
|
||||||
"cql": "sql",
|
|
||||||
"csl": "xml",
|
|
||||||
"cson": "coffeescript",
|
|
||||||
"csproj": "xml",
|
|
||||||
"ct": "xml",
|
|
||||||
"ctp": "php",
|
|
||||||
"cxx": "cpp",
|
|
||||||
"ddl": "sql",
|
|
||||||
"dfm": "pascal",
|
|
||||||
"dita": "xml",
|
|
||||||
"ditamap": "xml",
|
|
||||||
"ditaval": "xml",
|
|
||||||
"dll.config": "xml",
|
|
||||||
"dotsettings": "xml",
|
|
||||||
"dpr": "pascal",
|
|
||||||
"ecl": "ecl",
|
|
||||||
"eclxml": "ecl",
|
|
||||||
"es": "javascript",
|
|
||||||
"es6": "javascript",
|
|
||||||
"ex": "elixir",
|
|
||||||
"exs": "elixir",
|
|
||||||
"fcgi": "shell",
|
|
||||||
"filters": "xml",
|
|
||||||
"frag": "javascript",
|
|
||||||
"fsproj": "xml",
|
|
||||||
"fxml": "xml",
|
|
||||||
"gemspec": "ruby",
|
|
||||||
"geojson": "json",
|
|
||||||
"glade": "xml",
|
|
||||||
"gml": "xml",
|
|
||||||
"god": "ruby",
|
|
||||||
"grxml": "xml",
|
|
||||||
"gs": "javascript",
|
|
||||||
"gyp": "python",
|
|
||||||
"h": "cpp",
|
|
||||||
"h++": "cpp",
|
|
||||||
"handlebars": "handlebars",
|
|
||||||
"hbs": "handlebars",
|
|
||||||
"hcl": "hcl",
|
|
||||||
"hh": "cpp",
|
|
||||||
"hic": "clojure",
|
|
||||||
"hpp": "cpp",
|
|
||||||
"htm": "html",
|
|
||||||
"html.hl": "html",
|
|
||||||
"html": "html",
|
|
||||||
"hxx": "cpp",
|
|
||||||
"iced": "coffeescript",
|
|
||||||
"idc": "c",
|
|
||||||
"iml": "xml",
|
|
||||||
"inc": "sql",
|
|
||||||
"ini": "ini",
|
|
||||||
"inl": "cpp",
|
|
||||||
"ipp": "cpp",
|
|
||||||
"irbrc": "ruby",
|
|
||||||
"ivy": "xml",
|
|
||||||
"j2": "python",
|
|
||||||
"jake": "javascript",
|
|
||||||
"jbuilder": "ruby",
|
|
||||||
"jelly": "xml",
|
|
||||||
"jinja": "python",
|
|
||||||
"jinja2": "python",
|
|
||||||
"js": "javascript",
|
|
||||||
"jsb": "javascript",
|
|
||||||
"jscad": "javascript",
|
|
||||||
"jsfl": "javascript",
|
|
||||||
"jsm": "javascript",
|
|
||||||
"json": "json",
|
|
||||||
"jsproj": "xml",
|
|
||||||
"jss": "javascript",
|
|
||||||
"kml": "xml",
|
|
||||||
"ksh": "shell",
|
|
||||||
"kt": "kotlin",
|
|
||||||
"ktm": "kotlin",
|
|
||||||
"kts": "kotlin",
|
|
||||||
"launch": "xml",
|
|
||||||
"lmi": "python",
|
|
||||||
"lock": "json",
|
|
||||||
"lpr": "pascal",
|
|
||||||
"lua": "lua",
|
|
||||||
"markdown": "markdown",
|
|
||||||
"md": "markdown",
|
|
||||||
"mdpolicy": "xml",
|
|
||||||
"mkd": "markdown",
|
|
||||||
"mkdn": "markdown",
|
|
||||||
"mkdown": "markdown",
|
|
||||||
"mm": "xml",
|
|
||||||
"mod": "xml",
|
|
||||||
"mspec": "ruby",
|
|
||||||
"mustache": "python",
|
|
||||||
"mxml": "xml",
|
|
||||||
"njs": "javascript",
|
|
||||||
"nproj": "xml",
|
|
||||||
"nse": "lua",
|
|
||||||
"nuspec": "xml",
|
|
||||||
"odd": "xml",
|
|
||||||
"osm": "xml",
|
|
||||||
"pac": "javascript",
|
|
||||||
"pas": "pascal",
|
|
||||||
"pd_lua": "lua",
|
|
||||||
"perl": "perl",
|
|
||||||
"ph": "perl",
|
|
||||||
"php": "php",
|
|
||||||
"php3": "php",
|
|
||||||
"php4": "php",
|
|
||||||
"php5": "php",
|
|
||||||
"phps": "php",
|
|
||||||
"phpt": "php",
|
|
||||||
"pl": "perl",
|
|
||||||
"plist": "xml",
|
|
||||||
"pluginspec": "xml",
|
|
||||||
"plx": "perl",
|
|
||||||
"pm": "perl",
|
|
||||||
"pod": "perl",
|
|
||||||
"podspec": "ruby",
|
|
||||||
"pp": "pascal",
|
|
||||||
"prc": "sql",
|
|
||||||
"prefs": "ini",
|
|
||||||
"pro": "ini",
|
|
||||||
"properties": "ini",
|
|
||||||
"props": "xml",
|
|
||||||
"ps1": "powershell",
|
|
||||||
"ps1xml": "xml",
|
|
||||||
"psc1": "xml",
|
|
||||||
"psd1": "powershell",
|
|
||||||
"psgi": "perl",
|
|
||||||
"psm1": "powershell",
|
|
||||||
"pt": "xml",
|
|
||||||
"py": "python",
|
|
||||||
"pyde": "python",
|
|
||||||
"pyp": "python",
|
|
||||||
"pyt": "python",
|
|
||||||
"pyw": "python",
|
|
||||||
"r": "r",
|
|
||||||
"rabl": "ruby",
|
|
||||||
"rake": "ruby",
|
|
||||||
"rb": "ruby",
|
|
||||||
"rbuild": "ruby",
|
|
||||||
"rbw": "ruby",
|
|
||||||
"rbx": "ruby",
|
|
||||||
"rbxs": "lua",
|
|
||||||
"rd": "r",
|
|
||||||
"rdf": "xml",
|
|
||||||
"reek": "yaml",
|
|
||||||
"rest.txt": "restructuredtext",
|
|
||||||
"rest": "restructuredtext",
|
|
||||||
"ron": "markdown",
|
|
||||||
"rpy": "python",
|
|
||||||
"rq": "sparql",
|
|
||||||
"rs.in": "rust",
|
|
||||||
"rs": "rust",
|
|
||||||
"rss": "xml",
|
|
||||||
"rst.txt": "restructuredtext",
|
|
||||||
"rst": "restructuredtext",
|
|
||||||
"rsx": "r",
|
|
||||||
"ru": "ruby",
|
|
||||||
"ruby": "ruby",
|
|
||||||
"rviz": "yaml",
|
|
||||||
"sbt": "scala",
|
|
||||||
"sc": "scala",
|
|
||||||
"scala": "scala",
|
|
||||||
"scm": "scheme",
|
|
||||||
"scxml": "xml",
|
|
||||||
"sh.in": "shell",
|
|
||||||
"sh": "shell",
|
|
||||||
"sjs": "javascript",
|
|
||||||
"sld": "scheme",
|
|
||||||
"sls": "scheme",
|
|
||||||
"sparql": "sparql",
|
|
||||||
"sps": "scheme",
|
|
||||||
"sql": "sql",
|
|
||||||
"srdf": "xml",
|
|
||||||
"ss": "scheme",
|
|
||||||
"ssjs": "javascript",
|
|
||||||
"st": "html",
|
|
||||||
"storyboard": "xml",
|
|
||||||
"sttheme": "xml",
|
|
||||||
"sublime_metrics": "javascript",
|
|
||||||
"sublime_session": "javascript",
|
|
||||||
"sublime-build": "javascript",
|
|
||||||
"sublime-commands": "javascript",
|
|
||||||
"sublime-completions": "javascript",
|
|
||||||
"sublime-keymap": "javascript",
|
|
||||||
"sublime-macro": "javascript",
|
|
||||||
"sublime-menu": "javascript",
|
|
||||||
"sublime-mousemap": "javascript",
|
|
||||||
"sublime-project": "javascript",
|
|
||||||
"sublime-settings": "javascript",
|
|
||||||
"sublime-snippet": "xml",
|
|
||||||
"sublime-syntax": "yaml",
|
|
||||||
"sublime-theme": "javascript",
|
|
||||||
"sublime-workspace": "javascript",
|
|
||||||
"sv": "systemverilog",
|
|
||||||
"svh": "systemverilog",
|
|
||||||
"syntax": "yaml",
|
|
||||||
"t": "perl",
|
|
||||||
"tab": "sql",
|
|
||||||
"tac": "python",
|
|
||||||
"targets": "xml",
|
|
||||||
"tcc": "cpp",
|
|
||||||
"tcl": "tcl",
|
|
||||||
"tf": "hcl",
|
|
||||||
"thor": "ruby",
|
|
||||||
"tm": "tcl",
|
|
||||||
"tmcommand": "xml",
|
|
||||||
"tml": "xml",
|
|
||||||
"tmlanguage": "xml",
|
|
||||||
"tmpreferences": "xml",
|
|
||||||
"tmsnippet": "xml",
|
|
||||||
"tmtheme": "xml",
|
|
||||||
"tmux": "shell",
|
|
||||||
"tool": "shell",
|
|
||||||
"topojson": "json",
|
|
||||||
"tpp": "cpp",
|
|
||||||
"ts": "typescript",
|
|
||||||
"tsx": "typescript",
|
|
||||||
"udf": "sql",
|
|
||||||
"ui": "xml",
|
|
||||||
"urdf": "xml",
|
|
||||||
"ux": "xml",
|
|
||||||
"v": "verilog",
|
|
||||||
"vbproj": "xml",
|
|
||||||
"vcxproj": "xml",
|
|
||||||
"veo": "verilog",
|
|
||||||
"vh": "systemverilog",
|
|
||||||
"viw": "sql",
|
|
||||||
"vssettings": "xml",
|
|
||||||
"vxml": "xml",
|
|
||||||
"w": "c",
|
|
||||||
"watchr": "ruby",
|
|
||||||
"wlua": "lua",
|
|
||||||
"wsdl": "xml",
|
|
||||||
"wsf": "xml",
|
|
||||||
"wsgi": "python",
|
|
||||||
"wxi": "xml",
|
|
||||||
"wxl": "xml",
|
|
||||||
"wxs": "xml",
|
|
||||||
"x3d": "xml",
|
|
||||||
"xacro": "xml",
|
|
||||||
"xaml": "xml",
|
|
||||||
"xht": "html",
|
|
||||||
"xhtml": "html",
|
|
||||||
"xib": "xml",
|
|
||||||
"xlf": "xml",
|
|
||||||
"xliff": "xml",
|
|
||||||
"xmi": "xml",
|
|
||||||
"xml.dist": "xml",
|
|
||||||
"xml": "xml",
|
|
||||||
"xproj": "xml",
|
|
||||||
"xpy": "python",
|
|
||||||
"xsd": "xml",
|
|
||||||
"xsjs": "javascript",
|
|
||||||
"xsjslib": "javascript",
|
|
||||||
"xul": "xml",
|
|
||||||
"yaml-tmlanguage": "yaml",
|
|
||||||
"yaml": "yaml",
|
|
||||||
"yml": "yaml",
|
|
||||||
"zcml": "xml",
|
|
||||||
"zsh": "shell"
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ export const createTerminal = ({
|
|||||||
setTerminals,
|
setTerminals,
|
||||||
setActiveTerminalId,
|
setActiveTerminalId,
|
||||||
setCreatingTerminal,
|
setCreatingTerminal,
|
||||||
command,
|
|
||||||
socket,
|
socket,
|
||||||
}: {
|
}: {
|
||||||
setTerminals: React.Dispatch<React.SetStateAction<{
|
setTerminals: React.Dispatch<React.SetStateAction<{
|
||||||
@ -17,7 +16,6 @@ export const createTerminal = ({
|
|||||||
}[]>>;
|
}[]>>;
|
||||||
setActiveTerminalId: React.Dispatch<React.SetStateAction<string>>;
|
setActiveTerminalId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
command?: string;
|
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
|
|
||||||
}) => {
|
}) => {
|
||||||
@ -31,7 +29,6 @@ export const createTerminal = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
socket.emit("createTerminal", id, () => {
|
socket.emit("createTerminal", id, () => {
|
||||||
setCreatingTerminal(false);
|
setCreatingTerminal(false);
|
||||||
if (command) socket.emit("terminalData", id, command + "\n");
|
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
import * as monaco from "monaco-editor"
|
|
||||||
|
|
||||||
export function parseTSConfigToMonacoOptions(
|
|
||||||
tsconfig: any
|
|
||||||
): monaco.languages.typescript.CompilerOptions {
|
|
||||||
const compilerOptions: monaco.languages.typescript.CompilerOptions = {}
|
|
||||||
|
|
||||||
// Map tsconfig options to Monaco CompilerOptions
|
|
||||||
if (tsconfig.strict) compilerOptions.strict = tsconfig.strict
|
|
||||||
if (tsconfig.target) compilerOptions.target = mapScriptTarget(tsconfig.target)
|
|
||||||
if (tsconfig.module) compilerOptions.module = mapModule(tsconfig.module)
|
|
||||||
if (tsconfig.lib) compilerOptions.lib = tsconfig.lib
|
|
||||||
if (tsconfig.allowJs) compilerOptions.allowJs = tsconfig.allowJs
|
|
||||||
if (tsconfig.checkJs) compilerOptions.checkJs = tsconfig.checkJs
|
|
||||||
if (tsconfig.jsx) compilerOptions.jsx = mapJSX(tsconfig.jsx)
|
|
||||||
if (tsconfig.declaration) compilerOptions.declaration = tsconfig.declaration
|
|
||||||
if (tsconfig.declarationMap)
|
|
||||||
compilerOptions.declarationMap = tsconfig.declarationMap
|
|
||||||
if (tsconfig.sourceMap) compilerOptions.sourceMap = tsconfig.sourceMap
|
|
||||||
if (tsconfig.outFile) compilerOptions.outFile = tsconfig.outFile
|
|
||||||
if (tsconfig.outDir) compilerOptions.outDir = tsconfig.outDir
|
|
||||||
if (tsconfig.removeComments)
|
|
||||||
compilerOptions.removeComments = tsconfig.removeComments
|
|
||||||
if (tsconfig.noEmit) compilerOptions.noEmit = tsconfig.noEmit
|
|
||||||
if (tsconfig.noEmitOnError)
|
|
||||||
compilerOptions.noEmitOnError = tsconfig.noEmitOnError
|
|
||||||
|
|
||||||
return compilerOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapScriptTarget(
|
|
||||||
target: string
|
|
||||||
): monaco.languages.typescript.ScriptTarget {
|
|
||||||
const targetMap: { [key: string]: monaco.languages.typescript.ScriptTarget } =
|
|
||||||
{
|
|
||||||
es3: monaco.languages.typescript.ScriptTarget.ES3,
|
|
||||||
es5: monaco.languages.typescript.ScriptTarget.ES5,
|
|
||||||
es6: monaco.languages.typescript.ScriptTarget.ES2015,
|
|
||||||
es2015: monaco.languages.typescript.ScriptTarget.ES2015,
|
|
||||||
es2016: monaco.languages.typescript.ScriptTarget.ES2016,
|
|
||||||
es2017: monaco.languages.typescript.ScriptTarget.ES2017,
|
|
||||||
es2018: monaco.languages.typescript.ScriptTarget.ES2018,
|
|
||||||
es2019: monaco.languages.typescript.ScriptTarget.ES2019,
|
|
||||||
es2020: monaco.languages.typescript.ScriptTarget.ES2020,
|
|
||||||
esnext: monaco.languages.typescript.ScriptTarget.ESNext,
|
|
||||||
}
|
|
||||||
if (typeof target !== "string") {
|
|
||||||
return monaco.languages.typescript.ScriptTarget.Latest
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
targetMap[target?.toLowerCase()] ||
|
|
||||||
monaco.languages.typescript.ScriptTarget.Latest
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapModule(module: string): monaco.languages.typescript.ModuleKind {
|
|
||||||
const moduleMap: { [key: string]: monaco.languages.typescript.ModuleKind } = {
|
|
||||||
none: monaco.languages.typescript.ModuleKind.None,
|
|
||||||
commonjs: monaco.languages.typescript.ModuleKind.CommonJS,
|
|
||||||
amd: monaco.languages.typescript.ModuleKind.AMD,
|
|
||||||
umd: monaco.languages.typescript.ModuleKind.UMD,
|
|
||||||
system: monaco.languages.typescript.ModuleKind.System,
|
|
||||||
es6: monaco.languages.typescript.ModuleKind.ES2015,
|
|
||||||
es2015: monaco.languages.typescript.ModuleKind.ES2015,
|
|
||||||
esnext: monaco.languages.typescript.ModuleKind.ESNext,
|
|
||||||
}
|
|
||||||
if (typeof module !== "string") {
|
|
||||||
return monaco.languages.typescript.ModuleKind.ESNext
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
moduleMap[module.toLowerCase()] ||
|
|
||||||
monaco.languages.typescript.ModuleKind.ESNext
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapJSX(jsx: string): monaco.languages.typescript.JsxEmit {
|
|
||||||
const jsxMap: { [key: string]: monaco.languages.typescript.JsxEmit } = {
|
|
||||||
preserve: monaco.languages.typescript.JsxEmit.Preserve,
|
|
||||||
react: monaco.languages.typescript.JsxEmit.React,
|
|
||||||
"react-native": monaco.languages.typescript.JsxEmit.ReactNative,
|
|
||||||
}
|
|
||||||
return jsxMap[jsx.toLowerCase()] || monaco.languages.typescript.JsxEmit.React
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example usage:
|
|
||||||
const tsconfigJSON = {
|
|
||||||
compilerOptions: {
|
|
||||||
strict: true,
|
|
||||||
target: "ES2020",
|
|
||||||
module: "ESNext",
|
|
||||||
lib: ["DOM", "ES2020"],
|
|
||||||
jsx: "react",
|
|
||||||
sourceMap: true,
|
|
||||||
outDir: "./dist",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const monacoOptions = parseTSConfigToMonacoOptions(tsconfigJSON.compilerOptions)
|
|
||||||
console.log(monacoOptions)
|
|
@ -12,7 +12,7 @@ export type User = {
|
|||||||
export type Sandbox = {
|
export type Sandbox = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: "react" | "node";
|
||||||
visibility: "public" | "private";
|
visibility: "public" | "private";
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -2,19 +2,18 @@ import { type ClassValue, clsx } from "clsx"
|
|||||||
// import { toast } from "sonner"
|
// import { toast } from "sonner"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { Sandbox, TFile, TFolder } from "./types"
|
import { Sandbox, TFile, TFolder } from "./types"
|
||||||
import fileExtToLang from "./file-extension-to-language.json"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFileType(file: string) {
|
export function processFileType(file: string) {
|
||||||
const extension = file.split(".").pop()
|
const ending = file.split(".").pop()
|
||||||
const fileExtToLangMap = fileExtToLang as Record<string, string>
|
|
||||||
if (extension && fileExtToLangMap[extension]) {
|
|
||||||
return fileExtToLangMap[extension]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (ending === "ts" || ending === "tsx") return "typescript"
|
||||||
|
if (ending === "js" || ending === "jsx") return "javascript"
|
||||||
|
|
||||||
|
if (ending) return ending
|
||||||
return "plaintext"
|
return "plaintext"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,39 +61,3 @@ export function addNew(
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function debounce<T extends (...args: any[]) => void>(
|
|
||||||
func: T,
|
|
||||||
wait: number
|
|
||||||
): T {
|
|
||||||
let timeout: NodeJS.Timeout | null = null
|
|
||||||
return function (...args: Parameters<T>) {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
|
||||||
timeout = setTimeout(() => func(...args), wait)
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deep merge utility function
|
|
||||||
export const deepMerge = (target: any, source: any) => {
|
|
||||||
const output = { ...target }
|
|
||||||
if (isObject(target) && isObject(source)) {
|
|
||||||
Object.keys(source).forEach((key) => {
|
|
||||||
if (isObject(source[key])) {
|
|
||||||
if (!(key in target)) {
|
|
||||||
Object.assign(output, { [key]: source[key] })
|
|
||||||
} else {
|
|
||||||
output[key] = deepMerge(target[key], source[key])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Object.assign(output, { [key]: source[key] })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
const isObject = (item: any) => {
|
|
||||||
return item && typeof item === "object" && !Array.isArray(item)
|
|
||||||
}
|
|
||||||
|
2456
frontend/package-lock.json
generated
2456
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@
|
|||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.1.7",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.1.7",
|
||||||
"@clerk/nextjs": "^4.29.12",
|
"@clerk/nextjs": "^4.29.12",
|
||||||
"@clerk/themes": "^1.7.12",
|
"@clerk/themes": "^1.7.12",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@liveblocks/client": "^1.12.0",
|
"@liveblocks/client": "^1.12.0",
|
||||||
"@liveblocks/node": "^1.12.0",
|
"@liveblocks/node": "^1.12.0",
|
||||||
@ -26,13 +25,10 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@react-three/fiber": "^8.16.6",
|
"@react-three/fiber": "^8.16.6",
|
||||||
"@uiw/codemirror-theme-vscode": "^4.23.5",
|
|
||||||
"@uiw/react-codemirror": "^4.23.5",
|
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
@ -45,14 +41,10 @@
|
|||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"next": "14.1.3",
|
"next": "14.1.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"posthog-js": "^1.147.0",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.51.3",
|
"react-hook-form": "^7.51.3",
|
||||||
"react-markdown": "^9.0.1",
|
|
||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
|
||||||
"remark-gfm": "^4.0.0",
|
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
"sonner": "^1.4.41",
|
"sonner": "^1.4.41",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
@ -65,11 +57,9 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/estree": "^1.0.6",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
|
||||||
"@types/three": "^0.164.0",
|
"@types/three": "^0.164.0",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
3
frontend/react-syntax-highlighter.d.ts
vendored
3
frontend/react-syntax-highlighter.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
declare module 'react-syntax-highlighter';
|
|
||||||
declare module 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"types": ["node"],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
630
package-lock.json
generated
630
package-lock.json
generated
@ -1,630 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "sandbox",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-popover": "^1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@floating-ui/core": {
|
|
||||||
"version": "1.6.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz",
|
|
||||||
"integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==",
|
|
||||||
"dependencies": {
|
|
||||||
"@floating-ui/utils": "^0.2.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@floating-ui/dom": {
|
|
||||||
"version": "1.6.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz",
|
|
||||||
"integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==",
|
|
||||||
"dependencies": {
|
|
||||||
"@floating-ui/core": "^1.6.0",
|
|
||||||
"@floating-ui/utils": "^0.2.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@floating-ui/react-dom": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@floating-ui/dom": "^1.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8.0",
|
|
||||||
"react-dom": ">=16.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@floating-ui/utils": {
|
|
||||||
"version": "0.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz",
|
|
||||||
"integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA=="
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/primitive": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-arrow": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-primitive": "2.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-context": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-dismissable-layer": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/primitive": "1.1.0",
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
|
||||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-focus-guards": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-focus-scope": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-id": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-popover": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/primitive": "1.1.0",
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
|
||||||
"@radix-ui/react-context": "1.1.0",
|
|
||||||
"@radix-ui/react-dismissable-layer": "1.1.0",
|
|
||||||
"@radix-ui/react-focus-guards": "1.1.0",
|
|
||||||
"@radix-ui/react-focus-scope": "1.1.0",
|
|
||||||
"@radix-ui/react-id": "1.1.0",
|
|
||||||
"@radix-ui/react-popper": "1.2.0",
|
|
||||||
"@radix-ui/react-portal": "1.1.1",
|
|
||||||
"@radix-ui/react-presence": "1.1.0",
|
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
|
||||||
"@radix-ui/react-slot": "1.1.0",
|
|
||||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
||||||
"aria-hidden": "^1.1.1",
|
|
||||||
"react-remove-scroll": "2.5.7"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-popper": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@floating-ui/react-dom": "^2.0.0",
|
|
||||||
"@radix-ui/react-arrow": "1.1.0",
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
|
||||||
"@radix-ui/react-context": "1.1.0",
|
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
|
||||||
"@radix-ui/react-use-rect": "1.1.0",
|
|
||||||
"@radix-ui/react-use-size": "1.1.0",
|
|
||||||
"@radix-ui/rect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-portal": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-presence": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-primitive": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-slot": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"@types/react-dom": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-slot": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-controllable-state": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-escape-keydown": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-rect": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/rect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-size": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/rect": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
|
|
||||||
},
|
|
||||||
"node_modules/aria-hidden": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/detect-node-es": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
|
||||||
},
|
|
||||||
"node_modules/get-nonce": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/invariant": {
|
|
||||||
"version": "2.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
|
||||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
|
||||||
},
|
|
||||||
"node_modules/loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"loose-envify": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
|
||||||
"version": "18.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dom": {
|
|
||||||
"version": "18.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"scheduler": "^0.23.2"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-remove-scroll": {
|
|
||||||
"version": "2.5.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
|
|
||||||
"integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
|
|
||||||
"dependencies": {
|
|
||||||
"react-remove-scroll-bar": "^2.3.4",
|
|
||||||
"react-style-singleton": "^2.2.1",
|
|
||||||
"tslib": "^2.1.0",
|
|
||||||
"use-callback-ref": "^1.3.0",
|
|
||||||
"use-sidecar": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-remove-scroll-bar": {
|
|
||||||
"version": "2.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
|
|
||||||
"integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
|
|
||||||
"dependencies": {
|
|
||||||
"react-style-singleton": "^2.2.1",
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-style-singleton": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
|
||||||
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
|
|
||||||
"dependencies": {
|
|
||||||
"get-nonce": "^1.0.0",
|
|
||||||
"invariant": "^2.2.4",
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/scheduler": {
|
|
||||||
"version": "0.23.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tslib": {
|
|
||||||
"version": "2.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
|
||||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
|
|
||||||
},
|
|
||||||
"node_modules/use-callback-ref": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/use-sidecar": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
|
|
||||||
"dependencies": {
|
|
||||||
"detect-node-es": "^1.1.0",
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-popover": "^1.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// Import necessary modules
|
|
||||||
import { io, Socket } from "socket.io-client";
|
|
||||||
import dotenv from "dotenv";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
interface CallbackResponse {
|
|
||||||
success: boolean;
|
|
||||||
apps?: string[];
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let socketRef: Socket = io(
|
|
||||||
`http://localhost:4000?userId=user_2hFB6KcK6bb3Gx9241UXsxFq4kO&sandboxId=v30a2c48xal03tzio7mapt19`,
|
|
||||||
{
|
|
||||||
timeout: 2000,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socketRef.on("connect", async () => {
|
|
||||||
console.log("Connected to the server");
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
socketRef.emit("list", (response: CallbackResponse) => {
|
|
||||||
if (response.success) {
|
|
||||||
console.log("List of apps:", response.apps);
|
|
||||||
} else {
|
|
||||||
console.log("Error:", response.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socketRef.emit("deploy", (response: CallbackResponse) => {
|
|
||||||
if (response.success) {
|
|
||||||
console.log("It worked!");
|
|
||||||
} else {
|
|
||||||
console.log("Error:", response.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socketRef.on("disconnect", () => {
|
|
||||||
console.log("Disconnected from the server");
|
|
||||||
});
|
|
||||||
|
|
||||||
socketRef.on("connect_error", (error: Error) => {
|
|
||||||
console.error("Connection error:", error);
|
|
||||||
});
|
|
310
tests/package-lock.json
generated
310
tests/package-lock.json
generated
@ -1,310 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "socket-io-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "socket-io-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"dotenv": "^16.4.5",
|
|
||||||
"socket.io-client": "^4.7.5",
|
|
||||||
"ts-node": "^10.9.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
|
||||||
"version": "0.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
|
||||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/trace-mapping": "0.3.9"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
|
||||||
"version": "0.3.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
|
||||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/resolve-uri": "^3.0.3",
|
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@socket.io/component-emitter": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
|
||||||
},
|
|
||||||
"node_modules/@tsconfig/node10": {
|
|
||||||
"version": "1.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
|
||||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="
|
|
||||||
},
|
|
||||||
"node_modules/@tsconfig/node12": {
|
|
||||||
"version": "1.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
|
||||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
|
|
||||||
},
|
|
||||||
"node_modules/@tsconfig/node14": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
|
|
||||||
},
|
|
||||||
"node_modules/@tsconfig/node16": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "20.14.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
|
|
||||||
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~5.26.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/acorn": {
|
|
||||||
"version": "8.12.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
|
||||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/acorn-walk": {
|
|
||||||
"version": "8.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
|
||||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
|
||||||
"dependencies": {
|
|
||||||
"acorn": "^8.11.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/arg": {
|
|
||||||
"version": "4.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
|
||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
|
|
||||||
},
|
|
||||||
"node_modules/create-require": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
|
||||||
},
|
|
||||||
"node_modules/debug": {
|
|
||||||
"version": "4.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
|
||||||
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/diff": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dotenv": {
|
|
||||||
"version": "16.4.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
|
||||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://dotenvx.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io-client": {
|
|
||||||
"version": "6.5.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
|
|
||||||
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.1",
|
|
||||||
"engine.io-parser": "~5.2.1",
|
|
||||||
"ws": "~8.17.1",
|
|
||||||
"xmlhttprequest-ssl": "~2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io-parser": {
|
|
||||||
"version": "5.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
|
||||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/make-error": {
|
|
||||||
"version": "1.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
|
||||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-client": {
|
|
||||||
"version": "4.7.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
|
||||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.2",
|
|
||||||
"engine.io-client": "~6.5.2",
|
|
||||||
"socket.io-parser": "~4.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-parser": {
|
|
||||||
"version": "4.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
|
||||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ts-node": {
|
|
||||||
"version": "10.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
|
||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
|
||||||
"@tsconfig/node10": "^1.0.7",
|
|
||||||
"@tsconfig/node12": "^1.0.7",
|
|
||||||
"@tsconfig/node14": "^1.0.0",
|
|
||||||
"@tsconfig/node16": "^1.0.2",
|
|
||||||
"acorn": "^8.4.1",
|
|
||||||
"acorn-walk": "^8.1.1",
|
|
||||||
"arg": "^4.1.0",
|
|
||||||
"create-require": "^1.1.0",
|
|
||||||
"diff": "^4.0.1",
|
|
||||||
"make-error": "^1.1.1",
|
|
||||||
"v8-compile-cache-lib": "^3.0.1",
|
|
||||||
"yn": "3.1.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"ts-node": "dist/bin.js",
|
|
||||||
"ts-node-cwd": "dist/bin-cwd.js",
|
|
||||||
"ts-node-esm": "dist/bin-esm.js",
|
|
||||||
"ts-node-script": "dist/bin-script.js",
|
|
||||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
|
||||||
"ts-script": "dist/bin-script-deprecated.js"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@swc/core": ">=1.2.50",
|
|
||||||
"@swc/wasm": ">=1.2.50",
|
|
||||||
"@types/node": "*",
|
|
||||||
"typescript": ">=2.7"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@swc/core": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@swc/wasm": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "5.26.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/v8-compile-cache-lib": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yn": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "socket-io-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A test script for socket.io-client using ES6 modules and TypeScript",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"start": "npm run build && node dist/index.js"
|
|
||||||
},
|
|
||||||
"author": "Your Name",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"dotenv": "^16.4.5",
|
|
||||||
"socket.io-client": "^4.7.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.0.0",
|
|
||||||
"ts-node": "^10.9.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"outDir": "./dist"
|
|
||||||
},
|
|
||||||
"include": ["index.ts"]
|
|
||||||
}
|
|
Reference in New Issue
Block a user