UNPKG

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