start-command 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # start-command
2
2
 
3
+ ## 0.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2ea7f43: feat: Use interactive shell mode in isolation environments to source startup files
8
+
9
+ In docker and ssh isolation environments, bash and zsh are now invoked with
10
+ the `-i` (interactive) flag when executing commands. This ensures that startup
11
+ files like `.bashrc` and `.zshrc` are sourced, making environment-dependent
12
+ tools like `nvm`, `rbenv`, `pyenv`, and similar version managers available
13
+ in isolated commands.
14
+
15
+ Previously, even though bash was correctly detected and used over sh, running
16
+ `nvm --version` in a Docker container would fail with "command not found"
17
+ because bash was started in non-interactive mode and did not source `.bashrc`.
18
+
19
+ With this fix:
20
+ - Docker: `docker run <image> bash -i -c "nvm --version"` sources `.bashrc`
21
+ - SSH: `ssh <host> bash -i -c "nvm --version"` sources `.bashrc` on the remote host
22
+ - `zsh` also gets the `-i` flag for the same reason
23
+ - `sh` does not get `-i` as it is used as a fallback for minimal containers
24
+
25
+ Fixes #79
26
+
3
27
  ## 0.23.0
4
28
 
5
29
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "src/bin/cli.js",
6
6
  "exports": {
@@ -211,6 +211,12 @@ function detectShellInEnvironment(
211
211
  return 'sh';
212
212
  }
213
213
 
214
+ /** Returns "-i" for bash/zsh (enables .bashrc sourcing), null otherwise. */
215
+ function getShellInteractiveFlag(shellPath) {
216
+ const shellName = shellPath.split('/').pop();
217
+ return shellName === 'bash' || shellName === 'zsh' ? '-i' : null;
218
+ }
219
+
214
220
  /**
215
221
  * Check if the current process has a TTY attached
216
222
  * @returns {boolean} True if TTY is available
@@ -648,30 +654,31 @@ function runInSsh(command, options = {}) {
648
654
  const sessionName = options.session || generateSessionName('ssh');
649
655
  const sshTarget = options.endpoint;
650
656
 
651
- // Detect the shell to use on the remote host
652
- // In auto mode, detection may fall back to passing command directly to leverage
653
- // the remote user's default login shell (which may already be bash)
654
657
  const shellToUse = detectShellInEnvironment('ssh', options, options.shell);
655
- // Whether to wrap command with a shell (only when explicit shell is specified)
658
+ // In auto mode, SSH login shells already source startup files (.bashrc etc.);
659
+ // only wrap with an explicit shell when user requests one.
656
660
  const useExplicitShell =
657
661
  options.shell && options.shell !== 'auto' ? shellToUse : null;
662
+ const shellInteractiveFlag = useExplicitShell
663
+ ? getShellInteractiveFlag(useExplicitShell)
664
+ : null;
658
665
 
659
666
  try {
660
667
  if (options.detached) {
661
- // Detached mode: Run command in background on remote server using nohup
662
- // The command will continue running even after SSH connection closes
668
+ // Detached mode: Run command in background via nohup; continues after SSH closes
663
669
  const remoteShell = useExplicitShell || shellToUse;
664
- const remoteCommand = `nohup ${remoteShell} -c ${JSON.stringify(command)} > /tmp/${sessionName}.log 2>&1 &`;
670
+ const shellInvocation = shellInteractiveFlag
671
+ ? `${remoteShell} ${shellInteractiveFlag}`
672
+ : remoteShell;
673
+ const remoteCommand = `nohup ${shellInvocation} -c ${JSON.stringify(command)} > /tmp/${sessionName}.log 2>&1 &`;
665
674
  const sshArgs = [sshTarget, remoteCommand];
666
675
 
667
676
  if (DEBUG) {
668
677
  console.log(`[DEBUG] Running: ssh ${sshArgs.join(' ')}`);
669
- console.log(`[DEBUG] shell: ${remoteShell}`);
678
+ console.log(`[DEBUG] shell: ${shellInvocation}`);
670
679
  }
671
680
 
672
- const result = spawnSync('ssh', sshArgs, {
673
- stdio: 'inherit',
674
- });
681
+ const result = spawnSync('ssh', sshArgs, { stdio: 'inherit' });
675
682
 
676
683
  if (result.error) {
677
684
  throw result.error;
@@ -683,11 +690,10 @@ function runInSsh(command, options = {}) {
683
690
  message: `Command started in detached SSH session on ${sshTarget}\nSession: ${sessionName}\nView logs: ssh ${sshTarget} "tail -f /tmp/${sessionName}.log"`,
684
691
  });
685
692
  } else {
686
- // Attached mode: Run command interactively over SSH
687
- // When a specific shell is requested, wrap the command with that shell.
688
- // In auto mode, pass the command directly and let the remote's default shell handle it.
693
+ // Attached mode: pass command directly (auto) or wrap with explicit shell + -i flag (bash/zsh).
694
+ const extraFlags = shellInteractiveFlag ? [shellInteractiveFlag] : [];
689
695
  const sshArgs = useExplicitShell
690
- ? [sshTarget, useExplicitShell, '-c', command]
696
+ ? [sshTarget, useExplicitShell, ...extraFlags, '-c', command]
691
697
  : [sshTarget, command];
692
698
 
693
699
  if (DEBUG) {
@@ -769,8 +775,8 @@ function runInDocker(command, options = {}) {
769
775
  }
770
776
  }
771
777
 
772
- // Detect the shell to use in the container
773
778
  const shellToUse = detectShellInEnvironment('docker', options, options.shell);
779
+ const shellInteractiveFlag = getShellInteractiveFlag(shellToUse);
774
780
 
775
781
  // Print the user command (this appears after any virtual commands like docker pull)
776
782
  const { createCommandLine } = require('./output-blocks');
@@ -780,20 +786,12 @@ function runInDocker(command, options = {}) {
780
786
  try {
781
787
  if (options.detached) {
782
788
  // Detached mode: docker run -d --name <name> [--user <user>] <image> <shell> -c '<command>'
783
- // By default (keepAlive=false), the container exits after command completes
784
- // With keepAlive=true, we keep the container running with a shell
785
- let effectiveCommand = command;
786
-
787
- if (options.keepAlive) {
788
- // With keep-alive: run command, then keep shell alive
789
- effectiveCommand = `${command}; exec ${shellToUse}`;
790
- }
791
- // Without keep-alive: container exits naturally when command completes
792
-
789
+ const effectiveCommand = options.keepAlive
790
+ ? `${command}; exec ${shellToUse}`
791
+ : command;
793
792
  const dockerArgs = ['run', '-d', '--name', containerName];
794
793
 
795
- // Add --rm flag if autoRemoveDockerContainer is true
796
- // Note: --rm must come before the image name
794
+ // --rm must come before the image name
797
795
  if (options.autoRemoveDockerContainer) {
798
796
  dockerArgs.splice(2, 0, '--rm');
799
797
  }
@@ -803,7 +801,10 @@ function runInDocker(command, options = {}) {
803
801
  dockerArgs.push('--user', options.user);
804
802
  }
805
803
 
806
- dockerArgs.push(options.image, shellToUse, '-c', effectiveCommand);
804
+ const shellArgs = shellInteractiveFlag
805
+ ? [shellToUse, shellInteractiveFlag]
806
+ : [shellToUse];
807
+ dockerArgs.push(options.image, ...shellArgs, '-c', effectiveCommand);
807
808
 
808
809
  if (DEBUG) {
809
810
  console.log(`[DEBUG] Running: docker ${dockerArgs.join(' ')}`);
@@ -840,19 +841,19 @@ function runInDocker(command, options = {}) {
840
841
  message,
841
842
  });
842
843
  } else {
843
- // Attached mode: docker run -it --name <name> [--user <user>] <image> <shell> -c '<command>'
844
+ // Attached mode: docker run -it --rm --name <name> [--user <user>] <image> <shell> -c '<cmd>'
844
845
  const dockerArgs = ['run', '-it', '--rm', '--name', containerName];
845
-
846
- // Add --user flag if specified
847
846
  if (options.user) {
848
847
  dockerArgs.push('--user', options.user);
849
848
  }
850
-
851
849
  if (DEBUG) {
852
850
  console.log(`[DEBUG] shell: ${shellToUse}`);
853
851
  }
854
852
 
855
- dockerArgs.push(options.image, shellToUse, '-c', command);
853
+ const shellCmdArgs = shellInteractiveFlag
854
+ ? [shellToUse, shellInteractiveFlag]
855
+ : [shellToUse];
856
+ dockerArgs.push(options.image, ...shellCmdArgs, '-c', command);
856
857
 
857
858
  if (DEBUG) {
858
859
  console.log(`[DEBUG] Running: docker ${dockerArgs.join(' ')}`);