pinggy 0.1.0

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.
Files changed (68) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +182 -0
  3. package/dist/TunnelConfig.js +39 -0
  4. package/dist/TunnelManager.js +155 -0
  5. package/dist/cli/buildConfig.js +323 -0
  6. package/dist/cli/defaults.js +18 -0
  7. package/dist/cli/extendedOptions.js +134 -0
  8. package/dist/cli/help.js +37 -0
  9. package/dist/cli/options.js +36 -0
  10. package/dist/cli/remoteManagement.js +167 -0
  11. package/dist/cli/starCli.js +62 -0
  12. package/dist/cli/websocket_handlers.js +160 -0
  13. package/dist/index.js +60 -0
  14. package/dist/logger.js +67 -0
  15. package/dist/remote_management/handler.js +182 -0
  16. package/dist/remote_management/remoteManagement.js +159 -0
  17. package/dist/remote_management/remote_schema.js +122 -0
  18. package/dist/remote_management/websocket_handlers.js +163 -0
  19. package/dist/tui/asciArt.js +7 -0
  20. package/dist/tui/index.js +75 -0
  21. package/dist/tui/useTerminalSize.js +21 -0
  22. package/dist/tunnel_manager/TunnelManager.js +551 -0
  23. package/dist/tunnel_manager/handler.js +102 -0
  24. package/dist/tunnel_manager/remote_schema.js +106 -0
  25. package/dist/types.js +105 -0
  26. package/dist/utils/parseArgs.js +7 -0
  27. package/dist/utils/printer.js +68 -0
  28. package/dist/utils/util.js +3 -0
  29. package/jest.config.js +19 -0
  30. package/package.json +49 -0
  31. package/src/TunnelConfig.ts +40 -0
  32. package/src/_tests_/build_config.test.ts +91 -0
  33. package/src/cli/buildConfig.ts +357 -0
  34. package/src/cli/defaults.ts +20 -0
  35. package/src/cli/extendedOptions.ts +134 -0
  36. package/src/cli/help.ts +41 -0
  37. package/src/cli/options.ts +46 -0
  38. package/src/cli/starCli.tsx +118 -0
  39. package/src/cli/worker.ts +72 -0
  40. package/src/index.ts +65 -0
  41. package/src/logger.ts +86 -0
  42. package/src/remote_management/handler.ts +199 -0
  43. package/src/remote_management/remoteManagement.ts +168 -0
  44. package/src/remote_management/remote_schema.ts +134 -0
  45. package/src/remote_management/websocket_handlers.ts +166 -0
  46. package/src/tui/asciArt.ts +7 -0
  47. package/src/tui/hooks/useQrCodes.ts +27 -0
  48. package/src/tui/hooks/useReqResHeaders.ts +27 -0
  49. package/src/tui/hooks/useTerminalSize.ts +26 -0
  50. package/src/tui/hooks/useTerminalStats.ts +24 -0
  51. package/src/tui/hooks/useWebDebugger.ts +98 -0
  52. package/src/tui/index.tsx +221 -0
  53. package/src/tui/layout/Borders.tsx +15 -0
  54. package/src/tui/layout/Container.tsx +15 -0
  55. package/src/tui/sections/DebuggerDetailModal.tsx +53 -0
  56. package/src/tui/sections/KeyBindings.tsx +58 -0
  57. package/src/tui/sections/QrCodeSection.tsx +28 -0
  58. package/src/tui/sections/StatsSection.tsx +20 -0
  59. package/src/tui/sections/URLsSection.tsx +53 -0
  60. package/src/tui/utils/utils.ts +35 -0
  61. package/src/tunnel_manager/TunnelManager.ts +646 -0
  62. package/src/types.ts +234 -0
  63. package/src/utils/getFreePort.ts +41 -0
  64. package/src/utils/parseArgs.ts +29 -0
  65. package/src/utils/printer.ts +79 -0
  66. package/src/utils/util.ts +13 -0
  67. package/tsconfig.jest.json +8 -0
  68. package/tsconfig.json +17 -0
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildRemoteManagementWsUrl = buildRemoteManagementWsUrl;
16
+ exports.parseRemoteManagement = parseRemoteManagement;
17
+ exports.initiateRemoteManagement = initiateRemoteManagement;
18
+ const ws_1 = __importDefault(require("ws"));
19
+ const logger_1 = require("../logger");
20
+ const websocket_handlers_1 = require("./websocket_handlers");
21
+ const RECONNECT_SLEEP_MS = 5000; // 5 seconds
22
+ const PING_INTERVAL_MS = 30000; // 30 seconds
23
+ function buildRemoteManagementWsUrl(manage) {
24
+ let baseUrl = (manage || "dashboard.pinggy.io").trim();
25
+ if (!(baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://"))) {
26
+ baseUrl = "wss://" + baseUrl;
27
+ }
28
+ // Avoid duplicate slashes when concatenating
29
+ const trimmed = baseUrl.replace(/\/$/, "");
30
+ return `${trimmed}/backend/api/v1/remote-management/connect`;
31
+ }
32
+ function extractHostname(u) {
33
+ try {
34
+ const url = new URL(u);
35
+ return url.host;
36
+ }
37
+ catch (_a) {
38
+ return u;
39
+ }
40
+ }
41
+ function sleep(ms) {
42
+ return new Promise((res) => setTimeout(res, ms));
43
+ }
44
+ function parseRemoteManagement(values) {
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ const rmToken = values["remote-management"];
47
+ if (typeof rmToken === 'string' && rmToken.trim().length > 0) {
48
+ const manageHost = values["manage"];
49
+ try {
50
+ yield initiateRemoteManagement(rmToken, manageHost);
51
+ return; // Exit after initiating remote management
52
+ }
53
+ catch (e) {
54
+ logger_1.logger.error("Failed to initiate remote management:", e);
55
+ return { Error: e };
56
+ }
57
+ }
58
+ });
59
+ }
60
+ /**
61
+ * Initiate remote management mode with a WebSocket connection.
62
+ * - Connect with Authorization: Bearer <token>
63
+ * - On HTTP 401: print Unauthorized and exit
64
+ * - On other failures: retry every 15 seconds
65
+ * - Keep running until closed or SIGINT
66
+ */
67
+ function initiateRemoteManagement(token, manage) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ if (!token || token.trim().length === 0) {
70
+ throw new Error("Remote management token is required (use --remote-management <TOKEN>)");
71
+ }
72
+ const wsUrl = buildRemoteManagementWsUrl(manage);
73
+ const wsHost = extractHostname(wsUrl);
74
+ logger_1.logger.info("Remote management mode enabled.");
75
+ // Ensure process exits cleanly on Ctrl+C
76
+ let stopRequested = false;
77
+ const sigintHandler = () => { stopRequested = true; };
78
+ process.once('SIGINT', sigintHandler);
79
+ let firstTry = true;
80
+ while (!stopRequested) {
81
+ if (firstTry) {
82
+ firstTry = false;
83
+ console.log(`Connecting to ${wsHost}`);
84
+ logger_1.logger.info("Connecting to remote management", { wsUrl });
85
+ }
86
+ else {
87
+ console.log(`Reconnecting in ${RECONNECT_SLEEP_MS / 1000} seconds.`);
88
+ logger_1.logger.info("Reconnecting after sleep", { seconds: RECONNECT_SLEEP_MS / 1000 });
89
+ yield sleep(RECONNECT_SLEEP_MS);
90
+ if (stopRequested)
91
+ break;
92
+ console.log(`Connecting to ${wsHost}`);
93
+ logger_1.logger.info("Connecting to remote management", { wsUrl });
94
+ }
95
+ const ws = new ws_1.default(wsUrl, {
96
+ headers: { Authorization: `Bearer ${token}` },
97
+ });
98
+ yield new Promise((resolve) => {
99
+ let heartbeat;
100
+ const startHeartbeat = () => {
101
+ heartbeat = setInterval(() => {
102
+ if (ws.readyState !== ws_1.default.OPEN)
103
+ return;
104
+ ws.ping(); // ask server for pong
105
+ }, PING_INTERVAL_MS);
106
+ };
107
+ ws.on("open", () => {
108
+ console.log(`Connected to ${wsHost}`);
109
+ startHeartbeat();
110
+ });
111
+ // Respond to server pings
112
+ ws.on("ping", () => ws.pong());
113
+ let firstMessage = true;
114
+ ws.on('message', (data) => __awaiter(this, void 0, void 0, function* () {
115
+ try {
116
+ if (firstMessage) {
117
+ firstMessage = false;
118
+ console.log("First msg", data.toString("utf-8"));
119
+ const ok = (0, websocket_handlers_1.handleConnectionStatusMessage)(data);
120
+ if (!ok) {
121
+ // The status message itself indicates failure, so close and retry.
122
+ ws.close();
123
+ }
124
+ return; // Wait for the next message (commands)
125
+ }
126
+ const text = data.toString('utf8');
127
+ console.log("WebSocket message received", { text });
128
+ const req = JSON.parse(text);
129
+ console.log("req in management", req);
130
+ const webSocketHandler = new websocket_handlers_1.WebSocketCommandHandler();
131
+ yield webSocketHandler.handle(ws, req);
132
+ }
133
+ catch (e) {
134
+ logger_1.logger.warn("Failed handling websocket message", { error: String(e) });
135
+ }
136
+ }));
137
+ ws.on('unexpected-response', (_req, res) => {
138
+ if (res.statusCode === 401) {
139
+ console.error("Unauthorized. Please enter a valid token.");
140
+ logger_1.logger.error("Unauthorized (401) on remote management connect");
141
+ stopRequested = true; // Mark to stop retrying
142
+ ws.close(); // This will trigger 'close' and resolve the promise
143
+ }
144
+ else {
145
+ logger_1.logger.warn("Unexpected HTTP response on WebSocket connect", { statusCode: res.statusCode });
146
+ ws.close(); // Trigger 'close' to retry
147
+ }
148
+ });
149
+ ws.on('close', (code, reason) => {
150
+ logger_1.logger.info("WebSocket closed", { code, reason: reason.toString() });
151
+ console.log("Connection closed.");
152
+ clearInterval(heartbeat);
153
+ resolve(); // End the promise, allowing the while loop to continue
154
+ });
155
+ ws.on('error', (err) => {
156
+ logger_1.logger.warn("WebSocket error", { error: err.message });
157
+ clearInterval(heartbeat);
158
+ resolve(); // End the promise, allowing the while loop to continue
159
+ });
160
+ });
161
+ if (stopRequested)
162
+ break;
163
+ }
164
+ process.removeListener('SIGINT', sigintHandler);
165
+ logger_1.logger.info("Remote management stopped.");
166
+ });
167
+ }
@@ -0,0 +1,62 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import CLIPrinter from "../utils/printer.js";
12
+ import chalk from "chalk";
13
+ import TunnelTui from "../tui/index.js";
14
+ import { withFullScreen } from "fullscreen-ink";
15
+ export function startCli(finalConfig, manager) {
16
+ return __awaiter(this, void 0, void 0, function* () {
17
+ try {
18
+ let tunnelListenerId;
19
+ CLIPrinter.startSpinner("Connecting to Pinggy...");
20
+ const tunnel = manager.createTunnel(finalConfig);
21
+ if (!finalConfig.NoTUI) {
22
+ tunnelListenerId = manager.registerStatsListener(tunnel.tunnelid, (tunnelId, stats) => {
23
+ var _a;
24
+ // Emit stats to TUI via global callback
25
+ (_a = globalThis.__PINGGY_TUNNEL_STATS__) === null || _a === void 0 ? void 0 : _a.call(globalThis, stats);
26
+ });
27
+ }
28
+ yield manager.startTunnel(tunnel.tunnelid);
29
+ CLIPrinter.stopSpinnerSuccess("Connected to Pinggy");
30
+ const urls = manager.getTunnelUrls(tunnel.tunnelid);
31
+ const greet = manager.getTunnelGreetMessage(tunnel.tunnelid);
32
+ CLIPrinter.success(chalk.bold("Tunnel established!"));
33
+ CLIPrinter.print(chalk.gray("───────────────────────────────"));
34
+ CLIPrinter.info(chalk.cyanBright("Remote URLs:"));
35
+ urls.forEach((url) => CLIPrinter.print(" " + chalk.magentaBright(url)));
36
+ CLIPrinter.print(chalk.gray("───────────────────────────────"));
37
+ // handle greet messages
38
+ if (greet === null || greet === void 0 ? void 0 : greet.includes("not authenticated")) {
39
+ // show unauthenticated warning
40
+ CLIPrinter.warn(chalk.yellowBright(greet));
41
+ }
42
+ else if (greet === null || greet === void 0 ? void 0 : greet.includes("authenticated as")) {
43
+ // extract email
44
+ const emailMatch = /authenticated as (.+)/.exec(greet);
45
+ const email = emailMatch ? emailMatch[1] : greet;
46
+ CLIPrinter.info("Authenticated as: " + chalk.greenBright(email));
47
+ }
48
+ if (!finalConfig.NoTUI) {
49
+ const tui = withFullScreen(_jsx(TunnelTui, { urls: urls, greet: greet || "", tunnelConfig: finalConfig }));
50
+ yield tui.start();
51
+ }
52
+ else {
53
+ CLIPrinter.print(chalk.gray("\nPress Ctrl+C to stop the tunnel.\n"));
54
+ }
55
+ }
56
+ catch (err) {
57
+ CLIPrinter.stopSpinnerFail("Failed to connect");
58
+ CLIPrinter.error(err.message || "Unknown error");
59
+ throw err;
60
+ }
61
+ });
62
+ }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.WebSocketCommandHandler = void 0;
16
+ exports.handleConnectionStatusMessage = handleConnectionStatusMessage;
17
+ const logger_1 = require("../logger");
18
+ const types_1 = require("../types");
19
+ const handler_1 = require("../tunnel_manager/handler");
20
+ const remote_schema_1 = require("../tunnel_manager/remote_schema");
21
+ const zod_1 = __importDefault(require("zod"));
22
+ class WebSocketCommandHandler {
23
+ constructor() {
24
+ this.tunnelHandler = new handler_1.TunnelOperations();
25
+ }
26
+ safeParse(text) {
27
+ if (!text)
28
+ return undefined;
29
+ try {
30
+ return JSON.parse(text);
31
+ }
32
+ catch (e) {
33
+ logger_1.logger.warn("Invalid JSON payload", { error: String(e), text });
34
+ return undefined;
35
+ }
36
+ }
37
+ sendResponse(ws, resp) {
38
+ const payload = Object.assign(Object.assign({}, resp), { response: Buffer.from(resp.response || []).toString("base64") });
39
+ ws.send(JSON.stringify(payload));
40
+ }
41
+ sendError(ws, req, message, code = types_1.ErrorCode.InternalServerError) {
42
+ const resp = (0, types_1.NewErrorResponseObject)({ code, message });
43
+ resp.command = req.command || "";
44
+ resp.requestid = req.requestid || "";
45
+ this.sendResponse(ws, resp);
46
+ }
47
+ handleStartReq(req, raw) {
48
+ console.log("Handle Start Request :", req.requestid);
49
+ const dc = remote_schema_1.StartSchema.parse(raw);
50
+ const result = this.tunnelHandler.handleStart(dc.tunnelConfig);
51
+ console.log("Start result", result);
52
+ return this.wrapResponse(result, req);
53
+ }
54
+ handleStopReq(req, raw) {
55
+ const dc = remote_schema_1.StopSchema.parse(raw);
56
+ const result = this.tunnelHandler.handleStop(dc.tunnelID);
57
+ return this.wrapResponse(result, req);
58
+ }
59
+ handleGetReq(req, raw) {
60
+ const dc = remote_schema_1.GetSchema.parse(raw);
61
+ const result = this.tunnelHandler.handleGet(dc.tunnelID);
62
+ return this.wrapResponse(result, req);
63
+ }
64
+ handleRestartReq(req, raw) {
65
+ const dc = remote_schema_1.RestartSchema.parse(raw);
66
+ const result = this.tunnelHandler.handleRestart(dc.tunnelID);
67
+ return this.wrapResponse(result, req);
68
+ }
69
+ handleUpdateConfigReq(req, raw) {
70
+ const dc = remote_schema_1.UpdateConfigSchema.parse(raw);
71
+ const result = this.tunnelHandler.handleUpdateConfig(dc.tunnelConfig);
72
+ return this.wrapResponse(result, req);
73
+ }
74
+ handleListReq(req) {
75
+ const result = this.tunnelHandler.handleList();
76
+ return this.wrapResponse(result, req);
77
+ }
78
+ wrapResponse(result, req) {
79
+ if ((0, types_1.isErrorResponse)(result)) {
80
+ console.log("Error Response:", result);
81
+ const errResp = (0, types_1.NewErrorResponseObject)(result);
82
+ errResp.command = req.command;
83
+ errResp.requestid = req.requestid;
84
+ return errResp;
85
+ }
86
+ const respObj = (0, types_1.NewResponseObject)(result);
87
+ respObj.command = req.command;
88
+ respObj.requestid = req.requestid;
89
+ console.log("Response Object:", respObj);
90
+ return respObj;
91
+ }
92
+ handle(ws, req) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ console.log("request received", req);
95
+ const cmd = (req.command || "").toLowerCase();
96
+ const raw = this.safeParse(req.data);
97
+ try {
98
+ let response;
99
+ switch (cmd) {
100
+ case "start": {
101
+ response = this.handleStartReq(req, raw);
102
+ break;
103
+ }
104
+ case "stop": {
105
+ response = this.handleStopReq(req, raw);
106
+ break;
107
+ }
108
+ case "get": {
109
+ response = this.handleGetReq(req, raw);
110
+ break;
111
+ }
112
+ case "restart": {
113
+ response = this.handleRestartReq(req, raw);
114
+ break;
115
+ }
116
+ case "updateconfig": {
117
+ response = this.handleUpdateConfigReq(req, raw);
118
+ break;
119
+ }
120
+ case "list": {
121
+ response = this.handleListReq(req);
122
+ break;
123
+ }
124
+ default:
125
+ return this.sendError(ws, req, "Invalid command");
126
+ }
127
+ console.log("Sending response", response);
128
+ this.sendResponse(ws, response);
129
+ }
130
+ catch (e) {
131
+ if (e instanceof zod_1.default.ZodError) {
132
+ logger_1.logger.warn("Validation failed", { cmd, issues: e.issues });
133
+ return this.sendError(ws, req, "Invalid request data", types_1.ErrorCode.InvalidBodyFormatError);
134
+ }
135
+ logger_1.logger.error("Error handling command", { cmd, error: String(e) });
136
+ return this.sendError(ws, req, (e === null || e === void 0 ? void 0 : e.message) || "Internal error");
137
+ }
138
+ });
139
+ }
140
+ }
141
+ exports.WebSocketCommandHandler = WebSocketCommandHandler;
142
+ // // Returns true if connection status is OK else sends logs and returns false
143
+ function handleConnectionStatusMessage(firstMessage) {
144
+ try {
145
+ const text = typeof firstMessage === 'string' ? firstMessage : firstMessage.toString();
146
+ const cs = JSON.parse(text);
147
+ if (!cs.success) {
148
+ const msg = cs.error_msg || "Connection failed";
149
+ console.log("Connection failed:", msg);
150
+ logger_1.logger.warn("Remote management connection failed", { error_code: cs.error_code, error_msg: msg });
151
+ return false;
152
+ }
153
+ return true;
154
+ }
155
+ catch (e) {
156
+ logger_1.logger.warn("Failed to parse connection status message", { error: String(e) });
157
+ // If parsing fails, assume connection is okay
158
+ return true;
159
+ }
160
+ }
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { TunnelManager } from "./tunnel_manager/TunnelManager.js";
12
+ import { printHelpMessage } from "./cli/help.js";
13
+ import { cliOptions } from "./cli/options.js";
14
+ import { buildFinalConfig } from "./cli/buildConfig.js";
15
+ import { configureLogger, logger } from "./logger.js";
16
+ import { parseRemoteManagement } from "./remote_management/remoteManagement.js";
17
+ import { parseCliArgs } from "./utils/parseArgs.js";
18
+ import CLIPrinter from "./utils/printer.js";
19
+ import { startCli } from "./cli/starCli.js";
20
+ function main() {
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ try {
23
+ // Parse arguments from the command line
24
+ const { values, positionals } = parseCliArgs(cliOptions);
25
+ // Configure logger from CLI args
26
+ configureLogger(values);
27
+ if (values.help) {
28
+ printHelpMessage();
29
+ return;
30
+ }
31
+ // Remote management mode
32
+ const parseResult = yield parseRemoteManagement(values);
33
+ if ((parseResult === null || parseResult === void 0 ? void 0 : parseResult.ok) === false) {
34
+ CLIPrinter.error(parseResult.error);
35
+ logger.error("Failed to initiate remote management:", parseResult.error);
36
+ process.exit(1);
37
+ }
38
+ // Build final configuration from parsed args
39
+ logger.debug("Building final config from CLI values and positionals", { values, positionals });
40
+ const finalConfig = buildFinalConfig(values, positionals);
41
+ logger.debug("Final configuration built", finalConfig);
42
+ // Use the TunnelManager to start the tunnel
43
+ const manager = TunnelManager.getInstance();
44
+ const tunnel = yield startCli(finalConfig, manager);
45
+ // Keep the process alive and handle graceful shutdown
46
+ process.on('SIGINT', () => {
47
+ logger.info("SIGINT received: stopping tunnels and exiting");
48
+ console.log("\nStopping all tunnels...");
49
+ manager.stopAllTunnels();
50
+ console.log("Tunnels stopped. Exiting.");
51
+ process.exit(0);
52
+ });
53
+ }
54
+ catch (error) {
55
+ logger.error("Unhandled error in CLI:", error);
56
+ CLIPrinter.error(error);
57
+ }
58
+ });
59
+ }
60
+ main();
package/dist/logger.js ADDED
@@ -0,0 +1,67 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import winston from "winston";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ // Singleton logger instance
16
+ let _logger = null;
17
+ function getLogger() {
18
+ if (!_logger) {
19
+ _logger = winston.createLogger({ level: "info", silent: true });
20
+ }
21
+ return _logger;
22
+ }
23
+ export const logger = getLogger();
24
+ export function configureLogger(values, silent = false) {
25
+ // Parse values from CLI args
26
+ const levelStr = values.loglevel || undefined;
27
+ const filePath = values.logfile || process.env.PINGGY_LOG_FILE || undefined;
28
+ const printlog = values.printlog;
29
+ // Ensure log directory exists if file logging is enabled
30
+ if (filePath) {
31
+ const dir = path.dirname(filePath);
32
+ if (!fs.existsSync(dir))
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ }
35
+ const transports = [];
36
+ // Console transport: only if explicitly requested or env var is set
37
+ const stdoutEnabled = printlog === true || (process.env.PINGGY_LOG_STDOUT || "").toLowerCase() === "true";
38
+ if (stdoutEnabled) {
39
+ transports.push(new winston.transports.Console({
40
+ format: winston.format.combine(winston.format.colorize(), winston.format.timestamp(), winston.format.printf((_a) => {
41
+ var { level, message, timestamp } = _a, meta = __rest(_a, ["level", "message", "timestamp"]);
42
+ return `${timestamp} [${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""}`;
43
+ })),
44
+ }));
45
+ }
46
+ // File transport
47
+ if (filePath) {
48
+ transports.push(new winston.transports.File({
49
+ filename: filePath,
50
+ format: winston.format.combine(winston.format.colorize(), winston.format.timestamp(), winston.format.printf((_a) => {
51
+ var { level, message, timestamp } = _a, meta = __rest(_a, ["level", "message", "timestamp"]);
52
+ return `${timestamp} [${level}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""}`;
53
+ })),
54
+ }));
55
+ }
56
+ const level = (levelStr || process.env.PINGGY_LOG_LEVEL || "info").toLowerCase();
57
+ // Mutate the singleton logger instead of replacing it so all imports keep the same instance.
58
+ const log = getLogger();
59
+ // Remove existing transports and add the new ones
60
+ log.clear();
61
+ for (const t of transports) {
62
+ log.add(t);
63
+ }
64
+ log.level = level;
65
+ log.silent = transports.length === 0 || silent === true;
66
+ return log;
67
+ }