(options: LocalProcessHandleOptions)
| 162 | private readonly envVars: Record<string, string> = {} |
| 163 | |
| 164 | constructor(options: LocalProcessHandleOptions) { |
| 165 | this.root = options.root |
| 166 | this.id = options.root |
| 167 | this.options = options |
| 168 | |
| 169 | this.fs = { |
| 170 | read: async (p) => fsp.readFile(this.resolve(p), 'utf8'), |
| 171 | readBytes: async (p) => |
| 172 | new Uint8Array(await fsp.readFile(this.resolve(p))), |
| 173 | write: async (p, data) => { |
| 174 | const target = this.resolve(p) |
| 175 | await fsp.mkdir(path.dirname(target), { recursive: true }) |
| 176 | await fsp.writeFile( |
| 177 | target, |
| 178 | typeof data === 'string' ? data : Buffer.from(data), |
| 179 | ) |
| 180 | }, |
| 181 | list: async (p) => { |
| 182 | const entries = await fsp.readdir(this.resolve(p), { |
| 183 | withFileTypes: true, |
| 184 | }) |
| 185 | return entries.map((e) => ({ |
| 186 | name: e.name, |
| 187 | path: `${p.replace(/\/$/, '')}/${e.name}`, |
| 188 | type: e.isDirectory() ? ('dir' as const) : ('file' as const), |
| 189 | })) |
| 190 | }, |
| 191 | mkdir: async (p) => { |
| 192 | await fsp.mkdir(this.resolve(p), { recursive: true }) |
| 193 | }, |
| 194 | remove: async (p) => { |
| 195 | await fsp.rm(this.resolve(p), { recursive: true, force: true }) |
| 196 | }, |
| 197 | rename: async (from, to) => { |
| 198 | await fsp.rename(this.resolve(from), this.resolve(to)) |
| 199 | }, |
| 200 | exists: async (p) => { |
| 201 | try { |
| 202 | await fsp.access(this.resolve(p)) |
| 203 | return true |
| 204 | } catch { |
| 205 | return false |
| 206 | } |
| 207 | }, |
| 208 | } |
| 209 | |
| 210 | // Native recursive file watching is supported on Windows/macOS but not |
| 211 | // Linux (Node throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM). Expose the |
| 212 | // optional `fs.watch` seam only where it works; on Linux it stays |
| 213 | // undefined so `watchWorkspace` falls back to the portable exec-poll path. |
| 214 | if (process.platform !== 'linux') { |
| 215 | this.fs.watch = (p, onEvent) => { |
| 216 | const dir = this.resolve(p) |
| 217 | // Emit paths under the requested watch root `p` (not a hardcoded |
| 218 | // `/workspace`), so callers watching a sub-path get consistent paths. |
| 219 | const base = p.replace(/\/+$/, '') |
| 220 | const watcher = watchFs( |
| 221 | dir, |
nothing calls this directly
no test coverage detected