Merge branch 'main' into code-streaming

This commit is contained in:
Anirban Kar
2024-11-12 23:20:30 +05:30
committed by GitHub
30 changed files with 6269 additions and 491 deletions

View File

@@ -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) {
@@ -113,11 +118,16 @@ export class ActionRunner {
await this.#runFileAction(action);
break;
}
case 'start': {
await this.#runStartAction(action)
break;
}
}
this.#updateAction(actionId, { status: isStreaming ? 'running' : action.abortSignal.aborted ? 'aborted' : 'complete' });
} catch (error) {
this.#updateAction(actionId, { status: 'failed', error: 'Action failed' });
logger.error(`[${action.type}]:Action failed\n\n`, error);
// re-throw the error to be caught in the promise chain
throw error;
@@ -128,28 +138,38 @@ 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)
logger.debug(`${action.type} Shell Response: [exit code:${resp?.exitCode}]`)
if (resp?.exitCode != 0) {
throw new Error("Failed To Execute Shell Command");
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)
logger.debug(`${action.type} Shell Response: [exit code:${resp?.exitCode}]`)
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}`);
if (resp?.exitCode != 0) {
throw new Error("Failed To Start Application");
}
return resp
}
async #runFileAction(action: ActionState) {
@@ -180,7 +200,6 @@ export class ActionRunner {
logger.error('Failed to write file\n\n', error);
}
}
#updateAction(id: string, newState: ActionStateUpdate) {
const actions = this.actions.get();