Files
esengine/packages/editor-app/src/services/RuntimeResolver.ts

220 lines
6.9 KiB
TypeScript
Raw Normal View History

/**
* Runtime Module Resolver
*
*
* Resolves runtime module paths based on environment and configuration
*
*/
import { TauriAPI } from '../api/tauri';
// Sanitize path by removing path traversal sequences and normalizing
const sanitizePath = (path: string): string => {
// Split by path separators, filter out '..' and empty segments, rejoin
const segments = path.split(/[/\\]/).filter((segment) =>
segment !== '..' && segment !== '.' && segment !== ''
);
return segments.join('/');
};
// Check if we're in development mode
const isDevelopment = (): boolean => {
try {
// Vite environment variable
return (import.meta as any).env?.DEV === true;
} catch {
return false;
}
};
export interface RuntimeModule {
type: 'javascript' | 'wasm' | 'binary';
files: string[];
sourcePath: string;
}
export interface RuntimeConfig {
runtime: {
version: string;
modules: Record<string, any>;
};
}
export class RuntimeResolver {
private static instance: RuntimeResolver;
private config: RuntimeConfig | null = null;
private baseDir: string = '';
private constructor() {}
static getInstance(): RuntimeResolver {
if (!RuntimeResolver.instance) {
RuntimeResolver.instance = new RuntimeResolver();
}
return RuntimeResolver.instance;
}
/**
* Initialize the runtime resolver
*
*/
async initialize(): Promise<void> {
// Load runtime configuration
const response = await fetch('/runtime.config.json');
this.config = await response.json();
// Determine base directory based on environment
if (isDevelopment()) {
// In development, use the project root
// We need to go up from src-tauri to get the actual project root
const currentDir = await TauriAPI.getCurrentDir();
// currentDir might be src-tauri, so we need to find the actual workspace root
this.baseDir = await this.findWorkspaceRoot(currentDir);
} else {
// In production, use the resource directory
this.baseDir = await TauriAPI.getAppResourceDir();
}
}
/**
* Find workspace root by looking for package.json or specific markers
* package.json
*/
private async findWorkspaceRoot(startPath: string): Promise<string> {
let currentPath = startPath;
// Try to find the workspace root by looking for key files
// We'll check up to 3 levels up from current directory
for (let i = 0; i < 3; i++) {
// Check if we're in src-tauri
if (currentPath.endsWith('src-tauri')) {
// Go up two levels to get to workspace root
const parts = currentPath.split(/[/\\]/);
parts.pop(); // Remove src-tauri
parts.pop(); // Remove editor-app
parts.pop(); // Remove packages
return parts.join('\\');
}
// Check for workspace markers
const workspaceMarkers = [
`${currentPath}\\pnpm-workspace.yaml`,
`${currentPath}\\packages\\editor-app`,
`${currentPath}\\packages\\platform-web`
];
for (const marker of workspaceMarkers) {
if (await TauriAPI.pathExists(marker)) {
return currentPath;
}
}
// Go up one level
const parts = currentPath.split(/[/\\]/);
parts.pop();
currentPath = parts.join('\\');
}
// Fallback to current directory
return startPath;
}
/**
* Get runtime module files
*
*/
async getModuleFiles(moduleName: string): Promise<RuntimeModule> {
if (!this.config) {
await this.initialize();
}
const moduleConfig = this.config!.runtime.modules[moduleName];
if (!moduleConfig) {
throw new Error(`Runtime module ${moduleName} not found in configuration`);
}
const isDev = isDevelopment();
const files: string[] = [];
let sourcePath: string;
if (isDev) {
// Development mode - use relative paths from workspace root
const devPath = moduleConfig.development.path;
sourcePath = `${this.baseDir}\\packages\\${sanitizePath(devPath)}`;
if (moduleConfig.main) {
files.push(`${sourcePath}\\${moduleConfig.main}`);
}
if (moduleConfig.files) {
for (const file of moduleConfig.files) {
files.push(`${sourcePath}\\${file}`);
}
}
} else {
// Production mode - files are bundled with the app
sourcePath = this.baseDir;
if (moduleConfig.main) {
files.push(`${sourcePath}\\${moduleConfig.main}`);
}
if (moduleConfig.files) {
for (const file of moduleConfig.files) {
files.push(`${sourcePath}\\${file}`);
}
}
}
return {
type: moduleConfig.type,
files,
sourcePath
};
}
/**
* Prepare runtime files for browser preview
*
*/
async prepareRuntimeFiles(targetDir: string): Promise<void> {
// Ensure target directory exists
const dirExists = await TauriAPI.pathExists(targetDir);
if (!dirExists) {
await TauriAPI.createDirectory(targetDir);
}
// Copy platform-web runtime
const platformWeb = await this.getModuleFiles('platform-web');
for (const srcFile of platformWeb.files) {
const filename = srcFile.split(/[/\\]/).pop() || '';
const dstFile = `${targetDir}\\${filename}`;
if (await TauriAPI.pathExists(srcFile)) {
await TauriAPI.copyFile(srcFile, dstFile);
} else {
throw new Error(`Runtime file not found: ${srcFile}`);
}
}
// Copy engine WASM files
const engine = await this.getModuleFiles('engine');
for (const srcFile of engine.files) {
const filename = srcFile.split(/[/\\]/).pop() || '';
const dstFile = `${targetDir}\\${filename}`;
if (await TauriAPI.pathExists(srcFile)) {
await TauriAPI.copyFile(srcFile, dstFile);
} else {
throw new Error(`Engine file not found: ${srcFile}`);
}
}
}
/**
* Get workspace root directory
*
*/
getBaseDir(): string {
return this.baseDir;
}
}