pinggy 0.3.5 → 0.3.6
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.
- package/README.md +1 -1
- package/dist/chunk-65R2GMKQ.js +2101 -0
- package/dist/index.cjs +1798 -1349
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +24 -2
- package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
- package/package.json +3 -4
- package/.github/workflows/npm-publish-github-packages.yml +0 -34
- package/.github/workflows/publish-binaries.yml +0 -223
- package/Makefile +0 -4
- package/caxa_build.js +0 -24
- package/dist/chunk-T5ESYDJY.js +0 -121
- package/ent.plist +0 -14
- package/jest.config.js +0 -19
- package/src/_tests_/build_config.test.ts +0 -91
- package/src/cli/buildConfig.ts +0 -535
- package/src/cli/defaults.ts +0 -20
- package/src/cli/extendedOptions.ts +0 -153
- package/src/cli/help.ts +0 -43
- package/src/cli/options.ts +0 -50
- package/src/cli/starCli.ts +0 -229
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -138
- package/src/main.ts +0 -87
- package/src/remote_management/handler.ts +0 -244
- package/src/remote_management/remoteManagement.ts +0 -226
- package/src/remote_management/remote_schema.ts +0 -176
- package/src/remote_management/websocket_handlers.ts +0 -180
- package/src/tui/blessed/TunnelTui.ts +0 -340
- package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
- package/src/tui/blessed/components/KeyBindings.ts +0 -236
- package/src/tui/blessed/components/Modals.ts +0 -302
- package/src/tui/blessed/components/UIComponents.ts +0 -306
- package/src/tui/blessed/components/index.ts +0 -4
- package/src/tui/blessed/config.ts +0 -53
- package/src/tui/blessed/headerFetcher.ts +0 -42
- package/src/tui/blessed/index.ts +0 -2
- package/src/tui/blessed/qrCodeGenerator.ts +0 -20
- package/src/tui/blessed/webDebuggerConnection.ts +0 -128
- package/src/tui/ink/asciArt.ts +0 -7
- package/src/tui/ink/hooks/useQrCodes.ts +0 -27
- package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
- package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
- package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
- package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
- package/src/tui/ink/index.tsx +0 -243
- package/src/tui/ink/layout/Borders.tsx +0 -15
- package/src/tui/ink/layout/Container.tsx +0 -15
- package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
- package/src/tui/ink/sections/KeyBindings.tsx +0 -58
- package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
- package/src/tui/ink/sections/StatsSection.tsx +0 -20
- package/src/tui/ink/sections/URLsSection.tsx +0 -53
- package/src/tui/ink/utils/utils.ts +0 -35
- package/src/tui/spinner/spinner.ts +0 -64
- package/src/tunnel_manager/TunnelManager.ts +0 -1212
- package/src/types.ts +0 -255
- package/src/utils/FileServer.ts +0 -112
- package/src/utils/detect_vc_redist_on_windows.ts +0 -111
- package/src/utils/getFreePort.ts +0 -41
- package/src/utils/htmlTemplates.ts +0 -146
- package/src/utils/parseArgs.ts +0 -79
- package/src/utils/printer.ts +0 -81
- package/src/utils/util.ts +0 -18
- package/src/workers/file_serve_worker.ts +0 -33
- package/tsconfig.json +0 -17
- package/tsup.config.ts +0 -12
package/src/cli/help.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { cliOptions } from "./options.js";
|
|
2
|
-
|
|
3
|
-
export function printHelpMessage() {
|
|
4
|
-
console.log("\nPinggy CLI Tool - Create secure tunnels to your localhost.");
|
|
5
|
-
console.log("\nUsage:");
|
|
6
|
-
console.log(" pinggy [options] -l <port>\n");
|
|
7
|
-
|
|
8
|
-
console.log("Options:");
|
|
9
|
-
for (const [key, value] of Object.entries(cliOptions)) {
|
|
10
|
-
if ((value as any).hidden) continue;
|
|
11
|
-
const short = 'short' in value && (value as any).short ? `-${(value as any).short}, ` : ' ';
|
|
12
|
-
const optType = (value as any).type === 'boolean' ? '' : '<value>';
|
|
13
|
-
console.log(` ${short}--${key.padEnd(17)} ${optType.padEnd(8)} ${(value as any).description}`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
console.log("\nExtended options :");
|
|
17
|
-
console.log(" x:https Enforce HTTPS only (redirect HTTP to HTTPS)");
|
|
18
|
-
console.log(" x:noreverseproxy Disable built-in reverse-proxy header injection");
|
|
19
|
-
console.log(" x:localservertls:host Connect to local HTTPS server with SNI");
|
|
20
|
-
console.log(" x:passpreflight Pass CORS preflight requests unchanged");
|
|
21
|
-
console.log(" a:Key:Val Add header");
|
|
22
|
-
console.log(" u:Key:Val Update header");
|
|
23
|
-
console.log(" r:Key Remove header");
|
|
24
|
-
console.log(" b:user:pass Basic auth");
|
|
25
|
-
console.log(" k:BEARER Bearer token");
|
|
26
|
-
console.log(" w:192.168.1.0/24 IP whitelist (CIDR)");
|
|
27
|
-
|
|
28
|
-
console.log("\nExamples (User-friendly):");
|
|
29
|
-
console.log(" pinggy -l 3000 # HTTP(S) tunnel to localhost port 3000");
|
|
30
|
-
console.log(" pinggy --type tcp -l 22 # TCP tunnel for SSH (port 22)");
|
|
31
|
-
console.log(" pinggy -l 8080 -d 4300 # HTTP tunnel to port 8080 with debugger running at localhost:4300");
|
|
32
|
-
console.log(" pinggy --token mytoken -l 3000 # Authenticated tunnel");
|
|
33
|
-
console.log(" pinggy x:https x:xff -l https://localhost:8443 # HTTPS-only + XFF");
|
|
34
|
-
console.log(" pinggy w:192.168.1.0/24 -l 8080 # IP whitelist restriction");
|
|
35
|
-
|
|
36
|
-
console.log("\nExamples (SSH-style):");
|
|
37
|
-
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
38
|
-
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
39
|
-
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
40
|
-
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region\n");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
package/src/cli/options.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export const cliOptions = {
|
|
2
|
-
// SSH-like options
|
|
3
|
-
R: { type: 'string' as const, multiple: true, description: 'Local port. Eg. -R0:localhost:3000 will forward tunnel connections to local port 3000.' },
|
|
4
|
-
L: { type: 'string' as const, multiple: true, description: 'Web Debugger address. Eg. -L4300:localhost:4300 will start web debugger on port 4300.' },
|
|
5
|
-
o: { type: 'string' as const, multiple: true, description: 'Options', hidden: true },
|
|
6
|
-
'server-port': { type: 'string' as const, short: 'p', description: 'Pinggy server port. Default: 443' },
|
|
7
|
-
|
|
8
|
-
v4: { type: 'boolean' as const, short: '4', description: 'IPv4 only', hidden: true },
|
|
9
|
-
v6: { type: 'boolean' as const, short: '6', description: 'IPv6 only', hidden: true },
|
|
10
|
-
|
|
11
|
-
// These options appear in the ssh command, but we ignore it in CLI
|
|
12
|
-
t: { type: 'boolean' as const, description: 'hidden', hidden: true },
|
|
13
|
-
T: { type: 'boolean' as const, description: 'hidden', hidden: true },
|
|
14
|
-
n: { type: 'boolean' as const, description: 'hidden', hidden: true },
|
|
15
|
-
N: { type: 'boolean' as const, description: 'hidden', hidden: true },
|
|
16
|
-
|
|
17
|
-
// Better options
|
|
18
|
-
type: { type: 'string' as const, description: 'Type of the connection. Eg. --type tcp' },
|
|
19
|
-
localport: { type: 'string' as const, short: 'l', description: 'Takes input as [protocol:][host:]port. Eg. --localport https://localhost:8000 OR -l 3000' },
|
|
20
|
-
debugger: { type: 'string' as const, short: 'd', description: 'Port for web debugger. Eg. --debugger 4300 OR -d 4300' },
|
|
21
|
-
token: { type: 'string' as const, description: 'Token for authentication. Eg. --token TOKEN_VALUE' },
|
|
22
|
-
|
|
23
|
-
// Logging options (CLI overrides env)
|
|
24
|
-
loglevel: { type: 'string' as const, description: 'Logging level: ERROR, INFO, DEBUG. Overrides PINGGY_LOG_LEVEL environment variable' },
|
|
25
|
-
logfile: { type: 'string' as const, description: 'Path to log file. Overrides PINGGY_LOG_FILE environment variable' },
|
|
26
|
-
v: { type: 'boolean' as const, description: 'Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable' },
|
|
27
|
-
vv: { type: 'boolean' as const, description: 'Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs.' },
|
|
28
|
-
vvv: { type: 'boolean' as const, description: 'Enable all logs from Cli, SDK and internal components.' },
|
|
29
|
-
|
|
30
|
-
autoreconnect: { type: 'string' as const, short :'a', description: 'Automatically reconnect tunnel on failure. Use -a (defaults to true), -a true, or -a false.' },
|
|
31
|
-
|
|
32
|
-
// Save and load config
|
|
33
|
-
saveconf: { type: 'string' as const, description: 'Create the configuration file based on the options provided here' },
|
|
34
|
-
conf: { type: 'string' as const, description: 'Use the configuration file as base. Other options will be used to override this file' },
|
|
35
|
-
|
|
36
|
-
// File server
|
|
37
|
-
serve: { type: 'string' as const, description: 'Start a webserver to serve files from the specified path. Eg --serve /path/to/files' },
|
|
38
|
-
|
|
39
|
-
// Remote Control
|
|
40
|
-
'remote-management': { type: 'string' as const, description: 'Enable remote management of tunnels with token. Eg. --remote-management API_KEY' },
|
|
41
|
-
manage: { type: 'string' as const, description: 'Provide a server address to manage tunnels. Eg --manage dashboard.pinggy.io' },
|
|
42
|
-
notui: { type: 'boolean' as const, description: 'Disable TUI in remote management mode' },
|
|
43
|
-
// Misc
|
|
44
|
-
version: { type: 'boolean' as const, description: 'Print version' },
|
|
45
|
-
|
|
46
|
-
// Help
|
|
47
|
-
help: { type: 'boolean' as const, short: 'h', description: 'Show this help message' },
|
|
48
|
-
} as const;
|
|
49
|
-
|
|
50
|
-
export type CliOptions = typeof cliOptions;
|
package/src/cli/starCli.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import CLIPrinter from "../utils/printer.js";
|
|
2
|
-
import { ManagedTunnel, TunnelManager } from "../tunnel_manager/TunnelManager.js";
|
|
3
|
-
import { FinalConfig } from "../types.js";
|
|
4
|
-
import { getFreePort } from "../utils/getFreePort.js";
|
|
5
|
-
import { logger } from "../logger.js";
|
|
6
|
-
import pico from "picocolors";
|
|
7
|
-
import { TunnelTui } from "../tui/blessed/index.js"
|
|
8
|
-
|
|
9
|
-
interface TunnelData {
|
|
10
|
-
urls: string[] | null;
|
|
11
|
-
greet: string | null;
|
|
12
|
-
usage: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const TunnelData: TunnelData = {
|
|
16
|
-
urls: null,
|
|
17
|
-
greet: null,
|
|
18
|
-
usage: null,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
let activeTui: any = null; // TunnelTui type - loaded dynamically
|
|
22
|
-
|
|
23
|
-
let disconnectState: {
|
|
24
|
-
disconnected: boolean;
|
|
25
|
-
error?: string;
|
|
26
|
-
messages?: string[];
|
|
27
|
-
} | null = null;
|
|
28
|
-
|
|
29
|
-
declare global {
|
|
30
|
-
var __PINGGY_TUNNEL_STATS__: ((stats: any) => void) | undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function launchTui(finalConfig: FinalConfig, urls: string[] | null, greet: string | null, tunnel: ManagedTunnel) {
|
|
34
|
-
try {
|
|
35
|
-
const isTTYEnabled = process.stdin.isTTY;
|
|
36
|
-
|
|
37
|
-
if (!isTTYEnabled) {
|
|
38
|
-
CLIPrinter.warn("Unable to initiate the TUI: your terminal does not support the required input mode.");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const tui = new TunnelTui({
|
|
44
|
-
urls: urls ?? [],
|
|
45
|
-
greet: greet ?? "",
|
|
46
|
-
tunnelConfig: finalConfig,
|
|
47
|
-
disconnectInfo: null,
|
|
48
|
-
tunnelInstance: tunnel,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
activeTui = tui;
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
tui.start();
|
|
55
|
-
await tui.waitUntilExit();
|
|
56
|
-
} catch (e) {
|
|
57
|
-
logger.warn("TUI error", e);
|
|
58
|
-
} finally {
|
|
59
|
-
activeTui = null;
|
|
60
|
-
}
|
|
61
|
-
} catch (e) {
|
|
62
|
-
logger.warn("Failed to (re-)initiate TUI", e);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
export async function startCli(finalConfig: FinalConfig, manager: TunnelManager) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!finalConfig.NoTUI && finalConfig.webDebugger === "") {
|
|
72
|
-
// Need a webdebugger port
|
|
73
|
-
const freePort = await getFreePort(finalConfig.webDebugger || "");
|
|
74
|
-
finalConfig.webDebugger = `localhost:${freePort}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const manager = TunnelManager.getInstance();
|
|
79
|
-
const tunnel = await manager.createTunnel(finalConfig);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
CLIPrinter.startSpinner("Connecting to Pinggy...");
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!finalConfig.NoTUI) {
|
|
86
|
-
manager.registerStatsListener(tunnel.tunnelid, (tunnelId, stats) => {
|
|
87
|
-
globalThis.__PINGGY_TUNNEL_STATS__?.(stats)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
manager.registerWorkerErrorListner(tunnel.tunnelid, (_tunnelid: string, error: Error) => {
|
|
93
|
-
|
|
94
|
-
// The CLI terminates in this callback because these errors occur only when the tunnel worker
|
|
95
|
-
// exits, crashes, or encounters critical problems (e.g., authentication failure or primary forwarding failure).
|
|
96
|
-
|
|
97
|
-
CLIPrinter.error(`${error.message}`);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await manager.startTunnel(tunnel.tunnelid);
|
|
102
|
-
CLIPrinter.stopSpinnerSuccess(" Connected to Pinggy");
|
|
103
|
-
CLIPrinter.success(pico.bold("Tunnel established!"));
|
|
104
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
105
|
-
|
|
106
|
-
TunnelData.urls = await manager.getTunnelUrls(tunnel.tunnelid);
|
|
107
|
-
TunnelData.greet = await manager.getTunnelGreetMessage(tunnel.tunnelid);
|
|
108
|
-
|
|
109
|
-
CLIPrinter.info(pico.cyanBright("Remote URLs:"));
|
|
110
|
-
(TunnelData.urls ?? []).forEach((url: string) =>
|
|
111
|
-
CLIPrinter.print(" " + pico.magentaBright(url))
|
|
112
|
-
);
|
|
113
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (TunnelData.greet?.includes("not authenticated")) {
|
|
117
|
-
// show unauthenticated warning
|
|
118
|
-
CLIPrinter.warn(pico.yellowBright(TunnelData.greet));
|
|
119
|
-
} else if (TunnelData.greet?.includes("authenticated as")) {
|
|
120
|
-
// extract email
|
|
121
|
-
const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
|
|
122
|
-
if (emailMatch) {
|
|
123
|
-
const email = emailMatch[1];
|
|
124
|
-
CLIPrinter.info(pico.cyanBright("Authenticated as: " + email));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
129
|
-
CLIPrinter.print(pico.gray("\nPress Ctrl+C to stop the tunnel.\n"));
|
|
130
|
-
|
|
131
|
-
manager.registerDisconnectListener(tunnel.tunnelid, async (tunnelId, error, messages) => {
|
|
132
|
-
if (activeTui) {
|
|
133
|
-
disconnectState = {
|
|
134
|
-
disconnected: true,
|
|
135
|
-
error: error,
|
|
136
|
-
messages: messages
|
|
137
|
-
};
|
|
138
|
-
activeTui.updateDisconnectInfo(disconnectState);
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
// Wait for Blessed TUI to fully exit
|
|
142
|
-
await activeTui.waitUntilExit();
|
|
143
|
-
} catch (e) {
|
|
144
|
-
logger.warn("Failed to wait for TUI exit", e);
|
|
145
|
-
} finally {
|
|
146
|
-
activeTui = null;
|
|
147
|
-
CLIPrinter.warn(`Error in tunnel:`);
|
|
148
|
-
messages?.forEach(function (m) {
|
|
149
|
-
CLIPrinter.warnTxt(m)
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Exit ONLY after blessed has restored the terminal
|
|
153
|
-
// On disconnect only exit if autoReconnect is false otherwise retry will not work
|
|
154
|
-
if (!finalConfig.autoReconnect) {
|
|
155
|
-
process.exit(0);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
messages?.forEach(function (m) {
|
|
160
|
-
CLIPrinter.warn(m)
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// On disconnect only exit if autoReconnect is false otherwise retry will not work
|
|
164
|
-
if (!finalConfig.autoReconnect) {
|
|
165
|
-
process.exit(0);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// start a spinner if autoReconnect is true
|
|
170
|
-
if (finalConfig.autoReconnect) {
|
|
171
|
-
CLIPrinter.startSpinner("Reconnecting to Pinggy");
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
// Listen for tunnel start events (auto-reconnect)
|
|
176
|
-
try {
|
|
177
|
-
await manager.registerStartListener(tunnel.tunnelid, async (tunnelId, urls) => {
|
|
178
|
-
try {
|
|
179
|
-
CLIPrinter.stopSpinnerSuccess("Reconnected to Pinggy");
|
|
180
|
-
} catch (e) {
|
|
181
|
-
// ignore
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
CLIPrinter.success(pico.bold("Tunnel re-established!"));
|
|
185
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
186
|
-
|
|
187
|
-
TunnelData.urls = urls;
|
|
188
|
-
TunnelData.greet = await manager.getTunnelGreetMessage(tunnel.tunnelid);
|
|
189
|
-
|
|
190
|
-
CLIPrinter.info(pico.cyanBright("Remote URLs:"));
|
|
191
|
-
(TunnelData.urls ?? []).forEach((url: string) =>
|
|
192
|
-
CLIPrinter.print(" " + pico.magentaBright(url))
|
|
193
|
-
);
|
|
194
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
195
|
-
|
|
196
|
-
if (TunnelData.greet?.includes("not authenticated")) {
|
|
197
|
-
CLIPrinter.warn(pico.yellowBright(TunnelData.greet));
|
|
198
|
-
} else if (TunnelData.greet?.includes("authenticated as")) {
|
|
199
|
-
const emailMatch = /authenticated as (.+)/.exec(TunnelData.greet);
|
|
200
|
-
if (emailMatch) {
|
|
201
|
-
const email = emailMatch[1];
|
|
202
|
-
CLIPrinter.info(pico.cyanBright("Authenticated as: " + email));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
CLIPrinter.print(pico.gray("───────────────────────────────"));
|
|
207
|
-
CLIPrinter.print(pico.gray("\nPress Ctrl+C to stop the tunnel.\n"));
|
|
208
|
-
|
|
209
|
-
// If the TUI was enabled previously, re-create and start it
|
|
210
|
-
if (!finalConfig.NoTUI) {
|
|
211
|
-
await launchTui(finalConfig, TunnelData.urls, TunnelData.greet, tunnel);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
} catch (e) {
|
|
215
|
-
logger.debug("Failed to register start listener", e);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (!finalConfig.NoTUI) {
|
|
219
|
-
await launchTui(finalConfig, TunnelData.urls, TunnelData.greet,tunnel);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
} catch (err: any) {
|
|
225
|
-
CLIPrinter.stopSpinnerFail("Failed to connect");
|
|
226
|
-
CLIPrinter.error(err.message || "Unknown error");
|
|
227
|
-
throw err;
|
|
228
|
-
}
|
|
229
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
checkVCRedist,
|
|
5
|
-
openDownloadPage,
|
|
6
|
-
} from "./utils/detect_vc_redist_on_windows.js";
|
|
7
|
-
import CLIPrinter from "./utils/printer.js";
|
|
8
|
-
|
|
9
|
-
async function verifyAndLoad() {
|
|
10
|
-
if (process.platform === "win32") {
|
|
11
|
-
const vcRedist = checkVCRedist();
|
|
12
|
-
if ( !vcRedist.installed ) {
|
|
13
|
-
CLIPrinter.warn(
|
|
14
|
-
vcRedist.message ??
|
|
15
|
-
"This application requires the Microsoft Visual C++ Runtime on Windows.",
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
// open browser
|
|
19
|
-
await openDownloadPage();
|
|
20
|
-
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
await import("./main.js");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
verifyAndLoad().catch((err) => {
|
|
29
|
-
CLIPrinter.error(`Failed to start CLI:, ${err}`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
});
|
package/src/logger.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import winston from "winston";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { ParsedValues } from "./utils/parseArgs.js";
|
|
5
|
-
import { cliOptions } from "./cli/options.js";
|
|
6
|
-
import { pinggy, LogLevel } from "@pinggy/pinggy";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// Singleton logger instance
|
|
11
|
-
let _logger: winston.Logger | null = null;
|
|
12
|
-
function getLogger(): winston.Logger {
|
|
13
|
-
if (!_logger) {
|
|
14
|
-
_logger = winston.createLogger({ level: "info", silent: true });
|
|
15
|
-
}
|
|
16
|
-
return _logger;
|
|
17
|
-
}
|
|
18
|
-
export const logger: winston.Logger = getLogger();
|
|
19
|
-
|
|
20
|
-
interface BaseLogConfig {
|
|
21
|
-
level?: string;
|
|
22
|
-
filePath?: string;
|
|
23
|
-
stdout?: boolean;
|
|
24
|
-
source?: boolean;
|
|
25
|
-
silent?: boolean; // if true all logs are suppressed
|
|
26
|
-
enableSdkLog?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function applyLoggingConfig(cfg: BaseLogConfig): winston.Logger {
|
|
30
|
-
const {
|
|
31
|
-
level,
|
|
32
|
-
filePath,
|
|
33
|
-
stdout = false,
|
|
34
|
-
source = false,
|
|
35
|
-
silent = false,
|
|
36
|
-
enableSdkLog = false,
|
|
37
|
-
} = cfg;
|
|
38
|
-
// Set SDK log level
|
|
39
|
-
if (enableSdkLog) {
|
|
40
|
-
enableLoggingByLogLevelInSdk(level ?? "info", filePath!);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Ensure directory exists
|
|
45
|
-
if (filePath) {
|
|
46
|
-
const dir = path.dirname(filePath);
|
|
47
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const transports: winston.transport[] = [];
|
|
51
|
-
|
|
52
|
-
// Console logging
|
|
53
|
-
if (stdout) {
|
|
54
|
-
transports.push(
|
|
55
|
-
new winston.transports.Console({
|
|
56
|
-
format: winston.format.combine(
|
|
57
|
-
winston.format.colorize(),
|
|
58
|
-
winston.format.timestamp(),
|
|
59
|
-
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
|
60
|
-
const srcLabel = source ? "[CLI] " : "";
|
|
61
|
-
return `${timestamp} ${srcLabel}[${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""
|
|
62
|
-
}`;
|
|
63
|
-
})
|
|
64
|
-
),
|
|
65
|
-
})
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// File logging
|
|
70
|
-
if (filePath) {
|
|
71
|
-
transports.push(
|
|
72
|
-
new winston.transports.File({
|
|
73
|
-
filename: filePath,
|
|
74
|
-
format: winston.format.combine(
|
|
75
|
-
winston.format.timestamp(),
|
|
76
|
-
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
|
77
|
-
return `${timestamp} [${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""
|
|
78
|
-
}`;
|
|
79
|
-
})
|
|
80
|
-
),
|
|
81
|
-
})
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Mutate the singleton logger instead of replacing it so all imports keep the same instance.
|
|
86
|
-
const log: winston.Logger = getLogger();
|
|
87
|
-
|
|
88
|
-
// Remove existing transports and add the new ones
|
|
89
|
-
log.clear();
|
|
90
|
-
for (const t of transports) {
|
|
91
|
-
log.add(t);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
log.level = (level || process.env.PINGGY_LOG_LEVEL || "info").toLowerCase();
|
|
95
|
-
log.silent = silent || transports.length === 0;
|
|
96
|
-
|
|
97
|
-
return log;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
export function configureLogger(values: ParsedValues<typeof cliOptions>, silent: boolean = false) {
|
|
102
|
-
|
|
103
|
-
// Parse values from CLI args
|
|
104
|
-
const level = (values.loglevel as string) || undefined;
|
|
105
|
-
const filePath = (values.logfile as string) || process.env.PINGGY_LOG_FILE || undefined;
|
|
106
|
-
const stdout = values.v as boolean || values.vvv || undefined;
|
|
107
|
-
const source = values.vvv ?? false;
|
|
108
|
-
const enableSdkLog = values.vv || values.vvv;
|
|
109
|
-
return applyLoggingConfig({
|
|
110
|
-
level,
|
|
111
|
-
filePath,
|
|
112
|
-
stdout,
|
|
113
|
-
source,
|
|
114
|
-
silent,
|
|
115
|
-
enableSdkLog
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export type BaseLogConfigType = BaseLogConfig;
|
|
121
|
-
|
|
122
|
-
export function enablePackageLogging(opts?: BaseLogConfigType) {
|
|
123
|
-
return applyLoggingConfig(opts ?? {});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function enableLoggingByLogLevelInSdk(loglevel: string | undefined, logFilePath: string | null): void {
|
|
127
|
-
if (!loglevel) return;
|
|
128
|
-
const l = loglevel.toUpperCase();
|
|
129
|
-
|
|
130
|
-
if (loglevel === "DEBUG") {
|
|
131
|
-
pinggy.setDebugLogging(true, LogLevel.DEBUG, logFilePath);
|
|
132
|
-
} else if (loglevel === "ERROR") {
|
|
133
|
-
pinggy.setDebugLogging(true, LogLevel.ERROR, logFilePath);
|
|
134
|
-
} else {
|
|
135
|
-
pinggy.setDebugLogging(true, LogLevel.INFO, logFilePath);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
package/src/main.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { TunnelManager } from "./tunnel_manager/TunnelManager.js";
|
|
3
|
-
import { printHelpMessage } from "./cli/help.js";
|
|
4
|
-
import { cliOptions } from "./cli/options.js";
|
|
5
|
-
import { buildFinalConfig } from "./cli/buildConfig.js";
|
|
6
|
-
import { configureLogger, logger } from "./logger.js";
|
|
7
|
-
import { parseRemoteManagement } from "./remote_management/remoteManagement.js";
|
|
8
|
-
import { parseCliArgs } from "./utils/parseArgs.js";
|
|
9
|
-
import CLIPrinter from "./utils/printer.js";
|
|
10
|
-
import { startCli } from "./cli/starCli.js";
|
|
11
|
-
import { getVersion } from "./utils/util.js";
|
|
12
|
-
import { TunnelOperations, TunnelResponse } from "./remote_management/handler.js";
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import { argv } from 'process';
|
|
15
|
-
import { realpathSync } from 'fs';
|
|
16
|
-
import { enablePackageLogging } from "./logger.js"
|
|
17
|
-
import { getRemoteManagementState, initiateRemoteManagement, closeRemoteManagement } from "./remote_management/remoteManagement.js";
|
|
18
|
-
|
|
19
|
-
export { TunnelManager, TunnelOperations, TunnelResponse, enablePackageLogging, getRemoteManagementState, initiateRemoteManagement, closeRemoteManagement };
|
|
20
|
-
|
|
21
|
-
async function main() {
|
|
22
|
-
try {
|
|
23
|
-
// Parse arguments from the command line
|
|
24
|
-
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
25
|
-
|
|
26
|
-
// Configure logger from CLI args
|
|
27
|
-
configureLogger(values);
|
|
28
|
-
|
|
29
|
-
// Use the TunnelManager to start the tunnel
|
|
30
|
-
const manager = TunnelManager.getInstance();
|
|
31
|
-
|
|
32
|
-
// Keep the process alive and handle graceful shutdown
|
|
33
|
-
process.on('SIGINT', () => {
|
|
34
|
-
logger.info("SIGINT received: stopping tunnels and exiting");
|
|
35
|
-
console.log("\nStopping all tunnels...");
|
|
36
|
-
manager.stopAllTunnels();
|
|
37
|
-
console.log("Tunnels stopped. Exiting.");
|
|
38
|
-
process.exit(0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (!hasAnyArgs || values.help) {
|
|
42
|
-
printHelpMessage();
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (values.version) {
|
|
46
|
-
CLIPrinter.print(`Pinggy CLI version: ${getVersion()}`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Remote management mode
|
|
51
|
-
const parseResult = await parseRemoteManagement(values);
|
|
52
|
-
if (parseResult?.ok === false) {
|
|
53
|
-
CLIPrinter.error(parseResult.error);
|
|
54
|
-
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Build final configuration from parsed args
|
|
60
|
-
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
61
|
-
const finalConfig = await buildFinalConfig(values, positionals);
|
|
62
|
-
logger.debug("Final configuration built", finalConfig);
|
|
63
|
-
await startCli(finalConfig, manager);
|
|
64
|
-
|
|
65
|
-
} catch (error) {
|
|
66
|
-
logger.error("Unhandled error in CLI:", error);
|
|
67
|
-
CLIPrinter.error(error);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Resolve the absolute path of the current module file.
|
|
72
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
73
|
-
|
|
74
|
-
let entryFile: string | null = null;
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
// Resolve the absolute path of the file Node was asked to execute.
|
|
78
|
-
entryFile = argv[1] ? realpathSync(argv[1]) : null;
|
|
79
|
-
} catch (e) {
|
|
80
|
-
entryFile = null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// If this file executed directly from Node then only run main()
|
|
84
|
-
// otherwise (if imported as module), do nothing.
|
|
85
|
-
if (entryFile && entryFile === currentFile) {
|
|
86
|
-
main();
|
|
87
|
-
}
|