feat(editor/runtime): Replace bare editor with Monaco-based editor and add runtime + workspace/filetree integration
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import * as webcontainer from '@webcontainer/api';
|
||||
import type { IExecutionEnvironment, IFileEntry, IProcessHandle } from '../interfaces/IExecutionEnvironment.js';
|
||||
|
||||
/**
|
||||
* WebContainer-based execution environment.
|
||||
* Runs Node.js and shell commands in the browser using WebContainer API.
|
||||
*/
|
||||
export class WebContainerEnvironment implements IExecutionEnvironment {
|
||||
private container: webcontainer.WebContainer | null = null;
|
||||
private _ready: boolean = false;
|
||||
|
||||
public readonly type = 'webcontainer' as const;
|
||||
|
||||
public get ready(): boolean {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
// ============ Lifecycle ============
|
||||
|
||||
public async init(): Promise<void> {
|
||||
if (this._ready && this.container) {
|
||||
return; // Already initialized
|
||||
}
|
||||
|
||||
// Check if SharedArrayBuffer is available (required for WebContainer)
|
||||
if (typeof SharedArrayBuffer === 'undefined') {
|
||||
throw new Error(
|
||||
'WebContainer requires SharedArrayBuffer which is not available. ' +
|
||||
'Ensure your server sends these headers:\n' +
|
||||
' Cross-Origin-Opener-Policy: same-origin\n' +
|
||||
' Cross-Origin-Embedder-Policy: require-corp'
|
||||
);
|
||||
}
|
||||
|
||||
this.container = await webcontainer.WebContainer.boot();
|
||||
this._ready = true;
|
||||
}
|
||||
|
||||
public async destroy(): Promise<void> {
|
||||
if (this.container) {
|
||||
this.container.teardown();
|
||||
this.container = null;
|
||||
this._ready = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Filesystem Operations ============
|
||||
|
||||
public async readFile(path: string): Promise<string> {
|
||||
this.ensureReady();
|
||||
return await this.container!.fs.readFile(path, 'utf-8');
|
||||
}
|
||||
|
||||
public async writeFile(path: string, contents: string): Promise<void> {
|
||||
this.ensureReady();
|
||||
await this.container!.fs.writeFile(path, contents, 'utf-8');
|
||||
}
|
||||
|
||||
public async readDir(path: string): Promise<IFileEntry[]> {
|
||||
this.ensureReady();
|
||||
const entries = await this.container!.fs.readdir(path, { withFileTypes: true });
|
||||
|
||||
return entries.map((entry) => ({
|
||||
type: entry.isDirectory() ? 'directory' as const : 'file' as const,
|
||||
name: entry.name,
|
||||
path: path === '/' ? `/${entry.name}` : `${path}/${entry.name}`,
|
||||
}));
|
||||
}
|
||||
|
||||
public async mkdir(path: string): Promise<void> {
|
||||
this.ensureReady();
|
||||
await this.container!.fs.mkdir(path, { recursive: true });
|
||||
}
|
||||
|
||||
public async rm(path: string, options?: { recursive?: boolean }): Promise<void> {
|
||||
this.ensureReady();
|
||||
await this.container!.fs.rm(path, { recursive: options?.recursive ?? false });
|
||||
}
|
||||
|
||||
public async exists(path: string): Promise<boolean> {
|
||||
this.ensureReady();
|
||||
try {
|
||||
await this.container!.fs.readFile(path);
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
await this.container!.fs.readdir(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Process Execution ============
|
||||
|
||||
public async spawn(command: string, args: string[] = []): Promise<IProcessHandle> {
|
||||
this.ensureReady();
|
||||
|
||||
const process = await this.container!.spawn(command, args);
|
||||
|
||||
return {
|
||||
output: process.output as unknown as ReadableStream<string>,
|
||||
input: process.input as unknown as { getWriter(): WritableStreamDefaultWriter<string> },
|
||||
exit: process.exit,
|
||||
kill: () => process.kill(),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ WebContainer-specific methods ============
|
||||
|
||||
/**
|
||||
* Mount files into the virtual filesystem.
|
||||
* This is a WebContainer-specific operation.
|
||||
* @param files - File tree structure to mount
|
||||
*/
|
||||
public async mount(files: webcontainer.FileSystemTree): Promise<void> {
|
||||
this.ensureReady();
|
||||
await this.container!.mount(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying WebContainer instance.
|
||||
* Use sparingly - prefer the interface methods.
|
||||
*/
|
||||
public getContainer(): webcontainer.WebContainer {
|
||||
this.ensureReady();
|
||||
return this.container!;
|
||||
}
|
||||
|
||||
// ============ Private Helpers ============
|
||||
|
||||
private ensureReady(): void {
|
||||
if (!this._ready || !this.container) {
|
||||
throw new Error('WebContainerEnvironment not initialized. Call init() first.');
|
||||
}
|
||||
}
|
||||
}
|
||||
1
ts_web/elements/00group-runtime/environments/index.ts
Normal file
1
ts_web/elements/00group-runtime/environments/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './WebContainerEnvironment.js';
|
||||
5
ts_web/elements/00group-runtime/index.ts
Normal file
5
ts_web/elements/00group-runtime/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Runtime Interfaces
|
||||
export * from './interfaces/index.js';
|
||||
|
||||
// Environment Implementations
|
||||
export * from './environments/index.js';
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Represents a file or directory entry in the virtual filesystem
|
||||
*/
|
||||
export interface IFileEntry {
|
||||
type: 'file' | 'directory';
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle to a spawned process with I/O streams
|
||||
*/
|
||||
export interface IProcessHandle {
|
||||
/** Stream of output data from the process */
|
||||
output: ReadableStream<string>;
|
||||
/** Input stream to write data to the process */
|
||||
input: { getWriter(): WritableStreamDefaultWriter<string> };
|
||||
/** Promise that resolves with exit code when process terminates */
|
||||
exit: Promise<number>;
|
||||
/** Kill the process */
|
||||
kill(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract execution environment interface.
|
||||
* Implementations can target WebContainer (browser), Backend API (server), or Mock (testing).
|
||||
*/
|
||||
export interface IExecutionEnvironment {
|
||||
// ============ Filesystem Operations ============
|
||||
|
||||
/**
|
||||
* Read the contents of a file
|
||||
* @param path - Absolute path to the file
|
||||
* @returns File contents as string
|
||||
*/
|
||||
readFile(path: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* Write contents to a file (creates or overwrites)
|
||||
* @param path - Absolute path to the file
|
||||
* @param contents - String contents to write
|
||||
*/
|
||||
writeFile(path: string, contents: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* List contents of a directory
|
||||
* @param path - Absolute path to the directory
|
||||
* @returns Array of file entries
|
||||
*/
|
||||
readDir(path: string): Promise<IFileEntry[]>;
|
||||
|
||||
/**
|
||||
* Create a directory (and parent directories if needed)
|
||||
* @param path - Absolute path to create
|
||||
*/
|
||||
mkdir(path: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove a file or directory
|
||||
* @param path - Absolute path to remove
|
||||
* @param options - Optional: { recursive: true } for directories
|
||||
*/
|
||||
rm(path: string, options?: { recursive?: boolean }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Check if a path exists
|
||||
* @param path - Absolute path to check
|
||||
*/
|
||||
exists(path: string): Promise<boolean>;
|
||||
|
||||
// ============ Process Execution ============
|
||||
|
||||
/**
|
||||
* Spawn a new process
|
||||
* @param command - Command to run (e.g., 'jsh', 'node', 'npm')
|
||||
* @param args - Optional arguments
|
||||
* @returns Process handle with I/O streams
|
||||
*/
|
||||
spawn(command: string, args?: string[]): Promise<IProcessHandle>;
|
||||
|
||||
// ============ Lifecycle ============
|
||||
|
||||
/**
|
||||
* Initialize the environment (e.g., boot WebContainer)
|
||||
* Must be called before any other operations
|
||||
*/
|
||||
init(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Destroy the environment and clean up resources
|
||||
*/
|
||||
destroy(): Promise<void>;
|
||||
|
||||
// ============ State ============
|
||||
|
||||
/** Whether the environment has been initialized and is ready */
|
||||
readonly ready: boolean;
|
||||
|
||||
/** Type identifier for the environment implementation */
|
||||
readonly type: 'webcontainer' | 'backend' | 'mock';
|
||||
}
|
||||
1
ts_web/elements/00group-runtime/interfaces/index.ts
Normal file
1
ts_web/elements/00group-runtime/interfaces/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './IExecutionEnvironment.js';
|
||||
Reference in New Issue
Block a user