merge with upstream/main

This commit is contained in:
Andrew Trokhymenko
2024-11-29 22:02:35 -05:00
38 changed files with 1463 additions and 952 deletions

View File

@@ -80,10 +80,6 @@ export class FilesStore {
this.#modifiedFiles.clear();
}
markFileAsNew(filePath: string) {
this.#modifiedFiles.set(filePath, '');
}
async saveFile(filePath: string, content: string) {
const webcontainer = await this.#webcontainer;
@@ -216,9 +212,5 @@ function isBinaryFile(buffer: Uint8Array | undefined) {
* array buffer.
*/
function convertToBuffer(view: Uint8Array): Buffer {
const buffer = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
Object.setPrototypeOf(buffer, Buffer.prototype);
return buffer as Buffer;
return Buffer.from(view.buffer, view.byteOffset, view.byteLength);
}

View File

@@ -7,7 +7,7 @@ import { coloredText } from '~/utils/terminal';
export class TerminalStore {
#webcontainer: Promise<WebContainer>;
#terminals: Array<{ terminal: ITerminal; process: WebContainerProcess }> = [];
#boltTerminal = newBoltShellProcess()
#boltTerminal = newBoltShellProcess();
showTerminal: WritableAtom<boolean> = import.meta.hot?.data.showTerminal ?? atom(true);
@@ -27,8 +27,8 @@ export class TerminalStore {
}
async attachBoltTerminal(terminal: ITerminal) {
try {
let wc = await this.#webcontainer
await this.#boltTerminal.init(wc, terminal)
const wc = await this.#webcontainer;
await this.#boltTerminal.init(wc, terminal);
} catch (error: any) {
terminal.write(coloredText.red('Failed to spawn bolt shell\n\n') + error.message);
return;

View File

@@ -11,9 +11,8 @@ import { PreviewsStore } from './previews';
import { TerminalStore } from './terminal';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
import { Octokit, type RestEndpointMethodTypes } from '@octokit/rest';
import * as nodePath from 'node:path';
import type { WebContainerProcess } from '@webcontainer/api';
import { extractRelativePath } from '~/utils/diff';
export interface ArtifactState {
@@ -32,7 +31,6 @@ export type WorkbenchViewType = 'code' | 'preview';
export class WorkbenchStore {
#previewsStore = new PreviewsStore(webcontainer);
#filesStore = new FilesStore(webcontainer);
#editorStore = new EditorStore(this.#filesStore);
#terminalStore = new TerminalStore(webcontainer);
@@ -43,7 +41,6 @@ export class WorkbenchStore {
unsavedFiles: WritableAtom<Set<string>> = import.meta.hot?.data.unsavedFiles ?? atom(new Set<string>());
modifiedFiles = new Set<string>();
artifactIdList: string[] = [];
#boltTerminal: { terminal: ITerminal; process: WebContainerProcess } | undefined;
#globalExecutionQueue = Promise.resolve();
constructor() {
if (import.meta.hot) {
@@ -55,7 +52,7 @@ export class WorkbenchStore {
}
addToExecutionQueue(callback: () => Promise<void>) {
this.#globalExecutionQueue = this.#globalExecutionQueue.then(() => callback())
this.#globalExecutionQueue = this.#globalExecutionQueue.then(() => callback());
}
get previews() {
@@ -97,7 +94,6 @@ export class WorkbenchStore {
this.#terminalStore.attachTerminal(terminal);
}
attachBoltTerminal(terminal: ITerminal) {
this.#terminalStore.attachBoltTerminal(terminal);
}
@@ -262,7 +258,8 @@ export class WorkbenchStore {
this.artifacts.setKey(messageId, { ...artifact, ...state });
}
addAction(data: ActionCallbackData) {
this._addAction(data)
this._addAction(data);
// this.addToExecutionQueue(()=>this._addAction(data))
}
async _addAction(data: ActionCallbackData) {
@@ -279,10 +276,9 @@ export class WorkbenchStore {
runAction(data: ActionCallbackData, isStreaming: boolean = false) {
if (isStreaming) {
this._runAction(data, isStreaming)
}
else {
this.addToExecutionQueue(() => this._runAction(data, isStreaming))
this._runAction(data, isStreaming);
} else {
this.addToExecutionQueue(() => this._runAction(data, isStreaming));
}
}
async _runAction(data: ActionCallbackData, isStreaming: boolean = false) {
@@ -293,16 +289,21 @@ export class WorkbenchStore {
if (!artifact) {
unreachable('Artifact not found');
}
if (data.action.type === 'file') {
let wc = await webcontainer
const wc = await webcontainer;
const fullPath = nodePath.join(wc.workdir, data.action.filePath);
if (this.selectedFile.value !== fullPath) {
this.setSelectedFile(fullPath);
}
if (this.currentView.value !== 'code') {
this.currentView.set('code');
}
const doc = this.#editorStore.documents.get()[fullPath];
if (!doc) {
await artifact.runner.runAction(data, isStreaming);
}
@@ -382,63 +383,7 @@ export class WorkbenchStore {
return syncedFiles;
}
async uploadFilesFromDisk(sourceHandle: FileSystemDirectoryHandle) {
const loadedFiles = [];
const wc = await webcontainer;
const newFiles = {};
const processDirectory = async (handle: FileSystemDirectoryHandle, currentPath: string = '') => {
const entries = await Array.fromAsync(handle.values());
for (const entry of entries) {
const entryPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
const fullPath = `/${entryPath}`;
if (entry.kind === 'directory') {
await wc.fs.mkdir(fullPath, { recursive: true });
const subDirHandle = await handle.getDirectoryHandle(entry.name);
await processDirectory(subDirHandle, entryPath);
} else {
const file = await entry.getFile();
const content = await file.text();
// Write to WebContainer
await wc.fs.writeFile(fullPath, content);
// Mark file as new
this.#filesStore.markFileAsNew(fullPath);
// Update the files store with the current content
this.files.setKey(fullPath, { type: 'file', content, isBinary: false });
// Collect for editor store with actual content
newFiles[fullPath] = { type: 'file', content, isBinary: false };
loadedFiles.push(entryPath);
}
}
}
await processDirectory(sourceHandle);
return loadedFiles;
}
async refreshFiles() {
// Clear old state
this.modifiedFiles = new Set<string>();
this.artifactIdList = [];
// Reset stores
this.#filesStore = new FilesStore(webcontainer);
this.#editorStore = new EditorStore(this.#filesStore);
// Update UI state
this.currentView.set('code');
this.unsavedFiles.set(new Set<string>());
}
async pushToGitHub(repoName: string, githubUsername: string, ghToken: string) {
try {
// Get the GitHub auth token from environment variables
const githubToken = ghToken;
@@ -453,10 +398,11 @@ export class WorkbenchStore {
const octokit = new Octokit({ auth: githubToken });
// Check if the repository already exists before creating it
let repo: RestEndpointMethodTypes["repos"]["get"]["response"]['data']
let repo: RestEndpointMethodTypes['repos']['get']['response']['data'];
try {
let resp = await octokit.repos.get({ owner: owner, repo: repoName });
repo = resp.data
const resp = await octokit.repos.get({ owner, repo: repoName });
repo = resp.data;
} catch (error) {
if (error instanceof Error && 'status' in error && error.status === 404) {
// Repository doesn't exist, so create a new one
@@ -474,6 +420,7 @@ export class WorkbenchStore {
// Get all files
const files = this.files.get();
if (!files || Object.keys(files).length === 0) {
throw new Error('No files found to push');
}
@@ -490,7 +437,9 @@ export class WorkbenchStore {
});
return { path: extractRelativePath(filePath), sha: blob.sha };
}
})
return null;
}),
);
const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs
@@ -542,21 +491,6 @@ export class WorkbenchStore {
console.error('Error pushing to GitHub:', error instanceof Error ? error.message : String(error));
}
}
async markFileAsModified(filePath: string) {
const file = this.#filesStore.getFile(filePath);
if (file?.type === 'file') {
// First collect all original content
const originalContent = file.content;
console.log(`Processing ${filePath}:`, originalContent);
// Then save modifications
await this.saveFile(filePath, originalContent);
}
}
}
export const workbenchStore = new WorkbenchStore();