Skip to content

Commit 3c42f5b

Browse files
authored
Extract REPL impl. to lib (#94)
* Extract REPL impl. to lib * Define and implement generic REPLConfig
1 parent 4e22b8b commit 3c42f5b

File tree

2 files changed

+118
-90
lines changed

2 files changed

+118
-90
lines changed

src/_boot/repl.ts

Lines changed: 11 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,19 @@
1-
import REPL, { REPLEval, ReplOptions, REPLServer } from 'repl';
2-
import vm from 'vm';
3-
import { createServer, Server } from 'net';
41
import { makeModule } from '@/context';
2+
import { makeREPL, REPLConfigType } from '@/_lib/repl';
53

6-
type REPLConfig = {
7-
appName: string;
8-
cli: boolean;
9-
repl: {
10-
port: number;
11-
};
12-
};
13-
14-
const repl = makeModule(
15-
'repl',
16-
async ({
17-
app: { onReady, terminate },
18-
container,
19-
config: {
20-
appName,
21-
cli,
22-
environment,
23-
repl: { port },
24-
},
25-
logger,
26-
}) => {
27-
const promisableEval: REPLEval = (cmd, context, filename, callback) => {
28-
const result = vm.runInContext(cmd, context);
29-
30-
if (isPromise(result)) {
31-
return result.then((v) => callback(null, v)).catch((e) => callback(e, null));
32-
}
33-
34-
return callback(null, result);
35-
};
36-
37-
const isPromise = (value) => value && typeof value.then === 'function' && typeof value.catch === 'function';
38-
39-
const createREPL = (
40-
config: Partial<ReplOptions> = { input: process.stdin, output: process.stdout }
41-
): REPLServer => {
42-
const repl = REPL.start({
43-
eval: promisableEval,
44-
prompt: `${appName}$ `,
45-
ignoreUndefined: true,
46-
...config,
47-
});
48-
49-
Object.assign(repl.context, { registry: container.cradle, container });
4+
type REPLConfig = REPLConfigType<{ appName: string; cli: boolean; repl: { port: number } }>;
505

51-
return repl;
52-
};
6+
const repl = makeModule('repl', async ({ app: { onReady, terminate }, container, config, logger }) => {
7+
const repl = makeREPL({ container, config, logger });
538

54-
let server: Server;
9+
onReady(async () => {
10+
await repl.start({ terminate });
11+
});
5512

56-
const startREPL = async () => {
57-
if (cli) {
58-
const repl = createREPL();
59-
60-
repl.on('close', terminate);
61-
} else if (!['production', 'test'].includes(environment)) {
62-
server = createServer((socket) => {
63-
const repl = createREPL({
64-
input: socket,
65-
output: socket,
66-
terminal: true,
67-
});
68-
69-
repl.on('close', () => {
70-
socket.end();
71-
});
72-
73-
socket.on('error', (err) => {
74-
logger.error('[REPL] Connection error');
75-
logger.error(err);
76-
socket.end();
77-
});
78-
}).listen(port);
79-
}
80-
};
81-
82-
onReady(startREPL);
83-
84-
return async () => {
85-
if (server && server.listening) {
86-
await new Promise<void>((resolve, reject) =>
87-
server.close((err) => {
88-
if (err) return reject(err);
89-
resolve();
90-
})
91-
);
92-
}
93-
};
94-
}
95-
);
13+
return async () => {
14+
await repl.close();
15+
};
16+
});
9617

9718
export { repl };
9819
export type { REPLConfig };

src/_lib/repl/index.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import REPL, { REPLEval, ReplOptions, REPLServer } from 'repl';
2+
import vm from 'vm';
3+
import { createServer, Server } from 'net';
4+
import { AwilixContainer } from 'awilix';
5+
import { Logger } from 'pino';
6+
import { EnvironmentConfig } from '../Environment';
7+
8+
type REPLConfigType<T> = {
9+
[key in keyof T]: T[key];
10+
};
11+
12+
type ReplParameters = {
13+
container: AwilixContainer;
14+
config: REPLConfigType<{
15+
appName: string;
16+
cli: boolean;
17+
repl: { port: number };
18+
environment: EnvironmentConfig['environment'];
19+
}>;
20+
logger: Logger;
21+
};
22+
23+
type REPL = {
24+
create: (config: Partial<ReplOptions>) => REPLServer;
25+
start: ({ terminate }) => Promise<void>;
26+
close: () => Promise<void>;
27+
};
28+
29+
const isPromise = (value) => value && typeof value.then === 'function' && typeof value.catch === 'function';
30+
31+
const promisableEval: REPLEval = (cmd, context, filename, callback) => {
32+
const result = vm.runInContext(cmd, context);
33+
34+
if (isPromise(result)) {
35+
return result.then((v) => callback(null, v)).catch((e) => callback(e, null));
36+
}
37+
38+
return callback(null, result);
39+
};
40+
41+
const makeREPL = ({
42+
container,
43+
config: {
44+
appName,
45+
cli,
46+
environment,
47+
repl: { port },
48+
},
49+
logger,
50+
}: ReplParameters): REPL => {
51+
let server: Server;
52+
53+
const create = (config: Partial<ReplOptions> = { input: process.stdin, output: process.stdout }): REPLServer => {
54+
const repl = REPL.start({
55+
eval: promisableEval,
56+
prompt: `${appName}$ `,
57+
ignoreUndefined: true,
58+
...config,
59+
});
60+
61+
Object.assign(repl.context, { registry: container.cradle, container });
62+
63+
return repl;
64+
};
65+
66+
return {
67+
create,
68+
start: async ({ terminate }) => {
69+
if (cli) {
70+
const repl = create();
71+
72+
repl.on('close', terminate);
73+
} else if (!['production', 'test'].includes(environment)) {
74+
server = createServer((socket) => {
75+
const repl = create({
76+
input: socket,
77+
output: socket,
78+
terminal: true,
79+
});
80+
81+
repl.on('close', () => {
82+
socket.end();
83+
});
84+
85+
socket.on('error', (err) => {
86+
logger.error('[REPL] Connection error');
87+
logger.error(err);
88+
socket.end();
89+
});
90+
}).listen(port);
91+
}
92+
},
93+
close: async () => {
94+
if (server && server.listening) {
95+
await new Promise<void>((resolve, reject) =>
96+
server.close((err) => {
97+
if (err) return reject(err);
98+
resolve();
99+
})
100+
);
101+
}
102+
},
103+
};
104+
};
105+
106+
export { makeREPL };
107+
export type { REPLConfigType };

0 commit comments

Comments
 (0)