|
1 | 1 | import fs from 'fs'
|
| 2 | +import os from 'os' |
2 | 3 | import path from 'path'
|
3 | 4 |
|
4 |
| -// @ts-ignore |
5 |
| -import spawn from 'await-spawn' |
| 5 | +import chalk from 'chalk' |
6 | 6 | import del from 'del'
|
7 | 7 | import glob from 'glob'
|
8 | 8 | import mkdirp from 'mkdirp'
|
| 9 | +import * as pty from 'node-pty' |
| 10 | +// @ts-ignore |
| 11 | +import spawn from 'await-spawn' |
9 | 12 | import yaml from 'js-yaml'
|
10 | 13 |
|
11 | 14 | import * as nix from './nix'
|
@@ -289,28 +292,96 @@ export default class Environment {
|
289 | 292 | }
|
290 | 293 |
|
291 | 294 | /**
|
292 |
| - * Execute a bash command within the environment |
| 295 | + * Create variables for an environment. |
| 296 | + * |
| 297 | + * This method is used in several other metho |
| 298 | + * e.g. `within`, `enter` |
293 | 299 | *
|
294 |
| - * A 'pure' shell will only hav available the executables that |
| 300 | + * A 'pure' environment will only have available the executables that |
295 | 301 | * were exlicitly installed into the environment
|
296 | 302 | *
|
| 303 | + * @param pure Should the shell that this command is executed in be 'pure'? |
| 304 | + */ |
| 305 | + async vars (pure: boolean = false) { |
| 306 | + const location = await nix.location(this.name) |
| 307 | + |
| 308 | + let PATH = `${location}/bin:${location}/sbin` |
| 309 | + if (!pure) PATH += ':' + process.env.PATH |
| 310 | + |
| 311 | + const R_LIBS_SITE = `${location}/library` |
| 312 | + |
| 313 | + return { |
| 314 | + PATH, |
| 315 | + R_LIBS_SITE |
| 316 | + } |
| 317 | + } |
| 318 | + |
| 319 | + /** |
| 320 | + * Execute a bash command within the environment |
| 321 | + * |
297 | 322 | * @param command The command to execute
|
298 | 323 | * @param pure Should the shell that this command is executed in be 'pure'?
|
299 | 324 | */
|
300 | 325 | async within (command: string, pure: boolean = false) {
|
301 |
| - const location = await nix.location(this.name) |
302 |
| - let path = `${location}/bin:${location}/sbin` |
303 |
| - if (!pure) path += ':' + process.env.PATH |
304 |
| - // Get the path to bash because it may not be available in the PATH of |
305 |
| - // a pure shell |
306 |
| - let bash = await spawn('which', ['bash']) |
307 |
| - await spawn(bash.toString().trim(), ['-c', command], { |
| 326 | + // Get the path to bash because it may not be available in |
| 327 | + // the PATH of a pure shell |
| 328 | + let shell = await spawn('which', ['bash']) |
| 329 | + shell = shell.toString().trim() |
| 330 | + await spawn(shell, ['-c', command], { |
308 | 331 | stdio: 'inherit',
|
309 |
| - env: { |
310 |
| - PATH: path, |
311 |
| - R_LIBS_SITE: `${location}/library` |
| 332 | + env: await this.vars() |
| 333 | + }) |
| 334 | + } |
| 335 | + |
| 336 | + /** |
| 337 | + * Enter the a shell within the environment |
| 338 | + * |
| 339 | + * @param command An initial command to execute in the shell e.g. R or python |
| 340 | + * @param pure Should the shell be 'pure'? |
| 341 | + */ |
| 342 | + async enter (command: string = '', pure: boolean = true) { |
| 343 | + const shellName = os.platform() === 'win32' ? 'powershell.exe' : 'bash' |
| 344 | + const shellArgs = ['--noprofile', '--norc'] |
| 345 | + |
| 346 | + let shellPath = await spawn('which', [shellName]) |
| 347 | + shellPath = shellPath.toString().trim() |
| 348 | + |
| 349 | + let vars = await this.vars(pure) |
| 350 | + vars = Object.assign(vars, { |
| 351 | + PS1: '☆ ' + chalk.green.bold(this.name) + ':' + chalk.blue('\\w') + '$ ' |
| 352 | + }) |
| 353 | + |
| 354 | + const shellProcess = pty.spawn(shellPath, shellArgs, { |
| 355 | + name: 'xterm-color', |
| 356 | + cols: 120, |
| 357 | + rows: 30, |
| 358 | + env: vars |
| 359 | + }) |
| 360 | + shellProcess.on('data', data => { |
| 361 | + process.stdout.write(data) |
| 362 | + }) |
| 363 | + |
| 364 | + // To prevent echoing of input set stdin to raw mode (see https://github.com/Microsoft/node-pty/issues/78) |
| 365 | + // https://nodejs.org/api/tty.html: "When in raw mode, input is always available character-by-character, |
| 366 | + // not including modifiers. Additionally, all special processing of characters |
| 367 | + // by the terminal is disabled, including echoing input characters. Note that CTRL+C |
| 368 | + // will no longer cause a SIGINT when in this mode." |
| 369 | + // @ts-ignore |
| 370 | + process.stdin.setRawMode(true) |
| 371 | + |
| 372 | + // Write the result through to the shell process |
| 373 | + // Capture Ctrl+D for special handling: |
| 374 | + // - if in the top level shell process then exit this process |
| 375 | + // - otherwise, pass on the process e.g. node, Rrm |
| 376 | + const ctrlD = Buffer.from([4]) |
| 377 | + process.stdin.on('data', data => { |
| 378 | + if (data.equals(ctrlD) && shellProcess.process == shellPath) { |
| 379 | + process.exit(1) |
312 | 380 | }
|
| 381 | + shellProcess.write(data) |
313 | 382 | })
|
| 383 | + |
| 384 | + if (command) shellProcess.write(command + '\r') |
314 | 385 | }
|
315 | 386 |
|
316 | 387 | /**
|
|
0 commit comments