feat(workspace-terminal): use environment shell command

This commit is contained in:
2026-05-25 06:13:11 +00:00
parent 0eb4611ea6
commit fed3247eea
8 changed files with 1492 additions and 877 deletions
@@ -1,5 +1,11 @@
import * as webcontainer from '@tempfix/webcontainer__api';
import type { IExecutionEnvironment, IFileEntry, IFileWatcher, IProcessHandle } from '../interfaces/IExecutionEnvironment.js';
import type {
IExecutionEnvironment,
IFileEntry,
IFileWatcher,
IProcessHandle,
IShellCommand,
} from '../interfaces/IExecutionEnvironment.js';
/**
* WebContainer-based execution environment.
@@ -154,6 +160,14 @@ export class WebContainerEnvironment implements IExecutionEnvironment {
};
}
public getShellCommand(): IShellCommand {
return {
command: 'jsh',
label: 'jsh',
prompt: '~/',
};
}
// ============ WebContainer-specific methods ============
/**
@@ -29,6 +29,17 @@ export interface IProcessHandle {
kill(): void;
}
export interface IShellCommand {
/** Executable to start for an interactive shell session. */
command: string;
/** Optional command arguments. */
args?: string[];
/** Optional display label for terminal tabs. */
label?: string;
/** Optional prompt marker used for setup commands. */
prompt?: string;
}
/**
* Abstract execution environment interface.
* Implementations can target WebContainer (browser), Backend API (server), or Mock (testing).
@@ -93,12 +104,18 @@ export interface IExecutionEnvironment {
/**
* Spawn a new process
* @param command - Command to run (e.g., 'jsh', 'node', 'npm')
* @param command - Command to run (e.g., 'node', 'npm')
* @param args - Optional arguments
* @returns Process handle with I/O streams
*/
spawn(command: string, args?: string[]): Promise<IProcessHandle>;
/**
* Return the environment-native interactive shell command.
* Implementations should provide this when terminal shells need a specific executable.
*/
getShellCommand?(): IShellCommand | Promise<IShellCommand>;
// ============ Lifecycle ============
/**
@@ -12,7 +12,7 @@ import * as domtools from '@design.estate/dees-domtools';
import type { Terminal } from 'xterm';
import { themeDefaultStyles } from '../../00theme.js';
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
import type { IExecutionEnvironment, IShellCommand } from '../../00group-runtime/index.js';
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
import '../../00group-utility/dees-icon/dees-icon.js';
import '../../00group-feedback/dees-actionbar/dees-actionbar.js';
@@ -660,6 +660,22 @@ export class DeesWorkspaceTerminal extends DeesElement {
}
}
private async getShellCommand(): Promise<IShellCommand> {
if (this.executionEnvironment && !this.executionEnvironment.ready) {
await this.executionEnvironment.init();
}
if (this.executionEnvironment?.getShellCommand) {
return await this.executionEnvironment.getShellCommand();
}
return {
command: 'jsh',
label: 'jsh',
prompt: '~/',
};
}
private handleProcessExit(tabId: string, exitCode: number): void {
const tab = this.tabManager.getTab(tabId);
if (!tab) return;
@@ -723,10 +739,11 @@ export class DeesWorkspaceTerminal extends DeesElement {
* Create a new shell tab
*/
public async createShellTab(label?: string): Promise<string> {
const shellCommand = await this.getShellCommand();
const tab = this.tabManager.createTab(
{
type: 'shell',
label: label || `bash ${this.tabManager.getTabCount() + 1}`,
label: label || `${shellCommand.label || shellCommand.command} ${this.tabManager.getTabCount() + 1}`,
closeable: this.tabManager.getTabCount() > 0, // First tab not closeable
},
this.isBright
@@ -739,11 +756,11 @@ export class DeesWorkspaceTerminal extends DeesElement {
// Wait for DOM update then spawn shell
await this.updateComplete;
await this.spawnProcessForTab(tab, 'jsh');
await this.spawnProcessForTab(tab, shellCommand.command, shellCommand.args || []);
// Run setup command if this is the first tab
if (this.tabManager.getTabCount() === 1 && this.setupCommand) {
await this.waitForPrompt(tab.terminal, '~/');
await this.waitForPrompt(tab.terminal, shellCommand.prompt || '~/');
if (tab.inputWriter) {
tab.inputWriter.write(this.setupCommand);
}
@@ -6,7 +6,7 @@ import type { IProcessHandle } from '../../00group-runtime/index.js';
* Type of terminal tab based on its source/purpose
*/
export type TTerminalTabType =
| 'shell' // Default interactive shell (jsh)
| 'shell' // Environment-native interactive shell
| 'script' // Script from package.json
| 'package-update' // Package update process
| 'custom'; // External process from API
@@ -75,7 +75,7 @@ export interface ICreateTerminalTabOptions {
/** Whether the tab can be closed (default: true for non-shell) */
closeable?: boolean;
/** Command to spawn (default: 'jsh' for shell type) */
/** Command to spawn (shell type uses the execution environment shell by default) */
command?: string;
/** Command arguments */