MCPcopy
hub / github.com/coder/mux / exec

Method exec

src/node/runtime/DevcontainerRuntime.ts:588–713  ·  view source on GitHub ↗

* Execute a command inside the devcontainer. * Overrides LocalBaseRuntime.exec() to use `devcontainer exec`.

(command: string, options: ExecOptions)

Source from the content-addressed store, hash-verified

586 * Overrides LocalBaseRuntime.exec() to use `devcontainer exec`.
587 */
588 override exec(command: string, options: ExecOptions): Promise<ExecStream> {
589 const startTime = performance.now();
590
591 // Short-circuit if already aborted
592 if (options.abortSignal?.aborted) {
593 throw new RuntimeError("Operation aborted before execution", "exec");
594 }
595
596 // Build devcontainer exec args
597 const workspaceFolder = this.currentWorkspacePath;
598 if (!workspaceFolder) {
599 throw new RuntimeError("Devcontainer not initialized. Call ensureReady() first.", "exec");
600 }
601
602 const args = ["exec", "--workspace-folder", workspaceFolder];
603
604 if (this.configPath) {
605 args.push("--config", this.configPath);
606 }
607
608 // Merge cached container credential env + caller env + non-interactive vars.
609 // Spread order: container env (lowest) < caller env < NON_INTERACTIVE (highest).
610 const envVars = { ...this.containerEnv, ...options.env, ...NON_INTERACTIVE_ENV_VARS };
611 for (const [key, value] of Object.entries(envVars)) {
612 args.push("--remote-env", `${key}=${value}`);
613 }
614
615 // Build the full command with cd
616 // Map host workspace path to container path; fall back to container workspace if unmappable
617 const mappedCwd = options.cwd ? this.mapHostPathToContainer(options.cwd) : null;
618 const cwd = mappedCwd ?? this.resolveContainerCwd(options.cwd, workspaceFolder);
619 const fullCommand = `cd ${shescape.quote(cwd)} && ${command}`;
620 args.push("--", "bash", "-c", fullCommand);
621
622 const childProcess = spawn("devcontainer", args, {
623 stdio: ["pipe", "pipe", "pipe"],
624 detached: true,
625 windowsHide: true,
626 cwd: workspaceFolder,
627 });
628
629 const disposable = new DisposableProcess(childProcess);
630
631 // Register cleanup to kill process tree and force-close stdio on timeout/abort.
632 disposable.addCleanup(() => {
633 if (childProcess.pid === undefined) return;
634 killProcessTree(childProcess.pid);
635 forceCloseStdio(childProcess);
636 });
637
638 // Convert Node.js streams to Web Streams (casts required for ExecStream compatibility)
639 /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
640 const stdout = Readable.toWeb(childProcess.stdout!) as unknown as ReadableStream<Uint8Array>;
641 const stderr = Readable.toWeb(childProcess.stderr!) as unknown as ReadableStream<Uint8Array>;
642 const stdin = Writable.toWeb(childProcess.stdin!) as unknown as WritableStream<Uint8Array>;
643 /* eslint-enable @typescript-eslint/no-unnecessary-type-assertion */
644
645 let timedOut = false;

Callers 6

setupCredentialsMethod · 0.95
fetchRemoteHomeMethod · 0.95
readFileViaExecMethod · 0.95
getExecStreamMethod · 0.95
ensureDirViaExecMethod · 0.95
statViaExecMethod · 0.95

Calls 13

resolveContainerCwdMethod · 0.95
addCleanupMethod · 0.95
killProcessTreeFunction · 0.90
forceCloseStdioFunction · 0.90
rejectFunction · 0.85
onMethod · 0.80
removeEventListenerMethod · 0.80
resolveMethod · 0.80
abortHandlerFunction · 0.70
pushMethod · 0.65
resolveFunction · 0.50

Tested by

no test coverage detected