UNPKG

5.36 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3// Native
4const path = require('path');
5const {existsSync} = require('fs');
6
7// Packages
8const arg = require('arg');
9
10// Utilities
11const serve = require('../lib');
12const handle = require('../lib/handler');
13const {version} = require('../package');
14const logError = require('../lib/error');
15const parseEndpoint = require('../lib/parse-endpoint.js');
16
17// Check if the user defined any options
18const args = arg({
19 '--listen': [parseEndpoint],
20 '-l': '--listen',
21
22 '--help': Boolean,
23
24 '--version': Boolean,
25 '-v': '--version',
26
27 // Deprecated options
28 '--port': Number,
29 '-p': '--port',
30 '--host': String,
31 '-h': '--host',
32 '--unix-socket': String,
33 '-s': '--unix-socket'
34});
35
36// When `-h` or `--help` are used, print out
37// the usage information
38if (args['--help']) {
39 console.error(`
40 micro - Asynchronous HTTP microservices
41
42 USAGE
43
44 $ micro --help
45 $ micro --version
46 $ micro [-l listen_uri [-l ...]] [entry_point.js]
47
48 By default micro will listen on 0.0.0.0:3000 and will look first
49 for the "main" property in package.json and subsequently for index.js
50 as the default entry_point.
51
52 Specifying a single --listen argument will overwrite the default, not supplement it.
53
54 OPTIONS
55
56 --help shows this help message
57
58 -v, --version displays the current version of micro
59
60 -l, --listen listen_uri specify a URI endpoint on which to listen (see below) -
61 more than one may be specified to listen in multiple places
62
63 ENDPOINTS
64
65 Listen endpoints (specified by the --listen or -l options above) instruct micro
66 to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
67
68 For TCP (traditional host/port) endpoints:
69
70 $ micro -l tcp://hostname:1234
71
72 For UNIX domain socket endpoints:
73
74 $ micro -l unix:/path/to/socket.sock
75
76 For Windows named pipe endpoints:
77
78 $ micro -l pipe:\\\\.\\pipe\\PipeName
79`);
80 process.exit(2);
81}
82
83// Print out the package's version when
84// `--version` or `-v` are used
85if (args['--version']) {
86 console.log(version);
87 process.exit();
88}
89
90if ((args['--port'] || args['--host']) && args['--unix-socket']) {
91 logError(
92 `Both host/port and socket provided. You can only use one.`,
93 'invalid-port-socket'
94 );
95 process.exit(1);
96}
97
98let deprecatedEndpoint = null;
99
100args['--listen'] = args['--listen'] || [];
101
102if (args['--port']) {
103 const {isNaN} = Number;
104 const port = Number(args['--port']);
105 if (isNaN(port) || (!isNaN(port) && (port < 1 || port >= Math.pow(2, 16)))) {
106 logError(
107 `Port option must be a number. Supplied: ${args['--port']}`,
108 'invalid-server-port'
109 );
110 process.exit(1);
111 }
112
113 deprecatedEndpoint = [args['--port']];
114}
115
116if (args['--host']) {
117 deprecatedEndpoint = deprecatedEndpoint || [];
118 deprecatedEndpoint.push(args['--host']);
119}
120
121if (deprecatedEndpoint) {
122 args['--listen'].push(deprecatedEndpoint);
123}
124
125if (args['--unix-socket']) {
126 if (typeof args['--unix-socket'] === 'boolean') {
127 logError(
128 `Socket must be a string. A boolean was provided.`,
129 'invalid-socket'
130 );
131 }
132 args['--listen'].push(args['--unix-socket']);
133}
134
135if (args['--port'] || args['--host'] || args['--unix-socket']) {
136 logError(
137 '--port, --host, and --unix-socket are deprecated - see --help for information on the --listen flag',
138 'deprecated-endpoint-flags'
139 );
140}
141
142if (args['--listen'].length === 0) {
143 // default endpoint
144 args['--listen'].push([3000]);
145}
146
147let file = args._[0];
148
149if (!file) {
150 try {
151 const packageJson = require(path.resolve(process.cwd(), 'package.json'));
152 file = packageJson.main || 'index.js';
153 } catch (err) {
154 if (err.code !== 'MODULE_NOT_FOUND') {
155 logError(
156 `Could not read \`package.json\`: ${err.message}`,
157 'invalid-package-json'
158 );
159 process.exit(1);
160 }
161 }
162}
163
164if (!file) {
165 logError('Please supply a file!', 'path-missing');
166 process.exit(1);
167}
168
169if (file[0] !== '/') {
170 file = path.resolve(process.cwd(), file);
171}
172
173if (!existsSync(file)) {
174 logError(
175 `The file or directory "${path.basename(file)}" doesn't exist!`,
176 'path-not-existent'
177 );
178 process.exit(1);
179}
180
181function registerShutdown(fn) {
182 let run = false;
183
184 const wrapper = () => {
185 if (!run) {
186 run = true;
187 fn();
188 }
189 };
190
191 process.on('SIGINT', wrapper);
192 process.on('SIGTERM', wrapper);
193 process.on('exit', wrapper);
194}
195
196function startEndpoint(module, endpoint) {
197 const server = serve(module);
198
199 server.on('error', err => {
200 console.error('micro:', err.stack);
201 process.exit(1);
202 });
203
204 server.listen(...endpoint, () => {
205 const details = server.address();
206
207 registerShutdown(() => server.close());
208
209 // `micro` is designed to run only in production, so
210 // this message is perfectly for prod
211 if (typeof details === 'string') {
212 console.log(`micro: Accepting connections on ${details}`);
213 } else if (typeof details === 'object' && details.port) {
214 console.log(`micro: Accepting connections on port ${details.port}`);
215 } else {
216 console.log('micro: Accepting connections');
217 }
218 });
219}
220
221async function start() {
222 const loadedModule = await handle(file);
223
224 for (const endpoint of args['--listen']) {
225 startEndpoint(loadedModule, endpoint);
226 }
227
228 registerShutdown(() => console.log('micro: Gracefully shutting down. Please wait...'));
229}
230
231start();