11#! /usr/bin/env node
22// @ts -ignore We can't load that from the outer non-Node 10 side
3- const repl = require ( " repl" ) ;
3+ const repl = require ( ' repl' ) ;
44const q = require ( 'q' ) ;
5- const net = require ( " net" ) ;
5+ const net = require ( ' net' ) ;
66const path = require ( 'path' ) ;
77const os = require ( 'os' ) ;
88const fs = require ( 'fs' ) ;
@@ -24,9 +24,10 @@ const HISTORY_FILE = (() => {
2424/**
2525 * @param {string } host
2626 * @param {number } port
27+ * @param {string } [cmd]
2728 * @returns
2829 */
29- function cli ( host , port ) {
30+ function cli ( host , port , cmd = undefined ) {
3031
3132 const defer = q . defer ( ) ;
3233
@@ -36,16 +37,31 @@ function cli(host, port) {
3637 let connected = false ;
3738
3839 /**
39- * Evaluate the REPL command
40- * @param {string } cmd
40+ * Send a command to the server for execution
41+ * @param {string } input
42+ */
43+ const executeCommand = ( input ) => {
44+ // The server side feeds the socket through `readline`, which splits on
45+ // newlines. To avoid breaking multi-line input into multiple commands,
46+ // we collapse internal newlines into spaces before sending.
47+ const toSend = input
48+ . replace ( / \r ? \n $ / , '' ) // drop the final newline REPL adds
49+ . replace ( / \r ? \n / g, ' ' ) ; // turn internal newlines into spaces
50+
51+ socket . write ( toSend + "\r\n" ) ;
52+ }
53+
54+ /**
55+ * Evaluate the REPL input
56+ * @param {string } input
4157 * @param {vm.Context } context
4258 * @param {string } filename
4359 * @param {(err: Error | null, result?: any) => void } callback
4460 */
45- const rplEval = ( cmd , context , filename , callback ) => {
61+ const replEval = ( input , context , filename , callback ) => {
4662 try {
4763 // Using "vm.Script" lets use the V8 parser to check for syntax validity.
48- new vm . Script ( cmd , { filename } ) ;
64+ new vm . Script ( input , { filename } ) ;
4965 } catch ( err ) {
5066 if ( ! ( err instanceof Error ) ) {
5167 console . error ( 'Unexpected error from repl eval' , err ) ;
@@ -58,17 +74,10 @@ function cli(host, port) {
5874 return callback ( err ) ;
5975 }
6076
61- // At this point the input is complete JS. REPL passes the whole buffered
62- // input as `cmd` , so multi-line constructs (like function definitions)
77+ // At this point the input is complete JS. Pass the whole buffered input
78+ // to the socket , so multi-line constructs (like function definitions)
6379 // are already combined.
64- // However the server side feeds the socket through `readline`, which splits
65- // on newlines. To avoid breaking multi-line input into multiple commands, we
66- // collapse internal newlines into spaces before sending.
67- const toSend = cmd
68- . replace ( / \r ? \n $ / , '' ) // drop the final newline REPL adds
69- . replace ( / \r ? \n / g, ' ' ) ; // turn internal newlines into spaces
70-
71- socket . write ( toSend + "\r\n" ) ;
80+ executeCommand ( input ) ;
7281 callback ( null ) ;
7382 } ;
7483
@@ -88,12 +97,31 @@ function cli(host, port) {
8897
8998 socket . on ( 'connect' , ( ) => {
9099 connected = true ;
100+
101+ if ( cmd ) {
102+ // Running in command mode, we're just gonna send the provided command,
103+ // wait for an answer and exit immediately.
104+ socket . on ( "data" , data => {
105+ const string = data . toString ( 'utf8' ) ;
106+ const cleaned = string . replace ( / ^ < / , '' ) . replace ( / \n < / g, '\n' ) ;
107+ if ( cleaned . match ( / ^ S c r e e p s s e r v e r v .* r u n n i n g o n p o r t .* / ) ) {
108+ // Skip over server connection answer
109+ return ;
110+ }
111+
112+ process . stdout . write ( cleaned ) ;
113+ process . exit ( 1 ) ;
114+ } ) ;
115+ executeCommand ( cmd ) ;
116+ return ;
117+ }
118+
91119 defer . resolve ( ) ;
92120 rl = repl . start ( {
93121 input : process . stdin ,
94122 output : process . stdout ,
95123 prompt : "> " ,
96- eval : rplEval ,
124+ eval : replEval ,
97125 } ) ;
98126
99127 try {
@@ -152,4 +180,48 @@ function cli(host, port) {
152180 return defer . promise ;
153181} ;
154182
155- cli ( "localhost" , 21026 ) ;
183+ // Command line options and arguments
184+ /** @type {string | undefined } */
185+ let host = undefined ;
186+ /** @type {number | undefined } */
187+ let port = undefined ;
188+ /** @type {string | undefined } */
189+ let command = undefined ;
190+
191+ // Janky option parsing
192+ const argStart = process . argv . findIndex ( arg => arg === __filename ) + 1 ;
193+ const ARGV = process . argv . slice ( argStart ) ;
194+ while ( ARGV . length ) {
195+ if ( ARGV [ 0 ] [ 0 ] === "-" ) {
196+ if ( ARGV [ 0 ] === "-c" ) {
197+ ARGV . shift ( )
198+ command = ARGV . shift ( ) ;
199+ } else {
200+ console . error ( `Unknown option ${ ARGV [ 0 ] } ` ) ;
201+ }
202+ } else {
203+ if ( host === undefined ) {
204+ host = ARGV . shift ( ) ;
205+ } else if ( port === undefined ) {
206+ const portStr = ARGV . shift ( ) ;
207+ if ( portStr === undefined ) {
208+ console . error ( `Missing port number ${ portStr } ` ) ;
209+ process . exit ( 1 ) ;
210+ }
211+ const portNum = parseInt ( portStr , 10 ) ;
212+ if ( isNaN ( portNum ) ) {
213+ console . error ( `Invalid port number ${ portStr } ` ) ;
214+ process . exit ( 1 ) ;
215+ }
216+ port = portNum ;
217+ } else {
218+ console . error ( `Unknown argument ${ ARGV [ 0 ] } ` ) ;
219+ process . exit ( 1 ) ;
220+ }
221+ }
222+ }
223+
224+ host = host || "localhost" ;
225+ port = port || 21026 ;
226+
227+ cli ( host , port , command ) ;
0 commit comments