// The coding tools see only world paths. Rust owns the VFS and process execution. import { LineScanner } from '@earendil-works/pi-durable/env'; const ops = Deno.core.ops; const ok = (value: any) => ({ ok: true, value }); const path = (cwd: string, input: string) => { const parts: string[] = []; for (const p of (input.startsWith('/') ? input : `${cwd}/${input}`).split('/')) { if (p === '..') parts.pop(); else if (p && p !== '.') parts.push(p); } return '/' + parts.join('/'); }; export function environment(cwd = '/') { const call = (kind: string, p: string, bytes: number[] = []) => ops.op_file(kind, path(cwd, p), bytes); const env: any = { id: 'world', cwd, absolutePath: async (p: string) => ok(path(cwd, p)), canonicalPath: async () => ({ ok: false, error: { code: 'not_supported', message: 'The VFS does not expose canonical paths' } }), joinPath: async (parts: string[]) => ok(path(cwd, parts.join('/'))), fileInfo: (p: string) => call('stat', p), exists: async (p: string) => { const result = await call('stat', p); return result.ok ? ok(true) : result.error.code === 'not_found' ? ok(false) : result; }, readBinaryFile: async (p: string) => { const r = await call('read', p); return r.ok ? ok(new Uint8Array(r.value)) : r; }, readTextFile: async (p: string) => { const r = await env.readBinaryFile(p); return r.ok ? ok(new TextDecoder().decode(r.value)) : r; }, writeFile: async (p: string, content: string | Uint8Array) => { const full = path(cwd, p); const parent = full.slice(0, full.lastIndexOf('/')) || '/'; const created = await env.createDir(parent, { recursive: true }); if (!created.ok) return created; return call('write', full, Array.from(typeof content === 'string' ? new TextEncoder().encode(content) : content)); }, createDir: async (p: string, options: any) => { const full = path(cwd, p); if (!options?.recursive) return call('mkdir', full); let current = ''; for (const part of full.split('/').filter(Boolean)) { current += '/' + part; const exists = await call('stat', current); if (!exists.ok) { const r = await call('mkdir', current); if (!r.ok) return r; } } return ok(undefined); }, openBinaryReader: async (p: string) => { const data = await env.readBinaryFile(p); if (!data.ok) return data; const info = await env.fileInfo(p); return ok({ info: async () => info, read: async (offset: number, length: number) => ok(data.value.slice(offset, offset + length)), scanLines: async (options: any) => { const scanner = new LineScanner(options.startLine, options.endLine); scanner.push(data.value); return ok(scanner.finish()); }, close: async () => {}, }); }, exec: async (command: any, options: any, context: any) => { try { const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout); const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() }; let result; while (true) { const chunk = await ops.op_exec_next(rid); if (!chunk.stream) { result = chunk; break; } options?.onOutput?.(decoders[chunk.stream].decode(new Uint8Array(chunk.bytes), { stream: true }), context, { stream: chunk.stream }); } if (result.error) throw new Error(result.error); for (const stream of ['stdout', 'stderr']) { const text = decoders[stream].decode(); if (text) options?.onOutput?.(text, context, { stream }); } return ok({ exitCode: result.exitCode }); } catch (error) { return { ok: false, error }; } }, cleanup: async () => {}, }; return env; }