pinggy 0.3.4 → 0.3.5
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/dist/index.cjs +85 -82
- package/dist/index.js +15 -54
- package/dist/{main-CZY6GID4.js → main-K44C44NW.js} +70 -28
- package/package.json +2 -2
- package/src/cli/buildConfig.ts +85 -25
- package/src/cli/defaults.ts +1 -1
- package/src/cli/options.ts +1 -1
- package/src/index.ts +16 -15
- package/src/utils/detect_vc_redist_on_windows.ts +10 -66
package/dist/index.cjs
CHANGED
|
@@ -1281,7 +1281,7 @@ var init_options = __esm({
|
|
|
1281
1281
|
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
1282
1282
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
1283
1283
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
1284
|
-
autoreconnect: { type: "
|
|
1284
|
+
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure. Use -a (defaults to true), -a true, or -a false." },
|
|
1285
1285
|
// Save and load config
|
|
1286
1286
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
1287
1287
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
@@ -1365,7 +1365,7 @@ var init_defaults = __esm({
|
|
|
1365
1365
|
originalRequestUrl: false,
|
|
1366
1366
|
allowPreflight: false,
|
|
1367
1367
|
reverseProxy: false,
|
|
1368
|
-
autoReconnect:
|
|
1368
|
+
autoReconnect: true
|
|
1369
1369
|
};
|
|
1370
1370
|
}
|
|
1371
1371
|
});
|
|
@@ -1506,33 +1506,58 @@ var init_extendedOptions = __esm({
|
|
|
1506
1506
|
});
|
|
1507
1507
|
|
|
1508
1508
|
// src/cli/buildConfig.ts
|
|
1509
|
+
function isKeyword(str) {
|
|
1510
|
+
return KEYWORDS.has(str.toLowerCase());
|
|
1511
|
+
}
|
|
1509
1512
|
function parseUserAndDomain(str) {
|
|
1510
1513
|
let token;
|
|
1511
1514
|
let type;
|
|
1512
1515
|
let server;
|
|
1513
1516
|
let qrCode;
|
|
1514
|
-
|
|
1517
|
+
let forceFlag;
|
|
1518
|
+
if (!str) return { token, type, server, qrCode, forceFlag };
|
|
1515
1519
|
if (str.includes("@")) {
|
|
1516
1520
|
const [user, domain] = str.split("@", 2);
|
|
1517
1521
|
if (domainRegex.test(domain)) {
|
|
1522
|
+
let processKeyword2 = function(keyword) {
|
|
1523
|
+
if ([import_pinggy3.TunnelType.Http, import_pinggy3.TunnelType.Tcp, import_pinggy3.TunnelType.Tls, import_pinggy3.TunnelType.Udp, import_pinggy3.TunnelType.TlsTcp].includes(keyword)) {
|
|
1524
|
+
type = keyword;
|
|
1525
|
+
} else if (keyword === "force") {
|
|
1526
|
+
forceFlag = true;
|
|
1527
|
+
} else if (keyword === "qr") {
|
|
1528
|
+
qrCode = true;
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
var processKeyword = processKeyword2;
|
|
1518
1532
|
server = domain;
|
|
1519
1533
|
const parts = user.split("+");
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1534
|
+
if (parts.length === 0) {
|
|
1535
|
+
return { token, type, server, qrCode, forceFlag };
|
|
1536
|
+
}
|
|
1537
|
+
const firstPart = parts[0];
|
|
1538
|
+
if (!isKeyword(firstPart)) {
|
|
1539
|
+
token = firstPart;
|
|
1540
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1541
|
+
const part = parts[i].toLowerCase();
|
|
1542
|
+
if (!isKeyword(part)) {
|
|
1543
|
+
throw new Error(`Invalid user format: unexpected token '${part}' when keywords are expected.`);
|
|
1544
|
+
}
|
|
1545
|
+
processKeyword2(part);
|
|
1546
|
+
}
|
|
1547
|
+
} else {
|
|
1548
|
+
for (const part of parts) {
|
|
1549
|
+
const lowerPart = part.toLowerCase();
|
|
1550
|
+
if (!isKeyword(lowerPart)) {
|
|
1551
|
+
throw new Error(`Invalid user format: unexpected token '${lowerPart}' when keywords are expected.`);
|
|
1552
|
+
}
|
|
1553
|
+
processKeyword2(lowerPart);
|
|
1529
1554
|
}
|
|
1530
1555
|
}
|
|
1531
1556
|
}
|
|
1532
1557
|
} else if (domainRegex.test(str)) {
|
|
1533
1558
|
server = str;
|
|
1534
1559
|
}
|
|
1535
|
-
return { token, type, server, qrCode };
|
|
1560
|
+
return { token, type, server, qrCode, forceFlag };
|
|
1536
1561
|
}
|
|
1537
1562
|
function parseUsers(positionalArgs, explicitToken) {
|
|
1538
1563
|
let token;
|
|
@@ -1546,6 +1571,8 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
1546
1571
|
if (parsed.server) server = parsed.server;
|
|
1547
1572
|
if (parsed.type) type = parsed.type;
|
|
1548
1573
|
if (parsed.token) token = parsed.token;
|
|
1574
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
1575
|
+
if (parsed.qrCode) qrCode = true;
|
|
1549
1576
|
}
|
|
1550
1577
|
if (remaining.length > 0) {
|
|
1551
1578
|
const first = remaining[0];
|
|
@@ -1553,19 +1580,9 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
1553
1580
|
if (parsed.server) {
|
|
1554
1581
|
server = parsed.server;
|
|
1555
1582
|
if (parsed.type) type = parsed.type;
|
|
1556
|
-
if (parsed.token)
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
const tOnly = parts.filter((p) => p !== "force").join("+");
|
|
1560
|
-
if (tOnly) token = tOnly;
|
|
1561
|
-
if (parts.includes("force")) forceFlag = true;
|
|
1562
|
-
} else {
|
|
1563
|
-
token = parsed.token;
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
if (parsed.qrCode) {
|
|
1567
|
-
qrCode = true;
|
|
1568
|
-
}
|
|
1583
|
+
if (parsed.token) token = parsed.token;
|
|
1584
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
1585
|
+
if (parsed.qrCode) qrCode = true;
|
|
1569
1586
|
remaining = remaining.slice(1);
|
|
1570
1587
|
}
|
|
1571
1588
|
}
|
|
@@ -1747,7 +1764,7 @@ function parseLocalTunnelAddr(finalConfig, values) {
|
|
|
1747
1764
|
const firstL = values.L[0];
|
|
1748
1765
|
const parts = firstL.split(":");
|
|
1749
1766
|
if (parts.length === 3) {
|
|
1750
|
-
const lp = parseInt(parts[
|
|
1767
|
+
const lp = parseInt(parts[0], 10);
|
|
1751
1768
|
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
1752
1769
|
finalConfig.webDebugger = `localhost:${lp}`;
|
|
1753
1770
|
} else {
|
|
@@ -1816,6 +1833,20 @@ function parseServe(finalConfig, values) {
|
|
|
1816
1833
|
finalConfig.serve = sv;
|
|
1817
1834
|
return null;
|
|
1818
1835
|
}
|
|
1836
|
+
function parseAutoReconnect(finalConfig, values) {
|
|
1837
|
+
const autoReconnectValue = values.autoreconnect;
|
|
1838
|
+
if (typeof autoReconnectValue === "string") {
|
|
1839
|
+
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
1840
|
+
if (trimmed === "true" || trimmed === "") {
|
|
1841
|
+
finalConfig.autoReconnect = true;
|
|
1842
|
+
} else if (trimmed === "false") {
|
|
1843
|
+
finalConfig.autoReconnect = false;
|
|
1844
|
+
} else {
|
|
1845
|
+
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1819
1850
|
async function buildFinalConfig(values, positionals) {
|
|
1820
1851
|
let token;
|
|
1821
1852
|
let server;
|
|
@@ -1843,7 +1874,7 @@ async function buildFinalConfig(values, positionals) {
|
|
|
1843
1874
|
tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [import_pinggy3.TunnelType.Http],
|
|
1844
1875
|
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
1845
1876
|
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
1846
|
-
autoReconnect:
|
|
1877
|
+
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect
|
|
1847
1878
|
};
|
|
1848
1879
|
parseType(finalConfig, values, type);
|
|
1849
1880
|
parseToken(finalConfig, token || values.token);
|
|
@@ -1857,12 +1888,14 @@ async function buildFinalConfig(values, positionals) {
|
|
|
1857
1888
|
if (lErr instanceof Error) throw lErr;
|
|
1858
1889
|
const serveErr = parseServe(finalConfig, values);
|
|
1859
1890
|
if (serveErr instanceof Error) throw serveErr;
|
|
1891
|
+
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
1892
|
+
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
1860
1893
|
if (forceFlag) finalConfig.force = true;
|
|
1861
1894
|
parseArgs(finalConfig, remainingPositionals);
|
|
1862
1895
|
storeJson(finalConfig, saveconf);
|
|
1863
1896
|
return finalConfig;
|
|
1864
1897
|
}
|
|
1865
|
-
var import_pinggy3, import_fs3, import_path3, domainRegex, VALID_PROTOCOLS;
|
|
1898
|
+
var import_pinggy3, import_fs3, import_path3, domainRegex, KEYWORDS, VALID_PROTOCOLS;
|
|
1866
1899
|
var init_buildConfig = __esm({
|
|
1867
1900
|
"src/cli/buildConfig.ts"() {
|
|
1868
1901
|
"use strict";
|
|
@@ -1875,6 +1908,15 @@ var init_buildConfig = __esm({
|
|
|
1875
1908
|
import_fs3 = __toESM(require("fs"), 1);
|
|
1876
1909
|
import_path3 = __toESM(require("path"), 1);
|
|
1877
1910
|
domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
1911
|
+
KEYWORDS = /* @__PURE__ */ new Set([
|
|
1912
|
+
import_pinggy3.TunnelType.Http,
|
|
1913
|
+
import_pinggy3.TunnelType.Tcp,
|
|
1914
|
+
import_pinggy3.TunnelType.Tls,
|
|
1915
|
+
import_pinggy3.TunnelType.Udp,
|
|
1916
|
+
import_pinggy3.TunnelType.TlsTcp,
|
|
1917
|
+
"force",
|
|
1918
|
+
"qr"
|
|
1919
|
+
]);
|
|
1878
1920
|
VALID_PROTOCOLS = ["http", "tcp", "udp", "tls"];
|
|
1879
1921
|
}
|
|
1880
1922
|
});
|
|
@@ -4275,52 +4317,21 @@ function checkRegistry() {
|
|
|
4275
4317
|
}
|
|
4276
4318
|
return false;
|
|
4277
4319
|
}
|
|
4278
|
-
function
|
|
4279
|
-
if (import_os.default.platform() !== "win32") return null;
|
|
4280
|
-
try {
|
|
4281
|
-
for (const key of REGISTRY_KEYS) {
|
|
4282
|
-
const cmd = `reg query "${key}" /v Version 2>nul`;
|
|
4283
|
-
const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8" });
|
|
4284
|
-
const match = result.match(/Version\s+REG_SZ\s+(\S+)/);
|
|
4285
|
-
if (match) {
|
|
4286
|
-
return match[1];
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
} catch {
|
|
4290
|
-
}
|
|
4291
|
-
return null;
|
|
4292
|
-
}
|
|
4293
|
-
function hasVCRedist() {
|
|
4294
|
-
if (import_os.default.platform() !== "win32") {
|
|
4295
|
-
return true;
|
|
4296
|
-
}
|
|
4297
|
-
if (checkRegistry()) {
|
|
4298
|
-
return true;
|
|
4299
|
-
}
|
|
4300
|
-
if (checkDLLs()) {
|
|
4301
|
-
return true;
|
|
4302
|
-
}
|
|
4303
|
-
return false;
|
|
4304
|
-
}
|
|
4305
|
-
function getVCRedistStatus() {
|
|
4320
|
+
function checkVCRedist() {
|
|
4306
4321
|
if (import_os.default.platform() !== "win32") {
|
|
4307
4322
|
return {
|
|
4308
4323
|
required: false,
|
|
4309
4324
|
installed: true,
|
|
4310
|
-
|
|
4311
|
-
method: "non-windows"
|
|
4325
|
+
message: null
|
|
4312
4326
|
};
|
|
4313
4327
|
}
|
|
4314
4328
|
const registryInstalled = checkRegistry();
|
|
4315
4329
|
const dllsPresent = checkDLLs();
|
|
4316
|
-
const
|
|
4330
|
+
const installed = registryInstalled || dllsPresent;
|
|
4317
4331
|
return {
|
|
4318
4332
|
required: true,
|
|
4319
|
-
installed
|
|
4320
|
-
|
|
4321
|
-
registryCheck: registryInstalled,
|
|
4322
|
-
dllCheck: dllsPresent,
|
|
4323
|
-
method: registryInstalled ? "registry" : dllsPresent ? "dll" : "none"
|
|
4333
|
+
installed,
|
|
4334
|
+
message: installed ? null : "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n"
|
|
4324
4335
|
};
|
|
4325
4336
|
}
|
|
4326
4337
|
async function openDownloadPage() {
|
|
@@ -4343,27 +4354,19 @@ async function openDownloadPage() {
|
|
|
4343
4354
|
printer_default.info(url + "\n");
|
|
4344
4355
|
}
|
|
4345
4356
|
}
|
|
4346
|
-
function getVCRedistMessage() {
|
|
4347
|
-
const status = getVCRedistStatus();
|
|
4348
|
-
if (!status.required || status.installed) {
|
|
4349
|
-
return null;
|
|
4350
|
-
}
|
|
4351
|
-
return {
|
|
4352
|
-
error: true,
|
|
4353
|
-
message: "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\nPlease download and install it using the link below, then restart this application.\n"
|
|
4354
|
-
};
|
|
4355
|
-
}
|
|
4356
4357
|
|
|
4357
4358
|
// src/index.ts
|
|
4358
4359
|
init_printer();
|
|
4359
4360
|
async function verifyAndLoad() {
|
|
4360
|
-
if (process.platform === "win32"
|
|
4361
|
-
const
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4361
|
+
if (process.platform === "win32") {
|
|
4362
|
+
const vcRedist = checkVCRedist();
|
|
4363
|
+
if (!vcRedist.installed) {
|
|
4364
|
+
printer_default.warn(
|
|
4365
|
+
vcRedist.message ?? "This application requires the Microsoft Visual C++ Runtime on Windows."
|
|
4366
|
+
);
|
|
4367
|
+
await openDownloadPage();
|
|
4368
|
+
process.exit(1);
|
|
4369
|
+
}
|
|
4367
4370
|
}
|
|
4368
4371
|
await Promise.resolve().then(() => (init_main(), main_exports));
|
|
4369
4372
|
}
|
package/dist/index.js
CHANGED
|
@@ -43,52 +43,21 @@ function checkRegistry() {
|
|
|
43
43
|
}
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
if (os.platform() !== "win32") return null;
|
|
48
|
-
try {
|
|
49
|
-
for (const key of REGISTRY_KEYS) {
|
|
50
|
-
const cmd = `reg query "${key}" /v Version 2>nul`;
|
|
51
|
-
const result = execSync(cmd, { encoding: "utf8" });
|
|
52
|
-
const match = result.match(/Version\s+REG_SZ\s+(\S+)/);
|
|
53
|
-
if (match) {
|
|
54
|
-
return match[1];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
function hasVCRedist() {
|
|
62
|
-
if (os.platform() !== "win32") {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
if (checkRegistry()) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
if (checkDLLs()) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
function getVCRedistStatus() {
|
|
46
|
+
function checkVCRedist() {
|
|
74
47
|
if (os.platform() !== "win32") {
|
|
75
48
|
return {
|
|
76
49
|
required: false,
|
|
77
50
|
installed: true,
|
|
78
|
-
|
|
79
|
-
method: "non-windows"
|
|
51
|
+
message: null
|
|
80
52
|
};
|
|
81
53
|
}
|
|
82
54
|
const registryInstalled = checkRegistry();
|
|
83
55
|
const dllsPresent = checkDLLs();
|
|
84
|
-
const
|
|
56
|
+
const installed = registryInstalled || dllsPresent;
|
|
85
57
|
return {
|
|
86
58
|
required: true,
|
|
87
|
-
installed
|
|
88
|
-
|
|
89
|
-
registryCheck: registryInstalled,
|
|
90
|
-
dllCheck: dllsPresent,
|
|
91
|
-
method: registryInstalled ? "registry" : dllsPresent ? "dll" : "none"
|
|
59
|
+
installed,
|
|
60
|
+
message: installed ? null : "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n"
|
|
92
61
|
};
|
|
93
62
|
}
|
|
94
63
|
async function openDownloadPage() {
|
|
@@ -111,28 +80,20 @@ async function openDownloadPage() {
|
|
|
111
80
|
printer_default.info(url + "\n");
|
|
112
81
|
}
|
|
113
82
|
}
|
|
114
|
-
function getVCRedistMessage() {
|
|
115
|
-
const status = getVCRedistStatus();
|
|
116
|
-
if (!status.required || status.installed) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
error: true,
|
|
121
|
-
message: "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\nPlease download and install it using the link below, then restart this application.\n"
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
83
|
|
|
125
84
|
// src/index.ts
|
|
126
85
|
async function verifyAndLoad() {
|
|
127
|
-
if (process.platform === "win32"
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
86
|
+
if (process.platform === "win32") {
|
|
87
|
+
const vcRedist = checkVCRedist();
|
|
88
|
+
if (!vcRedist.installed) {
|
|
89
|
+
printer_default.warn(
|
|
90
|
+
vcRedist.message ?? "This application requires the Microsoft Visual C++ Runtime on Windows."
|
|
91
|
+
);
|
|
92
|
+
await openDownloadPage();
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
134
95
|
}
|
|
135
|
-
await import("./main-
|
|
96
|
+
await import("./main-K44C44NW.js");
|
|
136
97
|
}
|
|
137
98
|
verifyAndLoad().catch((err) => {
|
|
138
99
|
printer_default.error(`Failed to start CLI:, ${err}`);
|
|
@@ -995,7 +995,7 @@ var cliOptions = {
|
|
|
995
995
|
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
996
996
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
997
997
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
998
|
-
autoreconnect: { type: "
|
|
998
|
+
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure. Use -a (defaults to true), -a true, or -a false." },
|
|
999
999
|
// Save and load config
|
|
1000
1000
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
1001
1001
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
@@ -1065,7 +1065,7 @@ var defaultOptions = {
|
|
|
1065
1065
|
originalRequestUrl: false,
|
|
1066
1066
|
allowPreflight: false,
|
|
1067
1067
|
reverseProxy: false,
|
|
1068
|
-
autoReconnect:
|
|
1068
|
+
autoReconnect: true
|
|
1069
1069
|
};
|
|
1070
1070
|
|
|
1071
1071
|
// src/cli/extendedOptions.ts
|
|
@@ -1199,33 +1199,67 @@ import { TunnelType as TunnelType2 } from "@pinggy/pinggy";
|
|
|
1199
1199
|
import fs from "fs";
|
|
1200
1200
|
import path2 from "path";
|
|
1201
1201
|
var domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
1202
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
1203
|
+
TunnelType2.Http,
|
|
1204
|
+
TunnelType2.Tcp,
|
|
1205
|
+
TunnelType2.Tls,
|
|
1206
|
+
TunnelType2.Udp,
|
|
1207
|
+
TunnelType2.TlsTcp,
|
|
1208
|
+
"force",
|
|
1209
|
+
"qr"
|
|
1210
|
+
]);
|
|
1211
|
+
function isKeyword(str) {
|
|
1212
|
+
return KEYWORDS.has(str.toLowerCase());
|
|
1213
|
+
}
|
|
1202
1214
|
function parseUserAndDomain(str) {
|
|
1203
1215
|
let token;
|
|
1204
1216
|
let type;
|
|
1205
1217
|
let server;
|
|
1206
1218
|
let qrCode;
|
|
1207
|
-
|
|
1219
|
+
let forceFlag;
|
|
1220
|
+
if (!str) return { token, type, server, qrCode, forceFlag };
|
|
1208
1221
|
if (str.includes("@")) {
|
|
1209
1222
|
const [user, domain] = str.split("@", 2);
|
|
1210
1223
|
if (domainRegex.test(domain)) {
|
|
1224
|
+
let processKeyword2 = function(keyword) {
|
|
1225
|
+
if ([TunnelType2.Http, TunnelType2.Tcp, TunnelType2.Tls, TunnelType2.Udp, TunnelType2.TlsTcp].includes(keyword)) {
|
|
1226
|
+
type = keyword;
|
|
1227
|
+
} else if (keyword === "force") {
|
|
1228
|
+
forceFlag = true;
|
|
1229
|
+
} else if (keyword === "qr") {
|
|
1230
|
+
qrCode = true;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
var processKeyword = processKeyword2;
|
|
1211
1234
|
server = domain;
|
|
1212
1235
|
const parts = user.split("+");
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1236
|
+
if (parts.length === 0) {
|
|
1237
|
+
return { token, type, server, qrCode, forceFlag };
|
|
1238
|
+
}
|
|
1239
|
+
const firstPart = parts[0];
|
|
1240
|
+
if (!isKeyword(firstPart)) {
|
|
1241
|
+
token = firstPart;
|
|
1242
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1243
|
+
const part = parts[i].toLowerCase();
|
|
1244
|
+
if (!isKeyword(part)) {
|
|
1245
|
+
throw new Error(`Invalid user format: unexpected token '${part}' when keywords are expected.`);
|
|
1246
|
+
}
|
|
1247
|
+
processKeyword2(part);
|
|
1248
|
+
}
|
|
1249
|
+
} else {
|
|
1250
|
+
for (const part of parts) {
|
|
1251
|
+
const lowerPart = part.toLowerCase();
|
|
1252
|
+
if (!isKeyword(lowerPart)) {
|
|
1253
|
+
throw new Error(`Invalid user format: unexpected token '${lowerPart}' when keywords are expected.`);
|
|
1254
|
+
}
|
|
1255
|
+
processKeyword2(lowerPart);
|
|
1222
1256
|
}
|
|
1223
1257
|
}
|
|
1224
1258
|
}
|
|
1225
1259
|
} else if (domainRegex.test(str)) {
|
|
1226
1260
|
server = str;
|
|
1227
1261
|
}
|
|
1228
|
-
return { token, type, server, qrCode };
|
|
1262
|
+
return { token, type, server, qrCode, forceFlag };
|
|
1229
1263
|
}
|
|
1230
1264
|
function parseUsers(positionalArgs, explicitToken) {
|
|
1231
1265
|
let token;
|
|
@@ -1239,6 +1273,8 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
1239
1273
|
if (parsed.server) server = parsed.server;
|
|
1240
1274
|
if (parsed.type) type = parsed.type;
|
|
1241
1275
|
if (parsed.token) token = parsed.token;
|
|
1276
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
1277
|
+
if (parsed.qrCode) qrCode = true;
|
|
1242
1278
|
}
|
|
1243
1279
|
if (remaining.length > 0) {
|
|
1244
1280
|
const first = remaining[0];
|
|
@@ -1246,19 +1282,9 @@ function parseUsers(positionalArgs, explicitToken) {
|
|
|
1246
1282
|
if (parsed.server) {
|
|
1247
1283
|
server = parsed.server;
|
|
1248
1284
|
if (parsed.type) type = parsed.type;
|
|
1249
|
-
if (parsed.token)
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
const tOnly = parts.filter((p) => p !== "force").join("+");
|
|
1253
|
-
if (tOnly) token = tOnly;
|
|
1254
|
-
if (parts.includes("force")) forceFlag = true;
|
|
1255
|
-
} else {
|
|
1256
|
-
token = parsed.token;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
if (parsed.qrCode) {
|
|
1260
|
-
qrCode = true;
|
|
1261
|
-
}
|
|
1285
|
+
if (parsed.token) token = parsed.token;
|
|
1286
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
1287
|
+
if (parsed.qrCode) qrCode = true;
|
|
1262
1288
|
remaining = remaining.slice(1);
|
|
1263
1289
|
}
|
|
1264
1290
|
}
|
|
@@ -1441,7 +1467,7 @@ function parseLocalTunnelAddr(finalConfig, values) {
|
|
|
1441
1467
|
const firstL = values.L[0];
|
|
1442
1468
|
const parts = firstL.split(":");
|
|
1443
1469
|
if (parts.length === 3) {
|
|
1444
|
-
const lp = parseInt(parts[
|
|
1470
|
+
const lp = parseInt(parts[0], 10);
|
|
1445
1471
|
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
1446
1472
|
finalConfig.webDebugger = `localhost:${lp}`;
|
|
1447
1473
|
} else {
|
|
@@ -1510,6 +1536,20 @@ function parseServe(finalConfig, values) {
|
|
|
1510
1536
|
finalConfig.serve = sv;
|
|
1511
1537
|
return null;
|
|
1512
1538
|
}
|
|
1539
|
+
function parseAutoReconnect(finalConfig, values) {
|
|
1540
|
+
const autoReconnectValue = values.autoreconnect;
|
|
1541
|
+
if (typeof autoReconnectValue === "string") {
|
|
1542
|
+
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
1543
|
+
if (trimmed === "true" || trimmed === "") {
|
|
1544
|
+
finalConfig.autoReconnect = true;
|
|
1545
|
+
} else if (trimmed === "false") {
|
|
1546
|
+
finalConfig.autoReconnect = false;
|
|
1547
|
+
} else {
|
|
1548
|
+
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1513
1553
|
async function buildFinalConfig(values, positionals) {
|
|
1514
1554
|
let token;
|
|
1515
1555
|
let server;
|
|
@@ -1537,7 +1577,7 @@ async function buildFinalConfig(values, positionals) {
|
|
|
1537
1577
|
tunnelType: initialTunnel ? [initialTunnel] : configFromFile?.tunnelType || [TunnelType2.Http],
|
|
1538
1578
|
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
1539
1579
|
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
1540
|
-
autoReconnect:
|
|
1580
|
+
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect
|
|
1541
1581
|
};
|
|
1542
1582
|
parseType(finalConfig, values, type);
|
|
1543
1583
|
parseToken(finalConfig, token || values.token);
|
|
@@ -1551,6 +1591,8 @@ async function buildFinalConfig(values, positionals) {
|
|
|
1551
1591
|
if (lErr instanceof Error) throw lErr;
|
|
1552
1592
|
const serveErr = parseServe(finalConfig, values);
|
|
1553
1593
|
if (serveErr instanceof Error) throw serveErr;
|
|
1594
|
+
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
1595
|
+
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
1554
1596
|
if (forceFlag) finalConfig.force = true;
|
|
1555
1597
|
parseArgs(finalConfig, remainingPositionals);
|
|
1556
1598
|
storeJson(finalConfig, saveconf);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinggy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Create secure, shareable tunnels to your localhost and manage them from the command line. ",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@pinggy/pinggy": "^0.3.
|
|
53
|
+
"@pinggy/pinggy": "^0.3.3",
|
|
54
54
|
"blessed": "^0.1.81",
|
|
55
55
|
"clipboardy": "^5.0.0",
|
|
56
56
|
"mime": "^4.1.0",
|
package/src/cli/buildConfig.ts
CHANGED
|
@@ -11,36 +11,85 @@ import path from "path";
|
|
|
11
11
|
|
|
12
12
|
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
13
13
|
|
|
14
|
+
// Define the set of valid keywords
|
|
15
|
+
const KEYWORDS = new Set([
|
|
16
|
+
TunnelType.Http,
|
|
17
|
+
TunnelType.Tcp,
|
|
18
|
+
TunnelType.Tls,
|
|
19
|
+
TunnelType.Udp,
|
|
20
|
+
TunnelType.TlsTcp,
|
|
21
|
+
'force',
|
|
22
|
+
'qr',
|
|
23
|
+
] as const);
|
|
24
|
+
|
|
25
|
+
type Keyword = typeof KEYWORDS extends Set<infer T> ? T : never;
|
|
26
|
+
|
|
27
|
+
function isKeyword(str: string): boolean {
|
|
28
|
+
return KEYWORDS.has(str.toLowerCase() as Keyword);
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
function parseUserAndDomain(str: string) {
|
|
15
32
|
let token: string | undefined;
|
|
16
33
|
let type: string | undefined;
|
|
17
34
|
let server: string | undefined;
|
|
18
35
|
let qrCode: boolean | undefined;
|
|
36
|
+
let forceFlag: boolean | undefined;
|
|
19
37
|
|
|
20
|
-
if (!str) return { token, type, server, qrCode } as const;
|
|
38
|
+
if (!str) return { token, type, server, qrCode, forceFlag } as const;
|
|
21
39
|
|
|
22
40
|
if (str.includes('@')) {
|
|
23
41
|
const [user, domain] = str.split('@', 2);
|
|
24
42
|
if (domainRegex.test(domain)) {
|
|
25
43
|
server = domain;
|
|
44
|
+
|
|
26
45
|
// parse user modifiers like token+type or just type
|
|
27
46
|
const parts = user.split('+');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
|
|
48
|
+
if (parts.length === 0) {
|
|
49
|
+
return { token, type, server, qrCode, forceFlag } as const;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const firstPart = parts[0];
|
|
53
|
+
|
|
54
|
+
// If the first part is NOT a keyword, it's a token
|
|
55
|
+
// All subsequent parts must be keywords
|
|
56
|
+
if (!isKeyword(firstPart)) {
|
|
57
|
+
token = firstPart;
|
|
58
|
+
// Process remaining parts as keywords
|
|
59
|
+
for (let i = 1; i < parts.length; i++) {
|
|
60
|
+
const part = parts[i].toLowerCase();
|
|
61
|
+
if (!isKeyword(part)) {
|
|
62
|
+
throw new Error(`Invalid user format: unexpected token '${part}' when keywords are expected.`);
|
|
63
|
+
}
|
|
64
|
+
processKeyword(part);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// First part is a keyword, all parts must be keywords
|
|
68
|
+
for (const part of parts) {
|
|
69
|
+
const lowerPart = part.toLowerCase();
|
|
70
|
+
if (!isKeyword(lowerPart)) {
|
|
71
|
+
// Invalid: non-keyword when keywords are expected
|
|
72
|
+
throw new Error(`Invalid user format: unexpected token '${lowerPart}' when keywords are expected.`);
|
|
73
|
+
}
|
|
74
|
+
processKeyword(lowerPart);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function processKeyword(keyword: string) {
|
|
79
|
+
if ([TunnelType.Http, TunnelType.Tcp, TunnelType.Tls, TunnelType.Udp, TunnelType.TlsTcp].includes(keyword as TunnelType)) {
|
|
80
|
+
type = keyword;
|
|
81
|
+
} else if (keyword === 'force') {
|
|
82
|
+
forceFlag = true;
|
|
83
|
+
} else if (keyword === 'qr') {
|
|
34
84
|
qrCode = true;
|
|
35
|
-
} else {
|
|
36
|
-
token = (token ? token + '+' : '') + part;
|
|
37
85
|
}
|
|
38
86
|
}
|
|
39
87
|
}
|
|
40
88
|
} else if (domainRegex.test(str)) {
|
|
41
89
|
server = str;
|
|
42
90
|
}
|
|
43
|
-
|
|
91
|
+
|
|
92
|
+
return { token, type, server, qrCode, forceFlag } as const;
|
|
44
93
|
}
|
|
45
94
|
|
|
46
95
|
function parseUsers(positionalArgs: string[], explicitToken?: string) {
|
|
@@ -57,6 +106,8 @@ function parseUsers(positionalArgs: string[], explicitToken?: string) {
|
|
|
57
106
|
if (parsed.server) server = parsed.server;
|
|
58
107
|
if (parsed.type) type = parsed.type;
|
|
59
108
|
if (parsed.token) token = parsed.token;
|
|
109
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
110
|
+
if (parsed.qrCode) qrCode = true;
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
if (remaining.length > 0) {
|
|
@@ -65,19 +116,9 @@ function parseUsers(positionalArgs: string[], explicitToken?: string) {
|
|
|
65
116
|
if (parsed.server) {
|
|
66
117
|
server = parsed.server;
|
|
67
118
|
if (parsed.type) type = parsed.type;
|
|
68
|
-
if (parsed.token)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const tOnly = parts.filter((p) => p !== 'force').join('+');
|
|
72
|
-
if (tOnly) token = tOnly;
|
|
73
|
-
if (parts.includes('force')) forceFlag = true;
|
|
74
|
-
} else {
|
|
75
|
-
token = parsed.token;
|
|
76
|
-
}
|
|
77
|
-
} if (parsed.qrCode) {
|
|
78
|
-
// QR code request detected
|
|
79
|
-
qrCode = true;
|
|
80
|
-
}
|
|
119
|
+
if (parsed.token) token = parsed.token;
|
|
120
|
+
if (parsed.forceFlag) forceFlag = true;
|
|
121
|
+
if (parsed.qrCode) qrCode = true;
|
|
81
122
|
remaining = remaining.slice(1);
|
|
82
123
|
}
|
|
83
124
|
}
|
|
@@ -327,7 +368,7 @@ function parseLocalTunnelAddr(finalConfig: FinalConfig, values: ParsedValues<typ
|
|
|
327
368
|
const firstL = values.L[0] as string;
|
|
328
369
|
const parts = firstL.split(':');
|
|
329
370
|
if (parts.length === 3) {
|
|
330
|
-
const lp = parseInt(parts[
|
|
371
|
+
const lp = parseInt(parts[0], 10);
|
|
331
372
|
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
332
373
|
finalConfig.webDebugger = `localhost:${lp}`;
|
|
333
374
|
} else {
|
|
@@ -410,6 +451,22 @@ function parseServe(finalConfig: FinalConfig, values: ParsedValues<typeof cliOpt
|
|
|
410
451
|
return null;
|
|
411
452
|
}
|
|
412
453
|
|
|
454
|
+
function parseAutoReconnect(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>): Error | null {
|
|
455
|
+
const autoReconnectValue = values.autoreconnect;
|
|
456
|
+
if (typeof autoReconnectValue === 'string') {
|
|
457
|
+
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
458
|
+
if (trimmed === 'true' || trimmed === '') {
|
|
459
|
+
finalConfig.autoReconnect = true;
|
|
460
|
+
} else if (trimmed === 'false') {
|
|
461
|
+
finalConfig.autoReconnect = false;
|
|
462
|
+
} else {
|
|
463
|
+
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
413
470
|
export async function buildFinalConfig(values: ParsedValues<typeof cliOptions>, positionals: string[]): Promise<FinalConfig> {
|
|
414
471
|
let token: string | undefined;
|
|
415
472
|
let server: string | undefined;
|
|
@@ -439,7 +496,7 @@ export async function buildFinalConfig(values: ParsedValues<typeof cliOptions>,
|
|
|
439
496
|
tunnelType: initialTunnel ? [initialTunnel] : (configFromFile?.tunnelType || [TunnelType.Http]),
|
|
440
497
|
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
441
498
|
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
442
|
-
autoReconnect:
|
|
499
|
+
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect,
|
|
443
500
|
};
|
|
444
501
|
|
|
445
502
|
|
|
@@ -463,6 +520,9 @@ export async function buildFinalConfig(values: ParsedValues<typeof cliOptions>,
|
|
|
463
520
|
const serveErr = parseServe(finalConfig, values);
|
|
464
521
|
if (serveErr instanceof Error) throw serveErr;
|
|
465
522
|
|
|
523
|
+
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
524
|
+
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
525
|
+
|
|
466
526
|
// Apply force flag if indicated via user
|
|
467
527
|
if (forceFlag) finalConfig.force = true;
|
|
468
528
|
|
package/src/cli/defaults.ts
CHANGED
package/src/cli/options.ts
CHANGED
|
@@ -27,7 +27,7 @@ export const cliOptions = {
|
|
|
27
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
28
|
vvv: { type: 'boolean' as const, description: 'Enable all logs from Cli, SDK and internal components.' },
|
|
29
29
|
|
|
30
|
-
autoreconnect: { type: '
|
|
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
31
|
|
|
32
32
|
// Save and load config
|
|
33
33
|
saveconf: { type: 'string' as const, description: 'Create the configuration file based on the options provided here' },
|
package/src/index.ts
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
openDownloadPage,
|
|
4
|
+
checkVCRedist,
|
|
5
|
+
openDownloadPage,
|
|
7
6
|
} from "./utils/detect_vc_redist_on_windows.js";
|
|
8
7
|
import CLIPrinter from "./utils/printer.js";
|
|
9
8
|
|
|
10
9
|
async function verifyAndLoad() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// open browser
|
|
19
|
+
await openDownloadPage();
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
process.exit(1);
|
|
22
22
|
}
|
|
23
|
+
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
await import("./main.js");
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
verifyAndLoad().catch((err) => {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
CLIPrinter.error(`Failed to start CLI:, ${err}`);
|
|
30
|
+
process.exit(1);
|
|
30
31
|
});
|
|
@@ -56,66 +56,29 @@ function checkRegistry() {
|
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function getVCRedistVersion() {
|
|
60
|
-
if (os.platform() !== "win32") return null;
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
for (const key of REGISTRY_KEYS) {
|
|
64
|
-
const cmd = `reg query "${key}" /v Version 2>nul`;
|
|
65
|
-
const result = execSync(cmd, { encoding: "utf8" });
|
|
66
|
-
|
|
67
|
-
const match = result.match(/Version\s+REG_SZ\s+(\S+)/);
|
|
68
|
-
if (match) {
|
|
69
|
-
return match[1];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
// Ignore errors
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
59
|
|
|
79
60
|
/**
|
|
80
|
-
* Main detection function
|
|
61
|
+
* Main detection function - returns status and message for VC++ Redistributable
|
|
81
62
|
*/
|
|
82
|
-
export function
|
|
83
|
-
if (os.platform() !== "win32") {
|
|
84
|
-
return true; // Not Windows, assume OK
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (checkRegistry()) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (checkDLLs()) {
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function getVCRedistStatus() {
|
|
63
|
+
export function checkVCRedist() {
|
|
99
64
|
if (os.platform() !== "win32") {
|
|
100
65
|
return {
|
|
101
66
|
required: false,
|
|
102
67
|
installed: true,
|
|
103
|
-
|
|
104
|
-
method: "non-windows",
|
|
68
|
+
message: null,
|
|
105
69
|
};
|
|
106
70
|
}
|
|
107
71
|
|
|
108
72
|
const registryInstalled = checkRegistry();
|
|
109
73
|
const dllsPresent = checkDLLs();
|
|
110
|
-
const
|
|
74
|
+
const installed = registryInstalled || dllsPresent;
|
|
111
75
|
|
|
112
76
|
return {
|
|
113
77
|
required: true,
|
|
114
|
-
installed
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
method: registryInstalled ? "registry" : dllsPresent ? "dll" : "none",
|
|
78
|
+
installed,
|
|
79
|
+
message: installed
|
|
80
|
+
? null
|
|
81
|
+
: "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n"
|
|
119
82
|
};
|
|
120
83
|
}
|
|
121
84
|
|
|
@@ -128,10 +91,10 @@ export async function openDownloadPage() {
|
|
|
128
91
|
}
|
|
129
92
|
const url =
|
|
130
93
|
"https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170";
|
|
131
|
-
|
|
94
|
+
|
|
132
95
|
// Use cmd.exe explicitly for better compatibility
|
|
133
96
|
const command = `cmd.exe /c start "" "${url}"`;
|
|
134
|
-
|
|
97
|
+
|
|
135
98
|
try {
|
|
136
99
|
await execAsync(command);
|
|
137
100
|
CLIPrinter.info("\nOpening Microsoft download page in your browser...");
|
|
@@ -146,22 +109,3 @@ export async function openDownloadPage() {
|
|
|
146
109
|
CLIPrinter.info(url + "\n");
|
|
147
110
|
}
|
|
148
111
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Get error message
|
|
153
|
-
*/
|
|
154
|
-
export function getVCRedistMessage() {
|
|
155
|
-
const status = getVCRedistStatus();
|
|
156
|
-
|
|
157
|
-
if (!status.required || status.installed) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
error: true,
|
|
163
|
-
message:
|
|
164
|
-
"Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n" +
|
|
165
|
-
"Please download and install it using the link below, then restart this application.\n",
|
|
166
|
-
};
|
|
167
|
-
}
|