mirror of
https://github.com/stackblitz/bolt.new
synced 2024-11-27 22:42:21 +00:00
95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
|
import type { PathWatcherEvent, WebContainer } from '@webcontainer/api';
|
||
|
import { map } from 'nanostores';
|
||
|
import { bufferWatchEvents } from '../../utils/buffer';
|
||
|
import { WORK_DIR } from '../../utils/constants';
|
||
|
|
||
|
const textDecoder = new TextDecoder('utf8', { fatal: true });
|
||
|
|
||
|
interface File {
|
||
|
type: 'file';
|
||
|
content: string;
|
||
|
}
|
||
|
|
||
|
interface Folder {
|
||
|
type: 'folder';
|
||
|
}
|
||
|
|
||
|
type Dirent = File | Folder;
|
||
|
|
||
|
export type FileMap = Record<string, Dirent | undefined>;
|
||
|
|
||
|
export class FilesStore {
|
||
|
#webcontainer: Promise<WebContainer>;
|
||
|
|
||
|
files = map<FileMap>({});
|
||
|
|
||
|
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||
|
this.#webcontainer = webcontainerPromise;
|
||
|
|
||
|
this.#init();
|
||
|
}
|
||
|
|
||
|
async #init() {
|
||
|
const webcontainer = await this.#webcontainer;
|
||
|
|
||
|
webcontainer.watchPaths(
|
||
|
{ include: [`${WORK_DIR}/**`], exclude: ['**/node_modules', '.git'], includeContent: true },
|
||
|
bufferWatchEvents(100, this.#processEventBuffer.bind(this)),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
|
||
|
const watchEvents = events.flat(2);
|
||
|
|
||
|
for (const { type, path, buffer } of watchEvents) {
|
||
|
// remove any trailing slashes
|
||
|
const sanitizedPath = path.replace(/\/+$/g, '');
|
||
|
|
||
|
switch (type) {
|
||
|
case 'add_dir': {
|
||
|
// we intentionally add a trailing slash so we can distinguish files from folders in the file tree
|
||
|
this.files.setKey(sanitizedPath, { type: 'folder' });
|
||
|
break;
|
||
|
}
|
||
|
case 'remove_dir': {
|
||
|
this.files.setKey(sanitizedPath, undefined);
|
||
|
|
||
|
for (const [direntPath] of Object.entries(this.files)) {
|
||
|
if (direntPath.startsWith(sanitizedPath)) {
|
||
|
this.files.setKey(direntPath, undefined);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case 'add_file':
|
||
|
case 'change': {
|
||
|
this.files.setKey(sanitizedPath, { type: 'file', content: this.#decodeFileContent(buffer) });
|
||
|
break;
|
||
|
}
|
||
|
case 'remove_file': {
|
||
|
this.files.setKey(sanitizedPath, undefined);
|
||
|
break;
|
||
|
}
|
||
|
case 'update_directory': {
|
||
|
// we don't care about these events
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#decodeFileContent(buffer?: Uint8Array) {
|
||
|
if (!buffer || buffer.byteLength === 0) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return textDecoder.decode(buffer);
|
||
|
} catch (error) {
|
||
|
console.log(error);
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
}
|