@syncular/cli 0.0.0-108 → 0.0.0-113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,252 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import {
3
- createServer,
4
- type IncomingHttpHeaders,
5
- type IncomingMessage,
6
- type ServerResponse,
7
- } from 'node:http';
8
- import process from 'node:process';
9
- import { createConsoleStaticResponder } from 'syncular/console-server';
10
- import {
11
- optionalBooleanFlag,
12
- optionalNullableFlag as optionalFlag,
13
- } from '../flags';
14
- import { printError } from '../output';
15
-
16
- const DEFAULT_HOST = '127.0.0.1';
17
- const DEFAULT_PORT = 4310;
18
-
19
- function parsePort(value: string | null): number | null {
20
- if (!value) {
21
- return DEFAULT_PORT;
22
- }
23
- const parsed = Number(value);
24
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
25
- return null;
26
- }
27
- return parsed;
28
- }
29
-
30
- function resolveToken(flagValues: Map<string, string>): string | undefined {
31
- const directToken = optionalFlag(flagValues, '--token');
32
- if (directToken) {
33
- return directToken;
34
- }
35
-
36
- const tokenEnvName = optionalFlag(flagValues, '--token-env');
37
- if (!tokenEnvName) {
38
- return undefined;
39
- }
40
-
41
- const envToken = process.env[tokenEnvName]?.trim();
42
- if (!envToken) {
43
- throw new Error(
44
- `Environment variable ${tokenEnvName} is empty or undefined.`
45
- );
46
- }
47
-
48
- return envToken;
49
- }
50
-
51
- function normalizeHost(value: string | null): string {
52
- return value ?? DEFAULT_HOST;
53
- }
54
-
55
- function formatHostForUrl(host: string): string {
56
- return host.includes(':') ? `[${host}]` : host;
57
- }
58
-
59
- function browserHost(host: string): string {
60
- if (host === '0.0.0.0' || host === '::') {
61
- return 'localhost';
62
- }
63
- return host;
64
- }
65
-
66
- function toHeaders(headers: IncomingHttpHeaders): Headers {
67
- const result = new Headers();
68
- for (const [name, value] of Object.entries(headers)) {
69
- if (value === undefined) continue;
70
- if (Array.isArray(value)) {
71
- for (const entry of value) {
72
- result.append(name, entry);
73
- }
74
- continue;
75
- }
76
- result.set(name, value);
77
- }
78
- return result;
79
- }
80
-
81
- function toRequest(request: IncomingMessage): Request {
82
- const origin = `http://${request.headers.host ?? 'localhost'}`;
83
- const requestUrl = new URL(request.url ?? '/', origin);
84
- return new Request(requestUrl, {
85
- method: request.method ?? 'GET',
86
- headers: toHeaders(request.headers),
87
- });
88
- }
89
-
90
- async function writeFetchResponse(
91
- response: ServerResponse,
92
- fetchResponse: Response,
93
- method: string
94
- ): Promise<void> {
95
- response.statusCode = fetchResponse.status;
96
- for (const [name, value] of fetchResponse.headers.entries()) {
97
- response.setHeader(name, value);
98
- }
99
-
100
- if (method === 'HEAD' || fetchResponse.body === null) {
101
- response.end();
102
- return;
103
- }
104
-
105
- const body = Buffer.from(await fetchResponse.arrayBuffer());
106
- response.end(body);
107
- }
108
-
109
- async function openInBrowser(url: string): Promise<void> {
110
- await new Promise<void>((resolvePromise, rejectPromise) => {
111
- let command = '';
112
- let commandArgs: string[] = [];
113
-
114
- if (process.platform === 'darwin') {
115
- command = 'open';
116
- commandArgs = [url];
117
- } else if (process.platform === 'win32') {
118
- command = 'cmd';
119
- commandArgs = ['/c', 'start', '', url];
120
- } else {
121
- command = 'xdg-open';
122
- commandArgs = [url];
123
- }
124
-
125
- const child = spawn(command, commandArgs, {
126
- stdio: 'ignore',
127
- detached: true,
128
- });
129
- child.once('error', rejectPromise);
130
- child.once('spawn', () => {
131
- child.unref();
132
- resolvePromise();
133
- });
134
- });
135
- }
136
-
137
- export async function runConsole(
138
- flagValues: Map<string, string>
139
- ): Promise<number> {
140
- try {
141
- const host = normalizeHost(optionalFlag(flagValues, '--host'));
142
- const port = parsePort(optionalFlag(flagValues, '--port'));
143
- if (port === null) {
144
- throw new Error('Invalid --port value. Use an integer from 0 to 65535.');
145
- }
146
-
147
- const serverUrl = optionalFlag(flagValues, '--server-url');
148
- if (serverUrl) {
149
- try {
150
- new URL(serverUrl);
151
- } catch {
152
- throw new Error(`Invalid --server-url: ${serverUrl}`);
153
- }
154
- }
155
-
156
- const token = resolveToken(flagValues);
157
- const shouldOpen = optionalBooleanFlag(flagValues, '--open', false);
158
-
159
- const consoleStaticResponder = createConsoleStaticResponder({
160
- mountPath: '/',
161
- defaultPrefill: {
162
- basePath: '/',
163
- serverUrl: serverUrl ?? undefined,
164
- token,
165
- },
166
- });
167
-
168
- const server = createServer((request, response) => {
169
- const method = request.method ?? 'GET';
170
- if (method !== 'GET' && method !== 'HEAD') {
171
- response.statusCode = 405;
172
- response.end('Method Not Allowed');
173
- return;
174
- }
175
-
176
- void (async () => {
177
- const fetchRequest = toRequest(request);
178
- const fetchResponse = await consoleStaticResponder(fetchRequest);
179
- if (!fetchResponse) {
180
- response.statusCode = 404;
181
- response.end('Not Found');
182
- return;
183
- }
184
-
185
- await writeFetchResponse(response, fetchResponse, method);
186
- })().catch((error: unknown) => {
187
- const message = error instanceof Error ? error.message : String(error);
188
- printError(`Console request failed: ${message}`);
189
- if (!response.headersSent) {
190
- response.statusCode = 500;
191
- }
192
- response.end('Internal Server Error');
193
- });
194
- });
195
-
196
- await new Promise<void>((resolvePromise, rejectPromise) => {
197
- server.once('error', rejectPromise);
198
- server.listen(port, host, () => {
199
- server.off('error', rejectPromise);
200
- resolvePromise();
201
- });
202
- }).catch((error: unknown) => {
203
- const message = error instanceof Error ? error.message : String(error);
204
- throw new Error(`Failed to start console server: ${message}`);
205
- });
206
-
207
- const address = server.address();
208
- if (!address || typeof address === 'string') {
209
- throw new Error('Failed to resolve console server address.');
210
- }
211
-
212
- const localUrl = `http://${formatHostForUrl(host)}:${address.port}`;
213
- console.log(`Console running at ${localUrl}`);
214
- if (serverUrl) {
215
- console.log(`Using API server: ${serverUrl}`);
216
- } else {
217
- console.log(
218
- 'No API server configured. Set --server-url to preconfigure connection.'
219
- );
220
- }
221
- if (token) {
222
- console.log('Using preconfigured token.');
223
- }
224
-
225
- if (shouldOpen) {
226
- const openUrl = `http://${browserHost(host)}:${address.port}`;
227
- try {
228
- await openInBrowser(openUrl);
229
- } catch (error: unknown) {
230
- const message = error instanceof Error ? error.message : String(error);
231
- printError(`Unable to open browser automatically: ${message}`);
232
- console.log(`Open manually: ${openUrl}`);
233
- }
234
- }
235
-
236
- await new Promise<void>((resolvePromise) => {
237
- const shutdown = () => {
238
- process.off('SIGINT', shutdown);
239
- process.off('SIGTERM', shutdown);
240
- server.close(() => resolvePromise());
241
- };
242
-
243
- process.on('SIGINT', shutdown);
244
- process.on('SIGTERM', shutdown);
245
- });
246
-
247
- return 0;
248
- } catch (error: unknown) {
249
- printError(error instanceof Error ? error.message : 'Console failed.');
250
- return 1;
251
- }
252
- }