Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion src/components/FileManager.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useStore } from '@nanostores/react'
import { useStore } from '@nanostores/react';
import tutorialStore from 'tutorialkit:store';
import { useRef, useEffect } from 'react';
import { webcontainer } from 'tutorialkit:core';
import type { WebContainer } from '@webcontainer/api';
import { RAILS_WASM_PACKAGE_VERSION } from '../templates/default/lib/constants';

export function FileManager() {
const files = useStore(tutorialStore.files);
const processedFiles = useRef(new Set<string>());
const wasmCached = useRef(false);
const cachingInterval = useRef<number | null>(null);
const VERSIONED_RAILS_WASM_FILE_NAME = `rails-${RAILS_WASM_PACKAGE_VERSION}.wasm`;

async function chmodx(wc: WebContainer, path: string) {
const process = await wc.spawn('chmod', ['+x', path]);
Expand All @@ -20,12 +24,69 @@ export function FileManager() {
}
}

async function fetchCachedWasmFile(): Promise<Uint8Array | null> {
try {
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle(VERSIONED_RAILS_WASM_FILE_NAME);
const file = await fileHandle.getFile();
console.log(`Found cached WASM version ${RAILS_WASM_PACKAGE_VERSION}`);
return new Uint8Array(await file.arrayBuffer());
} catch {
return null;
}
}

async function persistWasmFile(wasmData: Uint8Array): Promise<void> {
try {
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle(VERSIONED_RAILS_WASM_FILE_NAME, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(wasmData);
await writable.close();
console.log(`Rails WASM v${RAILS_WASM_PACKAGE_VERSION} cached`);
} catch (error) {
console.error('Failed to persist Rails WASM:', error);
}
}

async function cacheWasmFile(wc: WebContainer): Promise<void> {
if (cachingInterval.current) return;

console.log(`Caching WASM file v${RAILS_WASM_PACKAGE_VERSION}...`);

cachingInterval.current = window.setInterval(async () => {
try {
const wasmData = await wc.fs.readFile('/node_modules/@rails-tutorial/wasm/dist/rails.wasm');
if (wasmData && wasmData.length > 0) {
clearInterval(cachingInterval.current!);
cachingInterval.current = null;

await persistWasmFile(wasmData);
wasmCached.current = true;
}
} catch (error) {
// File not ready yet, continue checking
}
}, 1000);
}

useEffect(() => {
if (!files) return;

(async () => {
const wc = await webcontainer;

if (!wasmCached.current) {
const cachedWasm = await fetchCachedWasmFile();
if (cachedWasm) {
await wc.fs.writeFile(VERSIONED_RAILS_WASM_FILE_NAME, cachedWasm);
console.log(`Rails WASM v${RAILS_WASM_PACKAGE_VERSION} loaded from cache`);
wasmCached.current = true;
} else {
await cacheWasmFile(wc);
}
}

Object.entries(files).forEach(([_, fd]) => {
const dir = fd.path.split('/').filter(Boolean).slice(-2, -1)[0];
if (dir === "bin" && !processedFiles.current.has(fd.path)) {
Expand All @@ -34,6 +95,13 @@ export function FileManager() {
}
});
})();

return () => {
if (cachingInterval.current) {
clearInterval(cachingInterval.current);
cachingInterval.current = null;
}
};
}, [files]);

return null;
Expand Down
1 change: 1 addition & 0 deletions src/templates/default/lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const RAILS_WASM_PACKAGE_VERSION = '8.0.2-rc.1';
2 changes: 1 addition & 1 deletion src/templates/default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"preinstall": "node scripts/preinstall.js",
"dev": "vite",
"test": "node --disable-warning=ExperimentalWarning ./scripts/test.js",
"test:watch": "node --disable-warning=ExperimentalWarning --watch --watch-path=./workspace ./scripts/test.js",
Expand All @@ -12,7 +13,6 @@
"dependencies": {
"@electric-sql/pglite": "^0.3.1",
"@ruby/wasm-wasi": "^2.7.1",
"@rails-tutorial/wasm": "8.0.2-rc.1",
"express": "^5.1.0",
"multer": "^2.0.1",
"set-cookie-parser": "^2.7.1"
Expand Down
63 changes: 63 additions & 0 deletions src/templates/default/scripts/preinstall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node

import fs from 'fs/promises';
import path from 'path';
import { execSync } from 'child_process';
import { RAILS_WASM_PACKAGE_VERSION } from '../lib/constants.js';
const VERSIONED_RAILS_WASM_PATH = `./rails-${RAILS_WASM_PACKAGE_VERSION}.wasm`;
const TARGET_DIR = 'node_modules/@rails-tutorial/wasm/dist';
const TARGET_FILE = path.join(TARGET_DIR, 'rails.wasm');

async function checkIfFileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}

async function moveFile(src, dest) {
try {
await fs.mkdir(path.dirname(dest), { recursive: true });
await fs.rename(src, dest);
console.log(`✓ Moved ${src} to ${dest}`);
return true;
} catch (error) {
console.error(`✗ Failed to move ${src} to ${dest}:`, error.message);
return false;
}
}

async function installPackage(packageName, version) {
try {
console.log(`Installing ${packageName}@${version}...`);
execSync(`npm install ${packageName}@${version}`, { stdio: 'inherit' });
console.log(`✓ Installed ${packageName}@${version}`);
return true;
} catch (error) {
console.error(`✗ Failed to install ${packageName}@${version}:`, error.message);
return false;
}
}

async function main() {
const versionedWasmExists = await checkIfFileExists(VERSIONED_RAILS_WASM_PATH);

if (versionedWasmExists) {
const success = await moveFile(VERSIONED_RAILS_WASM_PATH, TARGET_FILE);
if (!success) {
process.exit(1);
}
} else {
const success = await installPackage('@rails-tutorial/wasm', RAILS_WASM_PACKAGE_VERSION);
if (!success) {
process.exit(1);
}
}
}

main().catch(error => {
console.error('Preinstall script failed:', error);
process.exit(1);
});