Skip to content

Commit a69c9c1

Browse files
authored
Allow passing commands to run against the server through shell (#277)
1 parent 53e27b2 commit a69c9c1

File tree

1 file changed

+91
-19
lines changed

1 file changed

+91
-19
lines changed

server/screeps-cli.cjs

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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');
44
const q = require('q');
5-
const net = require("net");
5+
const net = require('net');
66
const path = require('path');
77
const os = require('os');
88
const 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(/^Screeps server v.* running on port .*/)) {
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

Comments
 (0)