start terminal ui + logic
This commit is contained in:
parent
9726e7c249
commit
ec900d3d77
18
backend/server/dist/index.js
vendored
18
backend/server/dist/index.js
vendored
@ -18,6 +18,7 @@ const http_1 = require("http");
|
|||||||
const socket_io_1 = require("socket.io");
|
const socket_io_1 = require("socket.io");
|
||||||
const zod_1 = require("zod");
|
const zod_1 = require("zod");
|
||||||
const utils_1 = require("./utils");
|
const utils_1 = require("./utils");
|
||||||
|
const terminal_1 = require("./terminal");
|
||||||
dotenv_1.default.config();
|
dotenv_1.default.config();
|
||||||
const app = (0, express_1.default)();
|
const app = (0, express_1.default)();
|
||||||
const port = process.env.PORT || 4000;
|
const port = process.env.PORT || 4000;
|
||||||
@ -28,6 +29,7 @@ const io = new socket_io_1.Server(httpServer, {
|
|||||||
origin: "*",
|
origin: "*",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const terminals = {};
|
||||||
const handshakeSchema = zod_1.z.object({
|
const handshakeSchema = zod_1.z.object({
|
||||||
userId: zod_1.z.string(),
|
userId: zod_1.z.string(),
|
||||||
sandboxId: zod_1.z.string(),
|
sandboxId: zod_1.z.string(),
|
||||||
@ -70,6 +72,7 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
|
|||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file)
|
if (!file)
|
||||||
return;
|
return;
|
||||||
|
console.log("get file " + file.id + ": ", file.data.slice(0, 10) + "...");
|
||||||
callback(file.data);
|
callback(file.data);
|
||||||
});
|
});
|
||||||
// todo: send diffs + debounce for efficiency
|
// todo: send diffs + debounce for efficiency
|
||||||
@ -88,6 +91,21 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
|
|||||||
file.id = newName;
|
file.id = newName;
|
||||||
yield (0, utils_1.renameFile)(fileId, newName, file.data);
|
yield (0, utils_1.renameFile)(fileId, newName, file.data);
|
||||||
}));
|
}));
|
||||||
|
socket.on("createTerminal", ({ id }) => {
|
||||||
|
console.log("creating terminal (" + id + ")");
|
||||||
|
terminals[id] = new terminal_1.Pty(socket, id);
|
||||||
|
});
|
||||||
|
socket.on("terminalData", ({ id, data }) => {
|
||||||
|
console.log(`Received data for terminal ${id}: ${data}`);
|
||||||
|
if (!terminals[id]) {
|
||||||
|
console.log("terminal not found");
|
||||||
|
console.log("terminals", terminals);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Writing to terminal ${id}`);
|
||||||
|
terminals[id].write(data);
|
||||||
|
});
|
||||||
|
socket.on("disconnect", () => { });
|
||||||
}));
|
}));
|
||||||
httpServer.listen(port, () => {
|
httpServer.listen(port, () => {
|
||||||
console.log(`Server running on port ${port}`);
|
console.log(`Server running on port ${port}`);
|
||||||
|
35
backend/server/dist/terminal.js
vendored
Normal file
35
backend/server/dist/terminal.js
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.Pty = void 0;
|
||||||
|
const node_pty_1 = require("node-pty");
|
||||||
|
const os_1 = __importDefault(require("os"));
|
||||||
|
class Pty {
|
||||||
|
constructor(socket, id) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.shell = os_1.default.platform() === "win32" ? "cmd.exe" : "bash";
|
||||||
|
this.ptyProcess = (0, node_pty_1.spawn)(this.shell, [], {
|
||||||
|
name: "xterm",
|
||||||
|
cols: 100,
|
||||||
|
cwd: `/temp`,
|
||||||
|
// env: process.env as { [key: string]: string },
|
||||||
|
});
|
||||||
|
this.ptyProcess.onData((data) => {
|
||||||
|
console.log("onData", data);
|
||||||
|
this.send(data);
|
||||||
|
});
|
||||||
|
// this.write("hello world")
|
||||||
|
}
|
||||||
|
write(data) {
|
||||||
|
console.log("writing data", data);
|
||||||
|
this.ptyProcess.write(data);
|
||||||
|
}
|
||||||
|
send(data) {
|
||||||
|
this.socket.emit("terminalResponse", {
|
||||||
|
data: Buffer.from(data, "utf-8"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Pty = Pty;
|
15
backend/server/package-lock.json
generated
15
backend/server/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
@ -977,6 +978,11 @@
|
|||||||
"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.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
||||||
|
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw=="
|
||||||
|
},
|
||||||
"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",
|
||||||
@ -985,6 +991,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-pty": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"nan": "^2.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import { Server } from "socket.io"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { User } from "./types"
|
import { User } from "./types"
|
||||||
import { getSandboxFiles, renameFile, saveFile } from "./utils"
|
import { getSandboxFiles, renameFile, saveFile } from "./utils"
|
||||||
|
import { Pty } from "./terminal"
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ const io = new Server(httpServer, {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const terminals: { [id: string]: Pty } = {}
|
||||||
|
|
||||||
const handshakeSchema = z.object({
|
const handshakeSchema = z.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
sandboxId: z.string(),
|
sandboxId: z.string(),
|
||||||
@ -76,6 +79,7 @@ io.on("connection", async (socket) => {
|
|||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
|
||||||
|
console.log("get file " + file.id + ": ", file.data.slice(0, 10) + "...")
|
||||||
callback(file.data)
|
callback(file.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -96,6 +100,26 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await renameFile(fileId, newName, file.data)
|
await renameFile(fileId, newName, file.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on("createTerminal", ({ id }: { id: string }) => {
|
||||||
|
console.log("creating terminal (" + id + ")")
|
||||||
|
terminals[id] = new Pty(socket, id)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on("terminalData", ({ id, data }: { id: string; data: string }) => {
|
||||||
|
console.log(`Received data for terminal ${id}: ${data}`)
|
||||||
|
|
||||||
|
if (!terminals[id]) {
|
||||||
|
console.log("terminal not found")
|
||||||
|
console.log("terminals", terminals)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Writing to terminal ${id}`)
|
||||||
|
terminals[id].write(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
httpServer.listen(port, () => {
|
httpServer.listen(port, () => {
|
||||||
|
49
backend/server/src/terminal.ts
Normal file
49
backend/server/src/terminal.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { spawn, IPty } from "node-pty"
|
||||||
|
import { Socket } from "socket.io"
|
||||||
|
import os from "os"
|
||||||
|
|
||||||
|
export class Pty {
|
||||||
|
socket: Socket
|
||||||
|
ptyProcess: IPty
|
||||||
|
shell: string
|
||||||
|
|
||||||
|
constructor(socket: Socket, id: string) {
|
||||||
|
this.socket = socket
|
||||||
|
this.shell = os.platform() === "win32" ? "cmd.exe" : "bash"
|
||||||
|
|
||||||
|
this.ptyProcess = spawn(this.shell, [], {
|
||||||
|
name: "xterm",
|
||||||
|
cols: 100,
|
||||||
|
cwd: `/temp`,
|
||||||
|
// env: process.env as { [key: string]: string },
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ptyProcess.onData((data) => {
|
||||||
|
console.log("onData", data)
|
||||||
|
this.send(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// this.write("hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
write(data: string) {
|
||||||
|
console.log("writing data", data)
|
||||||
|
|
||||||
|
this.ptyProcess.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: string) {
|
||||||
|
this.socket.emit("terminalResponse", {
|
||||||
|
data: Buffer.from(data, "utf-8"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill() {
|
||||||
|
// console.log("killing terminal")
|
||||||
|
|
||||||
|
// if (os.platform() !== "win32") {
|
||||||
|
// this.ptyProcess.kill()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
@ -25,6 +25,12 @@ import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
|
|||||||
import { io } from "socket.io-client"
|
import { io } from "socket.io-client"
|
||||||
import { processFileType } from "@/lib/utils"
|
import { processFileType } from "@/lib/utils"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
import EditorTerminal from "./terminal"
|
||||||
|
|
||||||
|
import { Terminal } from "@xterm/xterm"
|
||||||
|
import { FitAddon } from "@xterm/addon-fit"
|
||||||
|
|
||||||
|
import { decodeTerminalResponse } from "@/lib/utils"
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userId,
|
userId,
|
||||||
@ -33,6 +39,8 @@ export default function CodeEditor({
|
|||||||
userId: string
|
userId: string
|
||||||
sandboxId: string
|
sandboxId: string
|
||||||
}) {
|
}) {
|
||||||
|
const clerk = useClerk()
|
||||||
|
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||||
|
|
||||||
const handleEditorMount: OnMount = (editor, monaco) => {
|
const handleEditorMount: OnMount = (editor, monaco) => {
|
||||||
@ -73,7 +81,7 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
}, [tabs, activeId])
|
}, [tabs, activeId])
|
||||||
|
|
||||||
// WS event handlers ------------
|
// WS event handlers:
|
||||||
|
|
||||||
// connection/disconnection effect
|
// connection/disconnection effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -86,21 +94,28 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// event listener effect
|
// event listener effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onLoadedEvent(files: (TFolder | TFile)[]) {
|
const onConnect = () => {}
|
||||||
|
|
||||||
|
const onDisconnect = () => {}
|
||||||
|
|
||||||
|
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
||||||
console.log("onLoadedEvent")
|
console.log("onLoadedEvent")
|
||||||
setFiles(files)
|
setFiles(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket.on("connect", onConnect)
|
||||||
|
|
||||||
|
socket.on("disconnect", onDisconnect)
|
||||||
socket.on("loaded", onLoadedEvent)
|
socket.on("loaded", onLoadedEvent)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
socket.off("connect", onConnect)
|
||||||
|
socket.off("disconnect", onDisconnect)
|
||||||
socket.off("loaded", onLoadedEvent)
|
socket.off("loaded", onLoadedEvent)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ------------
|
// Helper functions:
|
||||||
|
|
||||||
const clerk = useClerk()
|
|
||||||
|
|
||||||
const selectFile = (tab: TTab) => {
|
const selectFile = (tab: TTab) => {
|
||||||
setTabs((prev) => {
|
setTabs((prev) => {
|
||||||
@ -274,7 +289,9 @@ export default function CodeEditor({
|
|||||||
<Tab selected>Node</Tab>
|
<Tab selected>Node</Tab>
|
||||||
<Tab>Console</Tab>
|
<Tab>Console</Tab>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full grow rounded-md bg-secondary"></div>
|
<div className="w-full relative grow rounded-md bg-secondary">
|
||||||
|
{socket ? <EditorTerminal socket={socket} /> : null}
|
||||||
|
</div>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
@ -24,7 +24,8 @@ export default function Sidebar({
|
|||||||
const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(null)
|
const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-56 select-none flex flex-col text-sm items-start p-2">
|
<div className="h-full w-56 select-none flex flex-col text-sm items-start justify-between p-2">
|
||||||
|
<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">
|
||||||
@ -80,5 +81,11 @@ export default function Sidebar({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{/* <div className="text-muted-foreground">
|
||||||
|
{connected ? "Connected" : "Not connected"}
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
79
frontend/components/editor/terminal.tsx
Normal file
79
frontend/components/editor/terminal.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Terminal } from "@xterm/xterm"
|
||||||
|
import { FitAddon } from "@xterm/addon-fit"
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
import { Socket } from "socket.io-client"
|
||||||
|
import { decodeTerminalResponse } from "@/lib/utils"
|
||||||
|
|
||||||
|
export default function EditorTerminal({ socket }: { socket: Socket }) {
|
||||||
|
const terminalRef = useRef(null)
|
||||||
|
const [term, setTerm] = useState<Terminal | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!terminalRef.current) return
|
||||||
|
|
||||||
|
const terminal = new Terminal({
|
||||||
|
cursorBlink: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
setTerm(terminal)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (terminal) terminal.dispose()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!term) return
|
||||||
|
|
||||||
|
const onConnect = () => {
|
||||||
|
console.log("Connected to server", socket.connected)
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("sending createTerminal")
|
||||||
|
socket.emit("createTerminal", { id: "testId" })
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTerminalResponse = (data: Buffer) => {
|
||||||
|
console.log("received data", decodeTerminalResponse(data))
|
||||||
|
term.write(decodeTerminalResponse(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on("connect", onConnect)
|
||||||
|
|
||||||
|
if (terminalRef.current) {
|
||||||
|
socket.on("terminalResponse", onTerminalResponse)
|
||||||
|
|
||||||
|
const fitAddon = new FitAddon()
|
||||||
|
term.loadAddon(fitAddon)
|
||||||
|
term.open(terminalRef.current)
|
||||||
|
fitAddon.fit()
|
||||||
|
setTerm(term)
|
||||||
|
}
|
||||||
|
const disposable = term.onData((data) => {
|
||||||
|
console.log("sending data", data)
|
||||||
|
socket.emit("terminalData", {
|
||||||
|
id: "testId",
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.emit("terminalData", {
|
||||||
|
data: "\n",
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off("connect", onConnect)
|
||||||
|
socket.off("terminalResponse", onTerminalResponse)
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
}, [term, terminalRef.current])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div ref={terminalRef} className="w-full h-1/2 text-left"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -14,3 +14,7 @@ export function processFileType(file: string) {
|
|||||||
if (ending) return ending
|
if (ending) return ending
|
||||||
return "plaintext"
|
return "plaintext"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function decodeTerminalResponse(buffer: Buffer): string {
|
||||||
|
return buffer.toString("utf-8")
|
||||||
|
}
|
||||||
|
15
frontend/package-lock.json
generated
15
frontend/package-lock.json
generated
@ -19,6 +19,8 @@
|
|||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@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",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/xterm": "^5.5.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"geist": "^1.3.0",
|
"geist": "^1.3.0",
|
||||||
@ -1454,6 +1456,19 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xterm/addon-fit": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@xterm/xterm": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xterm/xterm": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@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",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/xterm": "^5.5.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"geist": "^1.3.0",
|
"geist": "^1.3.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user