forked from vercel/serve
/
cli.ts
203 lines (155 loc) · 6.48 KB
/
cli.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// source/utilities/cli.ts
// CLI-related utility functions.
import { parse as parseUrl } from 'node:url';
import { env } from 'node:process';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import parseArgv from 'arg';
import checkForUpdate from 'update-check';
import { resolve } from './promise.js';
import { logger } from './logger.js';
import type { Arguments, ParsedEndpoint } from '../types.js';
// The help text for the CLI.
const helpText = chalkTemplate`
{bold.cyan serve} - Static file serving and directory listing
{bold USAGE}
{bold $} {cyan serve} --help
{bold $} {cyan serve} --version
{bold $} {cyan serve} folder_name
{bold $} {cyan serve} [-l {underline listen_uri} [-l ...]] [{underline directory}]
By default, {cyan serve} will listen on {bold 0.0.0.0:3000} and serve the
current working directory on that address.
Specifying a single {bold --listen} argument will overwrite the default, not supplement it.
{bold OPTIONS}
--help Shows this help message
-v, --version Displays the current version of serve
-l, --listen {underline listen_uri} Specify a URI endpoint on which to listen (see below) -
more than one may be specified to listen in multiple places
-p Specify custom port
-d, --debug Show debugging information
-s, --single Rewrite all not-found requests to \`index.html\`
-c, --config Specify custom path to \`serve.json\`
-C, --cors Enable CORS, sets \`Access-Control-Allow-Origin\` to \`*\`
-n, --no-clipboard Do not copy the local address to the clipboard
-u, --no-compression Do not compress files
--no-etag Send \`Last-Modified\` header instead of \`ETag\`
-S, --symlinks Resolve symlinks instead of showing 404 errors
--ssl-cert Optional path to an SSL/TLS certificate to serve with HTTPS
{grey Supported formats: PEM (default) and PKCS12 (PFX)}
--ssl-key Optional path to the SSL/TLS certificate\'s private key
{grey Applicable only for PEM certificates}
--ssl-pass Optional path to the SSL/TLS certificate\'s passphrase
--no-port-switching Do not open a port other than the one specified when it\'s taken.
{bold ENDPOINTS}
Listen endpoints (specified by the {bold --listen} or {bold -l} options above) instruct {cyan serve}
to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
For TCP ports on hostname "localhost":
{bold $} {cyan serve} -l {underline 1234}
For TCP (traditional host/port) endpoints:
{bold $} {cyan serve} -l tcp://{underline hostname}:{underline 1234}
For UNIX domain socket endpoints:
{bold $} {cyan serve} -l unix:{underline /path/to/socket.sock}
For Windows named pipe endpoints:
{bold $} {cyan serve} -l pipe:\\\\.\\pipe\\{underline PipeName}
`;
/**
* Returns the help text.
*
* @returns The help text shown when the `--help` option is used.
*/
export const getHelpText = (): string => helpText;
/**
* Parse and return the endpoints from the given string.
*
* @param uriOrPort - The endpoint to listen on.
* @returns A list of parsed endpoints.
*/
export const parseEndpoint = (uriOrPort: string): ParsedEndpoint => {
// If the endpoint is a port number, return it as is.
if (!isNaN(Number(uriOrPort))) return { port: Number(uriOrPort) };
// Cast it as a string, since we know for sure it is not a number.
const endpoint = uriOrPort;
// We cannot use `new URL` here, otherwise it will not
// parse the host properly and it would drop support for IPv6.
const url = parseUrl(endpoint);
switch (url.protocol) {
case 'pipe:': {
const pipe = endpoint.replace(/^pipe:/, '');
if (!pipe.startsWith('\\\\.\\'))
throw new Error(`Invalid Windows named pipe endpoint: ${endpoint}`);
return { host: pipe };
}
case 'unix:':
if (!url.pathname)
throw new Error(`Invalid UNIX domain socket endpoint: ${endpoint}`);
return { host: url.pathname };
case 'tcp:':
url.port = url.port ?? '3000';
url.hostname = url.hostname ?? 'localhost';
return {
port: Number(url.port),
host: url.hostname,
};
default:
throw new Error(
`Unknown --listen endpoint scheme (protocol): ${
url.protocol ?? 'undefined'
}`,
);
}
};
// The options the CLI accepts, and how to parse them.
const options = {
'--help': Boolean,
'--version': Boolean,
'--listen': [parseEndpoint] as [typeof parseEndpoint],
'--single': Boolean,
'--debug': Boolean,
'--config': String,
'--no-clipboard': Boolean,
'--no-compression': Boolean,
'--no-etag': Boolean,
'--symlinks': Boolean,
'--cors': Boolean,
'--no-port-switching': Boolean,
'--ssl-cert': String,
'--ssl-key': String,
'--ssl-pass': String,
// A list of aliases for the above options.
'-h': '--help',
'-v': '--version',
'-l': '--listen',
'-s': '--single',
'-d': '--debug',
'-c': '--config',
'-n': '--no-clipboard',
'-u': '--no-compression',
'-S': '--symlinks',
'-C': '--cors',
// The `-p` option is deprecated and is kept only for backwards-compatibility.
'-p': '--listen',
};
/**
* Parses the program's `process.argv` and returns the options and arguments.
*
* @returns The parsed options and arguments.
*/
export const parseArguments = (): Arguments => parseArgv(options);
/**
* Checks for updates to this package. If an update is available, it brings it
* to the user's notice by printing a message to the console.
*/
export const checkForUpdates = async (manifest: object): Promise<void> => {
// Do not check for updates if the `NO_UPDATE_CHECK` variable is set.
if (env.NO_UPDATE_CHECK) return;
// Check for a newer version of the package.
const [error, update] = await resolve(checkForUpdate(manifest));
// If there is an error, throw it; and if there is no update, return.
if (error) throw error;
if (!update) return;
// If a newer version is available, tell the user.
logger.log(
chalk.bgRed.white(' UPDATE '),
`The latest version of \`serve\` is ${update.latest}`,
);
};