Skip to content

Commit 85b627c

Browse files
committed
Create initial server layout (#11)
* Create initial server layout * Adjust command name to entry * Add @oclif/config as dependency * Implement build process for outputting single binary * Add init message * Remove unused import, add tsconfig.json to .gitignore * Accidently pushed wacky change to output host FS files * Add options to createApp
1 parent cd81cbf commit 85b627c

25 files changed

+4646
-222
lines changed

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,9 @@
4141
"webpack-cli": "^3.2.1",
4242
"webpack-dev-server": "^3.1.14",
4343
"write-file-webpack-plugin": "^4.5.0"
44+
},
45+
"dependencies": {
46+
"node-loader": "^0.6.0",
47+
"webpack-merge": "^4.2.1"
4448
}
4549
}

packages/logger/src/logger.ts

+36-17
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ export abstract class Formatter {
117117
public abstract push(arg: string, color?: string, weight?: string): void;
118118
public abstract push(arg: any): void; // tslint:disable-line no-any
119119

120+
public abstract fields(fields: Array<Field<any>>): void;
121+
120122
/**
121123
* Flush out the built arguments.
122124
*/
@@ -149,12 +151,12 @@ export abstract class Formatter {
149151
*/
150152
export class BrowserFormatter extends Formatter {
151153

152-
public tag(name: string, color:string): void {
154+
public tag(name: string, color: string): void {
153155
this.format += `%c ${name} `;
154156
this.args.push(
155157
`border: 1px solid #222; background-color: ${color}; padding-top: 1px;`
156-
+ " padding-bottom: 1px; font-size: 12px; font-weight: bold; color: white;"
157-
+ (name.length === 4 ? "padding-left: 3px; padding-right: 4px;" : ""),
158+
+ " padding-bottom: 1px; font-size: 12px; font-weight: bold; color: white;"
159+
+ (name.length === 4 ? "padding-left: 3px; padding-right: 4px;" : ""),
158160
);
159161
}
160162

@@ -170,6 +172,20 @@ export class BrowserFormatter extends Formatter {
170172
this.args.push(arg);
171173
}
172174

175+
public fields(fields: Array<Field<any>>): void {
176+
console.groupCollapsed(...this.flush());
177+
fields.forEach((field) => {
178+
this.push(field.identifier, "#3794ff", "bold");
179+
if (typeof field.value !== "undefined" && field.value.constructor && field.value.constructor.name) {
180+
this.push(` (${field.value.constructor.name})`);
181+
}
182+
this.push(": ");
183+
this.push(field.value);
184+
console.log(...this.flush());
185+
});
186+
console.groupEnd();
187+
}
188+
173189
}
174190

175191
/**
@@ -179,8 +195,11 @@ export class ServerFormatter extends Formatter {
179195

180196
public tag(name: string, color: string): void {
181197
const [r, g, b] = hexToRgb(color);
198+
while (name.length < 5) {
199+
name += " ";
200+
}
182201
this.format += "\u001B[1m";
183-
this.format += `\u001B[48;2;${r};${g};${b}m ${name} \u001B[0m`;
202+
this.format += `\u001B[38;2;${r};${g};${b}m ${name} \u001B[0m`;
184203
}
185204

186205
public push(arg: any, color?: string, weight?: string): void { // tslint:disable-line no-any
@@ -198,6 +217,16 @@ export class ServerFormatter extends Formatter {
198217
this.args.push(arg);
199218
}
200219

220+
public fields(fields: Array<Field<any>>): void {
221+
const obj = {} as any;
222+
this.format += "\u001B[38;2;140;140;140m"
223+
fields.forEach((field) => {
224+
obj[field.identifier] = field.value;
225+
});
226+
this.args.push(JSON.stringify(obj));
227+
console.log(...this.flush());
228+
}
229+
201230
}
202231

203232
/**
@@ -250,7 +279,7 @@ export class Logger {
250279
type: "warn",
251280
message: msg,
252281
fields,
253-
tagColor: "#919E00",
282+
tagColor: "#FF9D00",
254283
});
255284
}
256285

@@ -325,7 +354,7 @@ export class Logger {
325354
this._formatter.push(" ");
326355
this._formatter.tag(this.name.toUpperCase(), this.nameColor);
327356
}
328-
this._formatter.push(" " + options.message);
357+
this._formatter.push(options.message);
329358
if (times.length > 0) {
330359
times.forEach((time) => {
331360
const diff = now - time.value.ms;
@@ -341,17 +370,7 @@ export class Logger {
341370

342371
// tslint:disable no-console
343372
if (hasFields) {
344-
console.groupCollapsed(...this._formatter.flush());
345-
fields.forEach((field) => {
346-
this._formatter.push(field.identifier, "#3794ff", "bold");
347-
if (typeof field.value !== "undefined" && field.value.constructor && field.value.constructor.name) {
348-
this._formatter.push(` (${field.value.constructor.name})`);
349-
}
350-
this._formatter.push(": ");
351-
this._formatter.push(field.value);
352-
console.log(...this._formatter.flush());
353-
});
354-
console.groupEnd();
373+
this._formatter.fields(fields);
355374
} else {
356375
console.log(...this._formatter.flush());
357376
}

packages/protocol/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
"main": "src/index.ts",
44
"dependencies": {
55
"express": "^4.16.4",
6+
"google-protobuf": "^3.6.1",
67
"node-pty": "^0.8.0",
78
"ws": "^6.1.2"
89
},
910
"devDependencies": {
10-
"@types/express": "^4.16.0",
11+
"@types/google-protobuf": "^3.2.7",
1112
"@types/text-encoding": "^0.0.35",
12-
"@types/ws": "^6.0.1",
1313
"text-encoding": "^0.7.0",
1414
"ts-protoc-gen": "^0.8.0"
1515
}

packages/protocol/src/browser/client.ts

+47-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ReadWriteConnection } from "../common/connection";
2-
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage } from "../proto";
3-
import { Emitter } from "@coder/events";
1+
import { ReadWriteConnection, InitData, OperatingSystem } from "../common/connection";
2+
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, InitMessage } from "../proto";
3+
import { Emitter, Event } from "@coder/events";
44
import { logger, field } from "@coder/logger";
55
import { ChildProcess, SpawnOptions, ServerProcess } from "./command";
66

@@ -15,12 +15,17 @@ export class Client {
1515
private sessionId: number = 0;
1616
private sessions: Map<number, ServerProcess> = new Map();
1717

18+
private _initData: InitData | undefined;
19+
private initDataEmitter: Emitter<InitData> = new Emitter();
20+
1821
/**
1922
* @param connection Established connection to the server
2023
*/
2124
public constructor(
2225
private readonly connection: ReadWriteConnection,
2326
) {
27+
this.initDataEmitter = new Emitter();
28+
2429
connection.onMessage((data) => {
2530
try {
2631
this.handleMessage(ServerMessage.deserializeBinary(data));
@@ -30,6 +35,14 @@ export class Client {
3035
});
3136
}
3237

38+
public get onInitData(): Event<InitData> {
39+
return this.initDataEmitter.event;
40+
}
41+
42+
public get initData(): InitData | undefined {
43+
return this._initData;
44+
}
45+
3346
public evaluate<R>(func: () => R | Promise<R>): Promise<R>;
3447
public evaluate<R, T1>(func: (a1: T1) => R | Promise<R>, a1: T1): Promise<R>;
3548
public evaluate<R, T1, T2>(func: (a1: T1, a2: T2) => R | Promise<R>, a1: T1, a2: T2): Promise<R>;
@@ -47,7 +60,7 @@ export class Client {
4760
* console.log(returned);
4861
* // output: "hi"
4962
* @param func Function to evaluate
50-
* @returns {Promise} Promise rejected or resolved from the evaluated function
63+
* @returns Promise rejected or resolved from the evaluated function
5164
*/
5265
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): Promise<R> {
5366
const newEval = new NewEvalMessage();
@@ -61,8 +74,8 @@ export class Client {
6174
this.connection.send(clientMsg.serializeBinary());
6275

6376
let res: (value?: R) => void;
64-
let rej: (err?: any) => void;
65-
const prom = new Promise<R>((r, e) => {
77+
let rej: (err?: Error) => void;
78+
const prom = new Promise<R>((r, e): void => {
6679
res = r;
6780
rej = e;
6881
});
@@ -80,6 +93,7 @@ export class Client {
8093
}
8194

8295
const rt = resp.getType();
96+
// tslint:disable-next-line
8397
let val: any;
8498
switch (rt) {
8599
case TypedValue.Type.BOOLEAN:
@@ -107,7 +121,7 @@ export class Client {
107121
d1.dispose();
108122
d2.dispose();
109123

110-
rej(failedMsg.getMessage());
124+
rej(new Error(failedMsg.getMessage()));
111125
}
112126
});
113127

@@ -120,7 +134,6 @@ export class Client {
120134
* const cp = this.client.spawn("echo", ["test"]);
121135
* cp.stdout.on("data", (data) => console.log(data.toString()));
122136
* cp.on("exit", (code) => console.log("exited with", code));
123-
* @param command
124137
* @param args Arguments
125138
* @param options Options to execute for the command
126139
*/
@@ -167,14 +180,14 @@ export class Client {
167180

168181
const serverProc = new ServerProcess(this.connection, id, options ? options.tty !== undefined : false);
169182
serverProc.stdin.on("close", () => {
170-
console.log("stdin closed");
171183
const c = new CloseSessionInputMessage();
172184
c.setId(id);
173185
const cm = new ClientMessage();
174186
cm.setCloseSessionInput(c);
175187
this.connection.send(cm.serializeBinary());
176188
});
177189
this.sessions.set(id, serverProc);
190+
178191
return serverProc;
179192
}
180193

@@ -183,7 +196,31 @@ export class Client {
183196
* routed through here.
184197
*/
185198
private handleMessage(message: ServerMessage): void {
186-
if (message.hasEvalDone()) {
199+
if (message.hasInit()) {
200+
const init = message.getInit()!;
201+
let opSys: OperatingSystem;
202+
switch (init.getOperatingSystem()) {
203+
case InitMessage.OperatingSystem.WINDOWS:
204+
opSys = OperatingSystem.Windows;
205+
break;
206+
case InitMessage.OperatingSystem.LINUX:
207+
opSys = OperatingSystem.Linux;
208+
break;
209+
case InitMessage.OperatingSystem.MAC:
210+
opSys = OperatingSystem.Mac;
211+
break;
212+
default:
213+
throw new Error(`unsupported operating system ${init.getOperatingSystem()}`);
214+
}
215+
this._initData = {
216+
dataDirectory: init.getDataDirectory(),
217+
homeDirectory: init.getHomeDirectory(),
218+
tmpDirectory: init.getTmpDirectory(),
219+
workingDirectory: init.getWorkingDirectory(),
220+
os: opSys,
221+
};
222+
this.initDataEmitter.emit(this._initData);
223+
} else if (message.hasEvalDone()) {
187224
this.evalDoneEmitter.emit(message.getEvalDone()!);
188225
} else if (message.hasEvalFailed()) {
189226
this.evalFailedEmitter.emit(message.getEvalFailed()!);

packages/protocol/src/browser/modules/child_process.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ export class CP {
3838
);
3939
});
4040

41+
// @ts-ignore
4142
return process;
4243
}
4344

44-
public fork = (modulePath: string): cp.ChildProcess => {
45+
public fork(modulePath: string): cp.ChildProcess {
46+
//@ts-ignore
4547
return this.client.fork(modulePath);
4648
}
4749

48-
public spawn = (command: string, args?: ReadonlyArray<string> | cp.SpawnOptions, _options?: cp.SpawnOptions): cp.ChildProcess => {
50+
public spawn(command: string, args?: ReadonlyArray<string> | cp.SpawnOptions, _options?: cp.SpawnOptions): cp.ChildProcess {
51+
// TODO: fix this ignore. Should check for args or options here
52+
//@ts-ignore
4953
return this.client.spawn(command, args, options);
5054
}
5155

packages/protocol/src/common/connection.ts

+14
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,17 @@ export interface ReadWriteConnection extends SendableConnection {
77
onClose(cb: () => void): void;
88
close(): void;
99
}
10+
11+
export enum OperatingSystem {
12+
Windows,
13+
Linux,
14+
Mac,
15+
}
16+
17+
export interface InitData {
18+
readonly os: OperatingSystem;
19+
readonly dataDirectory: string;
20+
readonly workingDirectory: string;
21+
readonly homeDirectory: string;
22+
readonly tmpDirectory: string;
23+
}

packages/protocol/src/node/evaluate.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vm from "vm";
22
import { NewEvalMessage, TypedValue, EvalFailedMessage, EvalDoneMessage, ServerMessage } from "../proto";
33
import { SendableConnection } from "../common/connection";
44

5+
declare var __non_webpack_require__: typeof require;
56
export const evaluate = async (connection: SendableConnection, message: NewEvalMessage): Promise<void> => {
67
const argStr: string[] = [];
78
message.getArgsList().forEach((value) => {
@@ -51,7 +52,7 @@ export const evaluate = async (connection: SendableConnection, message: NewEvalM
5152
connection.send(serverMsg.serializeBinary());
5253
};
5354
try {
54-
const value = vm.runInNewContext(`(${message.getFunction()})(${argStr.join(",")})`, { Buffer, require, setTimeout }, {
55+
const value = vm.runInNewContext(`(${message.getFunction()})(${argStr.join(",")})`, { Buffer, require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require, setTimeout }, {
5556
timeout: message.getTimeout() || 30000,
5657
});
5758
sendResp(await value);

packages/protocol/src/node/server.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { logger, field } from "@coder/logger";
2+
import * as os from "os";
23
import { TextDecoder } from "text-encoding";
3-
import { ClientMessage } from "../proto";
4+
import { ClientMessage, InitMessage, ServerMessage } from "../proto";
45
import { evaluate } from "./evaluate";
56
import { ReadWriteConnection } from "../common/connection";
67
import { Process, handleNewSession } from "./command";
78

9+
export interface ServerOptions {
10+
readonly workingDirectory: string;
11+
readonly dataDirectory: string;
12+
}
13+
814
export class Server {
915

1016
private readonly sessions: Map<number, Process>;
1117

1218
public constructor(
1319
private readonly connection: ReadWriteConnection,
20+
options?: ServerOptions,
1421
) {
1522
this.sessions = new Map();
1623

@@ -21,6 +28,37 @@ export class Server {
2128
logger.error("Failed to handle client message", field("length", data.byteLength), field("exception", ex));
2229
}
2330
});
31+
32+
if (!options) {
33+
logger.warn("No server options provided. InitMessage will not be sent.");
34+
35+
return;
36+
}
37+
38+
const initMsg = new InitMessage();
39+
initMsg.setDataDirectory(options.dataDirectory);
40+
initMsg.setWorkingDirectory(options.workingDirectory);
41+
initMsg.setHomeDirectory(os.homedir());
42+
initMsg.setTmpDirectory(os.tmpdir());
43+
const platform = os.platform();
44+
let operatingSystem: InitMessage.OperatingSystem;
45+
switch (platform) {
46+
case "win32":
47+
operatingSystem = InitMessage.OperatingSystem.WINDOWS;
48+
break;
49+
case "linux":
50+
operatingSystem = InitMessage.OperatingSystem.LINUX;
51+
break;
52+
case "darwin":
53+
operatingSystem = InitMessage.OperatingSystem.MAC;
54+
break;
55+
default:
56+
throw new Error(`unrecognized platform "${platform}"`);
57+
}
58+
initMsg.setOperatingSystem(operatingSystem);
59+
const srvMsg = new ServerMessage();
60+
srvMsg.setInit(initMsg);
61+
connection.send(srvMsg.serializeBinary());
2462
}
2563

2664
private handleMessage(message: ClientMessage): void {

0 commit comments

Comments
 (0)