Merge branch 'refs/heads/feat/dokku' into production
# Conflicts: # frontend/app/layout.tsx
This commit is contained in:
commit
e8a3944b9e
@ -1,8 +1,13 @@
|
|||||||
# 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=
|
E2B_API_KEY=
|
||||||
|
DOKKU_HOST=
|
||||||
|
DOKKU_USERNAME=
|
||||||
|
DOKKU_KEY=
|
157
backend/server/package-lock.json
generated
157
backend/server/package-lock.json
generated
@ -15,13 +15,16 @@
|
|||||||
"e2b": "^0.16.1",
|
"e2b": "^0.16.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"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"
|
||||||
@ -75,6 +78,40 @@
|
|||||||
"@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.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/@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.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz",
|
||||||
@ -213,6 +250,24 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ssh2": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^18.11.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/ssh2/node_modules/@types/node": {
|
||||||
|
"version": "18.19.41",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz",
|
||||||
|
"integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/abbrev": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
@ -298,6 +353,14 @@
|
|||||||
"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",
|
||||||
@ -312,6 +375,14 @@
|
|||||||
"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",
|
||||||
@ -382,6 +453,15 @@
|
|||||||
"node": ">=6.14.2"
|
"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",
|
||||||
@ -593,6 +673,20 @@
|
|||||||
"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",
|
||||||
@ -1247,6 +1341,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/nan": {
|
||||||
|
"version": "2.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||||
|
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
@ -1630,6 +1730,41 @@
|
|||||||
"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.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/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",
|
||||||
@ -1748,6 +1883,23 @@
|
|||||||
"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",
|
||||||
@ -1880,6 +2032,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
"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",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
"e2b": "^0.16.1",
|
"e2b": "^0.16.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"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"
|
||||||
|
37
backend/server/src/DokkuClient.ts
Normal file
37
backend/server/src/DokkuClient.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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 };
|
90
backend/server/src/SSHSocketClient.ts
Normal file
90
backend/server/src/SSHSocketClient.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
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()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
80
backend/server/src/SecureGitClient.ts
Normal file
80
backend/server/src/SecureGitClient.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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.join(os.tmpdir(), 'git-push-'));
|
||||||
|
console.log(`Temporary directory created: ${tempDir}`);
|
||||||
|
|
||||||
|
// Write files to the temporary directory
|
||||||
|
for (const { id, data } of fileData) {
|
||||||
|
const filePath = path.join(tempDir, id);
|
||||||
|
const dirPath = path.dirname(filePath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Writing ", filePath, data);
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
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,10 +1,12 @@
|
|||||||
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 from "fs";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { User } from "./types";
|
import { User } from "./types";
|
||||||
@ -112,6 +114,23 @@ io.use(async (socket, next) => {
|
|||||||
|
|
||||||
const lockManager = new LockManager();
|
const lockManager = new LockManager();
|
||||||
|
|
||||||
|
if (!process.env.DOKKU_HOST) throw new Error('Environment variable DOKKU_HOST is not defined');
|
||||||
|
if (!process.env.DOKKU_USERNAME) throw new Error('Environment variable DOKKU_USERNAME is not defined');
|
||||||
|
if (!process.env.DOKKU_KEY) throw new Error('Environment variable DOKKU_KEY is not defined');
|
||||||
|
|
||||||
|
const client = new DokkuClient({
|
||||||
|
host: process.env.DOKKU_HOST,
|
||||||
|
username: process.env.DOKKU_USERNAME,
|
||||||
|
privateKey: fs.readFileSync(process.env.DOKKU_KEY),
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
|
||||||
|
const git = new SecureGitClient(
|
||||||
|
"dokku@gitwit.app",
|
||||||
|
process.env.DOKKU_KEY
|
||||||
|
)
|
||||||
|
|
||||||
io.on("connection", async (socket) => {
|
io.on("connection", async (socket) => {
|
||||||
try {
|
try {
|
||||||
if (inactivityTimeout) clearTimeout(inactivityTimeout);
|
if (inactivityTimeout) clearTimeout(inactivityTimeout);
|
||||||
@ -137,10 +156,6 @@ io.on("connection", async (socket) => {
|
|||||||
if (!containers[data.sandboxId]) {
|
if (!containers[data.sandboxId]) {
|
||||||
containers[data.sandboxId] = await Sandbox.create();
|
containers[data.sandboxId] = await Sandbox.create();
|
||||||
console.log("Created container ", data.sandboxId);
|
console.log("Created container ", data.sandboxId);
|
||||||
io.emit(
|
|
||||||
"previewURL",
|
|
||||||
"https://" + containers[data.sandboxId].getHostname(5173)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(`Error creating container ${data.sandboxId}:`, e);
|
console.error(`Error creating container ${data.sandboxId}:`, e);
|
||||||
@ -254,6 +269,57 @@ io.on("connection", async (socket) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface CallbackResponse {
|
||||||
|
success: boolean;
|
||||||
|
apps?: string[];
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
"list",
|
||||||
|
async (callback: (response: CallbackResponse) => void) => {
|
||||||
|
console.log("Retrieving apps list...");
|
||||||
|
try {
|
||||||
|
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}...");
|
||||||
|
// 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 {
|
||||||
const size: number = await getProjectSize(data.sandboxId);
|
const size: number = await getProjectSize(data.sandboxId);
|
||||||
@ -422,8 +488,26 @@ io.on("connection", async (socket) => {
|
|||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
terminals[id] = await containers[data.sandboxId].terminal.start({
|
terminals[id] = await containers[data.sandboxId].terminal.start({
|
||||||
onData: (data: string) => {
|
onData: (responseData: string) => {
|
||||||
io.emit("terminalResponse", { id, data });
|
io.emit("terminalResponse", { id, data: responseData });
|
||||||
|
|
||||||
|
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(responseData) ?? "");
|
||||||
|
if (port) {
|
||||||
|
io.emit(
|
||||||
|
"previewURL",
|
||||||
|
"https://" + containers[data.sandboxId].getHostname(port)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
size: { cols: 80, rows: 20 },
|
size: { cols: 80, rows: 20 },
|
||||||
onExit: () => console.log("Terminal exited", id),
|
onExit: () => console.log("Terminal exited", id),
|
||||||
|
@ -21,49 +21,49 @@ const startercode = {
|
|||||||
{
|
{
|
||||||
name: "package.json",
|
name: "package.json",
|
||||||
body: `{
|
body: `{
|
||||||
"name": "react",
|
"name": "react-app",
|
||||||
|
"version": "0.1.0",
|
||||||
"private": true,
|
"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": {
|
"dependencies": {
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react": "^7.34.1",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0"
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
|
||||||
"vite": "^5.2.0"
|
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vite.config.js",
|
name: "public/index.html",
|
||||||
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>
|
body: `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -133,7 +133,7 @@ export default App
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "src/main.jsx",
|
name: "src/index.js",
|
||||||
body: `import React from 'react'
|
body: `import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
@ -6,7 +6,8 @@ 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 { PHProvider } from "./providers"
|
import { TerminalProvider } from '@/context/TerminalContext';
|
||||||
|
import { PreviewProvider } from "@/context/PreviewContext"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
@ -21,7 +22,6 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<ClerkProvider>
|
<ClerkProvider>
|
||||||
<html lang="en" className={`${GeistSans.variable} ${GeistMono.variable}`}>
|
<html lang="en" className={`${GeistSans.variable} ${GeistMono.variable}`}>
|
||||||
<PHProvider>
|
|
||||||
<body>
|
<body>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
@ -29,12 +29,15 @@ export default function RootLayout({
|
|||||||
forcedTheme="dark"
|
forcedTheme="dark"
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
|
<PreviewProvider>
|
||||||
|
<TerminalProvider>
|
||||||
{children}
|
{children}
|
||||||
|
</TerminalProvider>
|
||||||
|
</PreviewProvider>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<Toaster position="bottom-left" richColors />
|
<Toaster position="bottom-left" richColors />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</PHProvider>
|
|
||||||
</html>
|
</html>
|
||||||
</ClerkProvider>
|
</ClerkProvider>
|
||||||
)
|
)
|
||||||
|
@ -31,6 +31,8 @@ import Loading from "./loading"
|
|||||||
import PreviewWindow from "./preview"
|
import PreviewWindow from "./preview"
|
||||||
import Terminals from "./terminals"
|
import Terminals from "./terminals"
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||||
|
import { PreviewProvider, usePreview } from '@/context/PreviewContext';
|
||||||
|
import { useTerminal } from '@/context/TerminalContext';
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
@ -48,8 +50,17 @@ export default function CodeEditor({
|
|||||||
{
|
{
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
}
|
}
|
||||||
);}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Terminalcontext functionsand effects
|
||||||
|
const { setUserAndSandboxId } = useTerminal();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUserAndSandboxId(userData.id, sandboxData.id);
|
||||||
|
}, [userData.id, sandboxData.id, setUserAndSandboxId]);
|
||||||
|
|
||||||
|
//Preview Button state
|
||||||
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
||||||
const [disableAccess, setDisableAccess] = useState({
|
const [disableAccess, setDisableAccess] = useState({
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
@ -315,7 +326,7 @@ export default function CodeEditor({
|
|||||||
console.log(`Saving file...${activeFileId}`);
|
console.log(`Saving file...${activeFileId}`);
|
||||||
console.log(`Saving file...${value}`);
|
console.log(`Saving file...${value}`);
|
||||||
socketRef.current?.emit("saveFile", activeFileId, value);
|
socketRef.current?.emit("saveFile", activeFileId, value);
|
||||||
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY)||1000),
|
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
||||||
[socketRef]
|
[socketRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -383,7 +394,6 @@ export default function CodeEditor({
|
|||||||
);
|
);
|
||||||
|
|
||||||
providerData.binding = binding;
|
providerData.binding = binding;
|
||||||
|
|
||||||
setProvider(providerData.provider);
|
setProvider(providerData.provider);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -415,7 +425,6 @@ export default function CodeEditor({
|
|||||||
// Connection/disconnection effect
|
// Connection/disconnection effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socketRef.current?.connect()
|
socketRef.current?.connect()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socketRef.current?.disconnect()
|
socketRef.current?.disconnect()
|
||||||
}
|
}
|
||||||
@ -423,7 +432,7 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// Socket event listener effect
|
// Socket event listener effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onConnect = () => {}
|
const onConnect = () => { }
|
||||||
|
|
||||||
const onDisconnect = () => {
|
const onDisconnect = () => {
|
||||||
setTerminals([])
|
setTerminals([])
|
||||||
@ -622,7 +631,7 @@ export default function CodeEditor({
|
|||||||
<DisableAccessModal
|
<DisableAccessModal
|
||||||
message={disableAccess.message}
|
message={disableAccess.message}
|
||||||
open={disableAccess.isDisabled}
|
open={disableAccess.isDisabled}
|
||||||
setOpen={() => {}}
|
setOpen={() => { }}
|
||||||
/>
|
/>
|
||||||
<Loading />
|
<Loading />
|
||||||
</>
|
</>
|
||||||
@ -631,6 +640,7 @@ export default function CodeEditor({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Copilot DOM elements */}
|
{/* Copilot DOM elements */}
|
||||||
|
<PreviewProvider>
|
||||||
<div ref={generateRef} />
|
<div ref={generateRef} />
|
||||||
<div className="z-50 p-1" ref={generateWidgetRef}>
|
<div className="z-50 p-1" ref={generateWidgetRef}>
|
||||||
{generate.show && ai ? (
|
{generate.show && ai ? (
|
||||||
@ -801,7 +811,7 @@ export default function CodeEditor({
|
|||||||
<ResizablePanel defaultSize={40}>
|
<ResizablePanel defaultSize={40}>
|
||||||
<ResizablePanelGroup direction="vertical">
|
<ResizablePanelGroup direction="vertical">
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
ref={previewPanelRef}
|
ref={usePreview().previewPanelRef}
|
||||||
defaultSize={4}
|
defaultSize={4}
|
||||||
collapsedSize={4}
|
collapsedSize={4}
|
||||||
minSize={25}
|
minSize={25}
|
||||||
@ -811,13 +821,10 @@ export default function CodeEditor({
|
|||||||
onExpand={() => setIsPreviewCollapsed(false)}
|
onExpand={() => setIsPreviewCollapsed(false)}
|
||||||
>
|
>
|
||||||
<PreviewWindow
|
<PreviewWindow
|
||||||
collapsed={isPreviewCollapsed}
|
|
||||||
open={() => {
|
open={() => {
|
||||||
previewPanelRef.current?.expand()
|
usePreview().previewPanelRef.current?.expand()
|
||||||
setIsPreviewCollapsed(false)
|
setIsPreviewCollapsed(false)
|
||||||
}}
|
} } collapsed={isPreviewCollapsed} src={previewURL}/>
|
||||||
src={previewURL}
|
|
||||||
/>
|
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
@ -826,11 +833,7 @@ export default function CodeEditor({
|
|||||||
className="p-2 flex flex-col"
|
className="p-2 flex flex-col"
|
||||||
>
|
>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<Terminals
|
<Terminals />
|
||||||
terminals={terminals}
|
|
||||||
setTerminals={setTerminals}
|
|
||||||
socket={socketRef.current}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
|
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
|
||||||
<TerminalSquare className="w-4 h-4 mr-2" />
|
<TerminalSquare className="w-4 h-4 mr-2" />
|
||||||
@ -841,6 +844,7 @@ export default function CodeEditor({
|
|||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
</PreviewProvider>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
28
frontend/components/editor/navbar/deploy.tsx
Normal file
28
frontend/components/editor/navbar/deploy.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Play, Pause } from "lucide-react";
|
||||||
|
|
||||||
|
export default function DeployButtonModal() {
|
||||||
|
const [isDeploying, setIsDeploying] = useState(false);
|
||||||
|
|
||||||
|
const handleDeploy = () => {
|
||||||
|
if (isDeploying) {
|
||||||
|
console.log("Stopping deployment...");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log("Starting deployment...");
|
||||||
|
}
|
||||||
|
setIsDeploying(!isDeploying);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button variant="outline" onClick={handleDeploy}>
|
||||||
|
{isDeploying ? <Pause className="w-4 h-4 mr-2" /> : <Play className="w-4 h-4 mr-2" />}
|
||||||
|
{isDeploying ? "Deployed" : "Deploy"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -11,6 +11,8 @@ 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,
|
||||||
@ -19,15 +21,13 @@ export default function Navbar({
|
|||||||
}: {
|
}: {
|
||||||
userData: User;
|
userData: User;
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
shared: {
|
shared: { id: string; name: string }[];
|
||||||
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,14 +62,21 @@ export default function Navbar({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<RunButtonModal
|
||||||
|
isRunning={isRunning}
|
||||||
|
setIsRunning={setIsRunning}
|
||||||
|
/>
|
||||||
<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 />
|
||||||
<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>
|
||||||
|
59
frontend/components/editor/navbar/run.tsx
Normal file
59
frontend/components/editor/navbar/run.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function RunButtonModal({
|
||||||
|
isRunning,
|
||||||
|
setIsRunning,
|
||||||
|
}: {
|
||||||
|
isRunning: boolean;
|
||||||
|
setIsRunning: (running: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const { createNewTerminal, terminals, closeTerminal } = useTerminal();
|
||||||
|
const { setIsPreviewCollapsed, previewPanelRef} = usePreview();
|
||||||
|
|
||||||
|
const handleRun = () => {
|
||||||
|
if (isRunning) {
|
||||||
|
console.log('Stopping sandbox...');
|
||||||
|
console.log('Closing Preview Window');
|
||||||
|
|
||||||
|
terminals.forEach(term => {
|
||||||
|
if (term.terminal) {
|
||||||
|
closeTerminal(term.id);
|
||||||
|
console.log('Closing Terminal', term.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsPreviewCollapsed(true);
|
||||||
|
previewPanelRef.current?.collapse();
|
||||||
|
} else {
|
||||||
|
console.log('Running sandbox...');
|
||||||
|
console.log('Opening Terminal');
|
||||||
|
console.log('Opening Preview Window');
|
||||||
|
|
||||||
|
if (terminals.length < 4) {
|
||||||
|
createNewTerminal("yarn install && yarn start");
|
||||||
|
} else {
|
||||||
|
toast.error("You reached the maximum # of terminals.");
|
||||||
|
console.error('Maximum number of terminals reached.');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPreviewCollapsed(false);
|
||||||
|
previewPanelRef.current?.expand();
|
||||||
|
}
|
||||||
|
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,13 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Globe,
|
|
||||||
Link,
|
Link,
|
||||||
RotateCw,
|
RotateCw,
|
||||||
TerminalSquare,
|
TerminalSquare,
|
||||||
UnfoldVertical,
|
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useRef, useState } from "react"
|
import { useRef, useState } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@ -27,22 +23,22 @@ export default function PreviewWindow({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${collapsed ? "h-full" : "h-10"
|
||||||
collapsed ? "h-full" : "h-10"
|
|
||||||
} select-none w-full flex gap-2`}
|
} 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">
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
<PreviewButton onClick={open}>
|
<PreviewButton disabled onClick={() => { }}>
|
||||||
<UnfoldVertical className="w-4 h-4" />
|
<TerminalSquare className="w-4 h-4" />
|
||||||
</PreviewButton>
|
</PreviewButton>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Todo, make this open inspector */}
|
{/* Removed the unfoldvertical button since we have the same thing via the run button.
|
||||||
{/* <PreviewButton disabled onClick={() => {}}>
|
|
||||||
<TerminalSquare className="w-4 h-4" />
|
<PreviewButton onClick={open}>
|
||||||
|
<UnfoldVertical className="w-4 h-4" />
|
||||||
</PreviewButton> */}
|
</PreviewButton> */}
|
||||||
|
|
||||||
<PreviewButton
|
<PreviewButton
|
||||||
@ -94,8 +90,7 @@ function PreviewButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${disabled ? "pointer-events-none opacity-50" : ""
|
||||||
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}
|
||||||
>
|
>
|
||||||
|
@ -2,35 +2,42 @@
|
|||||||
|
|
||||||
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 { useState } from "react";
|
import { useTerminal } from "@/context/TerminalContext";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function Terminals({
|
export default function Terminals() {
|
||||||
|
const {
|
||||||
terminals,
|
terminals,
|
||||||
setTerminals,
|
setTerminals,
|
||||||
socket,
|
socket,
|
||||||
}: {
|
createNewTerminal,
|
||||||
terminals: { id: string; terminal: Terminal | null }[];
|
closeTerminal,
|
||||||
setTerminals: React.Dispatch<
|
activeTerminalId,
|
||||||
React.SetStateAction<
|
setActiveTerminalId,
|
||||||
{
|
creatingTerminal,
|
||||||
id: string;
|
} = useTerminal();
|
||||||
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">
|
||||||
@ -39,18 +46,7 @@ export default function Terminals({
|
|||||||
key={term.id}
|
key={term.id}
|
||||||
creating={creatingTerminal}
|
creating={creatingTerminal}
|
||||||
onClick={() => setActiveTerminalId(term.id)}
|
onClick={() => setActiveTerminalId(term.id)}
|
||||||
onClose={() =>
|
onClose={() => closeTerminal(term.id)}
|
||||||
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" />
|
||||||
@ -59,18 +55,7 @@ export default function Terminals({
|
|||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
disabled={creatingTerminal}
|
disabled={creatingTerminal}
|
||||||
onClick={() => {
|
onClick={handleCreateTerminal}
|
||||||
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,7 +55,6 @@ 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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,6 +73,20 @@ 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
|
||||||
|
34
frontend/context/PreviewContext.tsx
Normal file
34
frontend/context/PreviewContext.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"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;
|
||||||
|
};
|
118
frontend/context/TerminalContext.tsx
Normal file
118
frontend/context/TerminalContext.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
import { io, Socket } from 'socket.io-client';
|
||||||
|
import { Terminal } from '@xterm/xterm';
|
||||||
|
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
|
||||||
|
|
||||||
|
interface TerminalContextType {
|
||||||
|
socket: Socket | null;
|
||||||
|
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;
|
||||||
|
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
|
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
|
||||||
|
const [activeTerminalId, setActiveTerminalId] = useState<string>('');
|
||||||
|
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false);
|
||||||
|
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(`${window.location.protocol}//${window.location.hostname}:${process.env.NEXT_PUBLIC_SERVER_PORT}?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 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 setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
|
||||||
|
setUserId(newUserId);
|
||||||
|
setSandboxId(newSandboxId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
socket,
|
||||||
|
terminals,
|
||||||
|
setTerminals,
|
||||||
|
activeTerminalId,
|
||||||
|
setActiveTerminalId,
|
||||||
|
creatingTerminal,
|
||||||
|
setCreatingTerminal,
|
||||||
|
createNewTerminal,
|
||||||
|
closeTerminal,
|
||||||
|
setUserAndSandboxId,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
@ -8,6 +8,7 @@ export const createTerminal = ({
|
|||||||
setTerminals,
|
setTerminals,
|
||||||
setActiveTerminalId,
|
setActiveTerminalId,
|
||||||
setCreatingTerminal,
|
setCreatingTerminal,
|
||||||
|
command,
|
||||||
socket,
|
socket,
|
||||||
}: {
|
}: {
|
||||||
setTerminals: React.Dispatch<React.SetStateAction<{
|
setTerminals: React.Dispatch<React.SetStateAction<{
|
||||||
@ -16,6 +17,7 @@ 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;
|
||||||
|
|
||||||
}) => {
|
}) => {
|
||||||
@ -29,6 +31,7 @@ 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);
|
||||||
};
|
};
|
||||||
|
47
tests/index.ts
Normal file
47
tests/index.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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
Normal file
310
tests/package-lock.json
generated
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
tests/package.json
Normal file
21
tests/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
13
tests/tsconfig.json
Normal file
13
tests/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["index.ts"]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user