feat: added bolt dedicated shell
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { WebContainer } from '@webcontainer/api';
|
||||
import { map, type MapStore } from 'nanostores';
|
||||
import { WebContainer, type WebContainerProcess } from '@webcontainer/api';
|
||||
import { atom, map, type MapStore } from 'nanostores';
|
||||
import * as nodePath from 'node:path';
|
||||
import type { BoltAction } from '~/types/actions';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
import { unreachable } from '~/utils/unreachable';
|
||||
import type { ActionCallbackData } from './message-parser';
|
||||
import type { ITerminal } from '~/types/terminal';
|
||||
import type { BoltShell } from '~/utils/shell';
|
||||
|
||||
const logger = createScopedLogger('ActionRunner');
|
||||
|
||||
@@ -36,11 +38,14 @@ type ActionsMap = MapStore<Record<string, ActionState>>;
|
||||
export class ActionRunner {
|
||||
#webcontainer: Promise<WebContainer>;
|
||||
#currentExecutionPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
#shellTerminal: () => BoltShell;
|
||||
runnerId = atom<string>(`${Date.now()}`);
|
||||
actions: ActionsMap = map({});
|
||||
|
||||
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||||
constructor(webcontainerPromise: Promise<WebContainer>, getShellTerminal: () => BoltShell) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
this.#shellTerminal = getShellTerminal;
|
||||
|
||||
}
|
||||
|
||||
addAction(data: ActionCallbackData) {
|
||||
@@ -110,6 +115,10 @@ export class ActionRunner {
|
||||
await this.#runFileAction(action);
|
||||
break;
|
||||
}
|
||||
case 'start': {
|
||||
await this.#runStartAction(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.#updateAction(actionId, { status: action.abortSignal.aborted ? 'aborted' : 'complete' });
|
||||
@@ -125,28 +134,35 @@ export class ActionRunner {
|
||||
if (action.type !== 'shell') {
|
||||
unreachable('Expected shell action');
|
||||
}
|
||||
const shell = this.#shellTerminal()
|
||||
await shell.ready()
|
||||
if (!shell || !shell.terminal || !shell.process) {
|
||||
unreachable('Shell terminal not found');
|
||||
}
|
||||
const resp = await shell.executeCommand(this.runnerId.get(), action.content)
|
||||
if (resp?.exitCode != 0) {
|
||||
throw new Error("Failed To Start Application");
|
||||
|
||||
const webcontainer = await this.#webcontainer;
|
||||
}
|
||||
}
|
||||
|
||||
const process = await webcontainer.spawn('jsh', ['-c', action.content], {
|
||||
env: { npm_config_yes: true },
|
||||
});
|
||||
async #runStartAction(action: ActionState) {
|
||||
if (action.type !== 'start') {
|
||||
unreachable('Expected shell action');
|
||||
}
|
||||
if (!this.#shellTerminal) {
|
||||
unreachable('Shell terminal not found');
|
||||
}
|
||||
const shell = this.#shellTerminal()
|
||||
await shell.ready()
|
||||
if (!shell || !shell.terminal || !shell.process) {
|
||||
unreachable('Shell terminal not found');
|
||||
}
|
||||
const resp = await shell.executeCommand(this.runnerId.get(), action.content)
|
||||
if (resp?.exitCode != 0) {
|
||||
throw new Error("Failed To Start Application");
|
||||
|
||||
action.abortSignal.addEventListener('abort', () => {
|
||||
process.kill();
|
||||
});
|
||||
|
||||
process.output.pipeTo(
|
||||
new WritableStream({
|
||||
write(data) {
|
||||
console.log(data);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const exitCode = await process.exit;
|
||||
|
||||
logger.debug(`Process terminated with code ${exitCode}`);
|
||||
}
|
||||
}
|
||||
|
||||
async #runFileAction(action: ActionState) {
|
||||
@@ -177,6 +193,23 @@ export class ActionRunner {
|
||||
logger.error('Failed to write file\n\n', error);
|
||||
}
|
||||
}
|
||||
async getCurrentExecutionResult(output: ReadableStreamDefaultReader<string>) {
|
||||
let fullOutput = '';
|
||||
let exitCode: number = 0;
|
||||
while (true) {
|
||||
const { value, done } = await output.read();
|
||||
if (done) break;
|
||||
const text = value || '';
|
||||
fullOutput += text;
|
||||
// Check if command completion signal with exit code
|
||||
const exitMatch = fullOutput.match(/\]654;exit=-?\d+:(\d+)/);
|
||||
if (exitMatch) {
|
||||
exitCode = parseInt(exitMatch[1], 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { output: fullOutput, exitCode };
|
||||
}
|
||||
|
||||
#updateAction(id: string, newState: ActionStateUpdate) {
|
||||
const actions = this.actions.get();
|
||||
|
||||
Reference in New Issue
Block a user