11package bloop .cli
22
3- import caseapp .core .RemainingArgs
3+ import java .nio .file .Files
4+
5+ import scala .concurrent .Await
6+ import scala .concurrent .duration .Duration
7+ import scala .io .Source
8+ import scala .util .Using
49
510import bloop .cli .options .DefaultOptions
6- import bloop .rifle .BloopThreads
711import bloop .rifle .BloopRifle
12+ import bloop .rifle .BloopThreads
813import bloop .rifle .internal .Operations
9- import scala . concurrent . Await
10- import scala . concurrent . duration . Duration
14+
15+ import caseapp . core . RemainingArgs
1116import caseapp .core .app .Command
1217
1318object Default extends Command [DefaultOptions ] {
@@ -42,12 +47,16 @@ object Default extends Command[DefaultOptions] {
4247 case Seq () =>
4348 // FIXME Give more details?
4449 logger.message(" Bloop server is running." )
45- case Seq (cmd, args @ _* ) =>
50+ case Seq (cmd, cmdArgs @ _* ) if cmd == " console" =>
51+ // Console command needs special handling because interactive REPLs
52+ // require direct terminal access which isn't available through nailgun
53+ runConsoleCommand(options, cmdArgs.toArray)
54+ case Seq (cmd, cmdArgs @ _* ) =>
4655 val assumeTty = System .console() != null
4756 val cwd = os.pwd.wrapped
4857 val retCode = Operations .run(
4958 command = cmd,
50- args = args .toArray,
59+ args = cmdArgs .toArray,
5160 workingDir = cwd,
5261 address = bloopRifleConfig.address,
5362 inOpt = Some (System .in),
@@ -68,4 +77,76 @@ object Default extends Command[DefaultOptions] {
6877 }
6978 }
7079 }
80+
81+ /**
82+ * Handles the console command specially by:
83+ * 1. Creating a temp file for the server to write the Ammonite command
84+ * 2. Running the console command on the server with --out-file
85+ * 3. Reading the command from the file and executing it locally with terminal access
86+ */
87+ private def runConsoleCommand (options : DefaultOptions , args : Array [String ]): Unit = {
88+ val logger = options.logging.logger
89+ val bloopRifleConfig = options.bloopRifleConfig
90+ val cwd = os.pwd.wrapped
91+
92+ // Create a temp file for the Ammonite command
93+ val outFile = Files .createTempFile(" bloop-console-" , " .txt" )
94+ outFile.toFile.deleteOnExit()
95+
96+ // Add --out-file to the args
97+ val argsWithOutFile = args ++ Array (" --out-file" , outFile.toString)
98+
99+ val assumeTty = System .console() != null
100+ // Don't pass stdin to the server - we'll need it for Ammonite later
101+ // The server only needs to compile and write the command to the outFile
102+ val retCode = Operations .run(
103+ command = " console" ,
104+ args = argsWithOutFile,
105+ workingDir = cwd,
106+ address = bloopRifleConfig.address,
107+ inOpt = None ,
108+ out = System .out,
109+ err = System .err,
110+ logger = logger.bloopRifleLogger,
111+ assumeInTty = false ,
112+ assumeOutTty = assumeTty,
113+ assumeErrTty = assumeTty
114+ )
115+
116+ if (retCode != 0 ) {
117+ logger.debug(s " Console command failed with return code $retCode" )
118+ sys.exit(retCode)
119+ }
120+
121+ // Read the command from the temp file
122+ if (! Files .exists(outFile) || Files .size(outFile) == 0 ) {
123+ logger.debug(" Console command output file is empty or doesn't exist" )
124+ sys.exit(1 )
125+ }
126+
127+ val consoleCmd = Using (Source .fromFile(outFile.toFile)) { source =>
128+ source.getLines().toArray
129+ }.getOrElse {
130+ logger.debug(" Failed to read console command from output file" )
131+ sys.exit(1 )
132+ }
133+
134+ // Clean up the temp file
135+ try Files .delete(outFile)
136+ catch { case _ : Exception => }
137+
138+ logger.debug(s " Running console command: ${consoleCmd.mkString(" " )}" )
139+
140+ // Run the command with inherited IO for proper terminal access
141+ import scala .jdk .CollectionConverters ._
142+ val builder = new ProcessBuilder (consoleCmd.toList.asJava)
143+ builder.directory(cwd.toFile)
144+ builder.inheritIO()
145+ val process = builder.start()
146+ val exitCode = process.waitFor()
147+
148+ if (exitCode != 0 ) {
149+ sys.exit(exitCode)
150+ }
151+ }
71152}
0 commit comments