vite-plugin-rpx 0.11.0 → 0.11.1
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.d.ts +3 -1
- package/dist/index.js +266 -125
- package/dist/simplified-plugin.d.ts +1 -3
- package/dist/types.d.ts +1 -9
- package/dist/utils.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,7 @@ export type { SimplifiedPluginOptions } from './simplified-plugin';
|
|
|
7
7
|
* @deprecated Use SimplifiedVitePlugin instead to avoid WebSocket port allocation issues
|
|
8
8
|
*/
|
|
9
9
|
export declare function VitePluginRpx(options: VitePluginRpxOptions): Plugin;
|
|
10
|
+
// Export the simplified plugin types and implementation
|
|
10
11
|
export { SimplifiedVitePlugin } from './simplified-plugin';
|
|
11
|
-
|
|
12
|
+
// Set the default export to be the simplified plugin
|
|
13
|
+
export default SimplifiedVitePlugin;
|
package/dist/index.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -3319,13 +3337,14 @@ function getSudoPassword() {
|
|
|
3319
3337
|
}
|
|
3320
3338
|
function execSudoSync(command) {
|
|
3321
3339
|
const sudoPassword = getSudoPassword();
|
|
3340
|
+
const escaped = command.replace(/'/g, `'\\''`);
|
|
3322
3341
|
if (sudoPassword) {
|
|
3323
|
-
return execSync(`echo '${sudoPassword}' | sudo -S ${
|
|
3342
|
+
return execSync(`echo '${sudoPassword}' | sudo -S sh -c '${escaped}' 2>/dev/null`, {
|
|
3324
3343
|
encoding: "utf-8",
|
|
3325
3344
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3326
3345
|
});
|
|
3327
3346
|
}
|
|
3328
|
-
return execSync(`sudo ${
|
|
3347
|
+
return execSync(`sudo sh -c '${escaped}'`, { encoding: "utf-8" });
|
|
3329
3348
|
}
|
|
3330
3349
|
function debugLog(category, message, verbose) {
|
|
3331
3350
|
if (verbose)
|
|
@@ -4796,15 +4815,21 @@ import * as net2 from "node:net";
|
|
|
4796
4815
|
import * as process18 from "node:process";
|
|
4797
4816
|
|
|
4798
4817
|
// ../rpx/src/logger.ts
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4818
|
+
var log = {
|
|
4819
|
+
info: (...args) => console.log("[info]", ...args),
|
|
4820
|
+
success: (...args) => console.log("[success]", ...args),
|
|
4821
|
+
warn: (...args) => console.warn("[warn]", ...args),
|
|
4822
|
+
error: (...args) => console.error("[error]", ...args),
|
|
4823
|
+
debug: (...args) => console.debug("[debug]", ...args),
|
|
4824
|
+
log: (...args) => console.log(...args),
|
|
4825
|
+
start: (...args) => console.log("[start]", ...args),
|
|
4826
|
+
box: (...args) => console.log("[box]", ...args)
|
|
4827
|
+
};
|
|
4803
4828
|
|
|
4804
4829
|
// ../rpx/src/start.ts
|
|
4805
4830
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
4806
4831
|
// ../rpx/package.json
|
|
4807
|
-
var version = "0.11.
|
|
4832
|
+
var version = "0.11.1";
|
|
4808
4833
|
|
|
4809
4834
|
// ../rpx/src/config.ts
|
|
4810
4835
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -9847,13 +9872,13 @@ var defaultConfig3 = {
|
|
|
9847
9872
|
};
|
|
9848
9873
|
|
|
9849
9874
|
// ../rpx/src/hosts.ts
|
|
9875
|
+
await init_utils();
|
|
9850
9876
|
import { exec } from "node:child_process";
|
|
9851
9877
|
import fs2 from "node:fs";
|
|
9852
9878
|
import os from "node:os";
|
|
9853
9879
|
import path from "node:path";
|
|
9854
9880
|
import * as process15 from "node:process";
|
|
9855
9881
|
import { promisify } from "node:util";
|
|
9856
|
-
await init_utils();
|
|
9857
9882
|
var execAsync = promisify(exec);
|
|
9858
9883
|
var hostsFilePath = process15.platform === "win32" ? path.join(process15.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
|
|
9859
9884
|
var sudoPrivilegesAcquired = false;
|
|
@@ -9861,21 +9886,22 @@ async function execSudo(command) {
|
|
|
9861
9886
|
if (process15.platform === "win32")
|
|
9862
9887
|
throw new Error("Administrator privileges required on Windows");
|
|
9863
9888
|
const sudoPassword = getSudoPassword();
|
|
9889
|
+
const escaped = command.replace(/'/g, `'\\''`);
|
|
9864
9890
|
try {
|
|
9865
9891
|
if (sudoPassword) {
|
|
9866
|
-
const { stdout: stdout2 } = await execAsync(`echo '${sudoPassword}' | sudo -S ${
|
|
9892
|
+
const { stdout: stdout2 } = await execAsync(`echo '${sudoPassword}' | sudo -S sh -c '${escaped}' 2>/dev/null`);
|
|
9867
9893
|
sudoPrivilegesAcquired = true;
|
|
9868
9894
|
return stdout2;
|
|
9869
9895
|
}
|
|
9870
9896
|
if (sudoPrivilegesAcquired) {
|
|
9871
9897
|
try {
|
|
9872
|
-
const { stdout: stdout2 } = await execAsync(`sudo -n
|
|
9898
|
+
const { stdout: stdout2 } = await execAsync(`sudo -n sh -c '${escaped}'`);
|
|
9873
9899
|
return stdout2;
|
|
9874
9900
|
} catch (error) {
|
|
9875
9901
|
debugLog("hosts", "Cached sudo privileges expired, requesting again", true);
|
|
9876
9902
|
}
|
|
9877
9903
|
}
|
|
9878
|
-
const { stdout } = await execAsync(`sudo ${
|
|
9904
|
+
const { stdout } = await execAsync(`sudo sh -c '${escaped}'`);
|
|
9879
9905
|
sudoPrivilegesAcquired = true;
|
|
9880
9906
|
return stdout;
|
|
9881
9907
|
} catch (error) {
|
|
@@ -9889,13 +9915,13 @@ async function addHosts(hosts, verbose) {
|
|
|
9889
9915
|
let existingContent;
|
|
9890
9916
|
try {
|
|
9891
9917
|
existingContent = await fs2.promises.readFile(hostsFilePath, "utf-8");
|
|
9892
|
-
} catch
|
|
9893
|
-
debugLog("hosts",
|
|
9894
|
-
log.error(`Failed to read hosts file: ${readErr}`);
|
|
9918
|
+
} catch {
|
|
9919
|
+
debugLog("hosts", "Reading hosts file requires elevated permissions, using sudo", verbose);
|
|
9895
9920
|
try {
|
|
9896
9921
|
existingContent = await execSudo(`cat "${hostsFilePath}"`);
|
|
9897
9922
|
} catch (sudoErr) {
|
|
9898
|
-
log
|
|
9923
|
+
console.log(" Could not read hosts file — skipping hosts setup");
|
|
9924
|
+
debugLog("hosts", `sudo read also failed: ${sudoErr}`, verbose);
|
|
9899
9925
|
throw new Error(`Cannot read hosts file: ${sudoErr}`);
|
|
9900
9926
|
}
|
|
9901
9927
|
}
|
|
@@ -9906,7 +9932,6 @@ async function addHosts(hosts, verbose) {
|
|
|
9906
9932
|
});
|
|
9907
9933
|
if (newEntries.length === 0) {
|
|
9908
9934
|
debugLog("hosts", "All hosts already exist in hosts file", verbose);
|
|
9909
|
-
log.info("All hosts are already in the hosts file");
|
|
9910
9935
|
return;
|
|
9911
9936
|
}
|
|
9912
9937
|
const hostEntries = newEntries.map((host) => `
|
|
@@ -9918,32 +9943,19 @@ async function addHosts(hosts, verbose) {
|
|
|
9918
9943
|
try {
|
|
9919
9944
|
await fs2.promises.writeFile(tmpFile, existingContent + hostEntries, "utf8");
|
|
9920
9945
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
9921
|
-
log
|
|
9946
|
+
console.log(` Hosts updated: ${newEntries.join(", ")}`);
|
|
9922
9947
|
} catch (error) {
|
|
9923
|
-
log
|
|
9924
|
-
log
|
|
9948
|
+
console.log(" Could not update hosts file automatically");
|
|
9949
|
+
console.log(" Add these entries to /etc/hosts:");
|
|
9925
9950
|
newEntries.forEach((host) => {
|
|
9926
|
-
log
|
|
9927
|
-
log
|
|
9951
|
+
console.log(` 127.0.0.1 ${host}`);
|
|
9952
|
+
console.log(` ::1 ${host}`);
|
|
9928
9953
|
});
|
|
9929
|
-
|
|
9930
|
-
log.info(`
|
|
9931
|
-
On Windows:`);
|
|
9932
|
-
log.info("1. Run notepad as administrator");
|
|
9933
|
-
log.info("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
9934
|
-
} else {
|
|
9935
|
-
log.info(`
|
|
9936
|
-
On Unix systems:`);
|
|
9937
|
-
log.info(` sudo nano ${hostsFilePath}`);
|
|
9938
|
-
}
|
|
9939
|
-
log.info(`
|
|
9940
|
-
Or run: buddy setup:ssl`);
|
|
9954
|
+
console.log(` Or run: sudo nano ${hostsFilePath}`);
|
|
9941
9955
|
} finally {
|
|
9942
9956
|
try {
|
|
9943
9957
|
await fs2.promises.unlink(tmpFile);
|
|
9944
|
-
} catch
|
|
9945
|
-
debugLog("hosts", `Failed to remove temporary file: ${unlinkErr}`, verbose);
|
|
9946
|
-
}
|
|
9958
|
+
} catch {}
|
|
9947
9959
|
}
|
|
9948
9960
|
} catch (err) {
|
|
9949
9961
|
const error = err;
|
|
@@ -9956,12 +9968,12 @@ async function removeHosts(hosts, verbose) {
|
|
|
9956
9968
|
let content;
|
|
9957
9969
|
try {
|
|
9958
9970
|
content = await fs2.promises.readFile(hostsFilePath, "utf-8");
|
|
9959
|
-
} catch
|
|
9960
|
-
debugLog("hosts",
|
|
9971
|
+
} catch {
|
|
9972
|
+
debugLog("hosts", "Reading hosts file requires elevated permissions, using sudo", verbose);
|
|
9961
9973
|
try {
|
|
9962
9974
|
content = await execSudo(`cat "${hostsFilePath}"`);
|
|
9963
9975
|
} catch (sudoErr) {
|
|
9964
|
-
|
|
9976
|
+
debugLog("hosts", `sudo read also failed: ${sudoErr}`, verbose);
|
|
9965
9977
|
throw new Error(`Cannot read hosts file: ${sudoErr}`);
|
|
9966
9978
|
}
|
|
9967
9979
|
}
|
|
@@ -9993,14 +10005,9 @@ async function removeHosts(hosts, verbose) {
|
|
|
9993
10005
|
try {
|
|
9994
10006
|
await fs2.promises.writeFile(tmpFile, newContent, "utf8");
|
|
9995
10007
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
9996
|
-
|
|
10008
|
+
debugLog("hosts", "Hosts removed successfully", verbose);
|
|
9997
10009
|
} catch (error) {
|
|
9998
|
-
|
|
9999
|
-
log.info("You may want to remove these entries from your hosts file:");
|
|
10000
|
-
hosts.forEach((host) => {
|
|
10001
|
-
log.info(` 127.0.0.1 ${host}`);
|
|
10002
|
-
log.info(` ::1 ${host}`);
|
|
10003
|
-
});
|
|
10010
|
+
debugLog("hosts", "Could not clean up hosts file automatically", verbose);
|
|
10004
10011
|
} finally {
|
|
10005
10012
|
try {
|
|
10006
10013
|
await fs2.promises.unlink(tmpFile);
|
|
@@ -19079,10 +19086,12 @@ async function generateCertificate(options) {
|
|
|
19079
19086
|
const domains = isMultiProxyOptions(options) ? options.proxies.map((proxy) => proxy.to) : [options.to];
|
|
19080
19087
|
debugLog("ssl", `Generating certificate for domains: ${domains.join(", ")}`, options.verbose);
|
|
19081
19088
|
const rootCAConfig = httpsConfig(options, options.verbose);
|
|
19082
|
-
|
|
19089
|
+
if (options.verbose)
|
|
19090
|
+
log.info("Generating Root CA certificate...");
|
|
19083
19091
|
const caCert = await QO(rootCAConfig);
|
|
19084
19092
|
const hostConfig = httpsConfig(options, options.verbose);
|
|
19085
|
-
|
|
19093
|
+
if (options.verbose)
|
|
19094
|
+
log.info(`Generating host certificate for: ${domains.join(", ")}`);
|
|
19086
19095
|
const hostCert = await YO({
|
|
19087
19096
|
...hostConfig,
|
|
19088
19097
|
rootCA: {
|
|
@@ -19106,22 +19115,26 @@ async function generateCertificate(options) {
|
|
|
19106
19115
|
const alreadyTrusted = await isCertTrusted(hostConfig.certPath, { verbose: options.verbose, regenerateUntrustedCerts: true });
|
|
19107
19116
|
if (alreadyTrusted) {
|
|
19108
19117
|
debugLog("ssl", "Certificate is already trusted, skipping trust store update", options.verbose);
|
|
19109
|
-
|
|
19118
|
+
if (options.verbose)
|
|
19119
|
+
log.success("Certificate is already trusted in system trust store");
|
|
19110
19120
|
cachedSSLConfig = {
|
|
19111
19121
|
key: hostCert.privateKey,
|
|
19112
19122
|
cert: hostCert.certificate,
|
|
19113
19123
|
ca: caCert.certificate
|
|
19114
19124
|
};
|
|
19115
|
-
|
|
19125
|
+
if (options.verbose)
|
|
19126
|
+
log.success(`Certificate generated successfully for ${domains.length} domain${domains.length > 1 ? "s" : ""}`);
|
|
19116
19127
|
return;
|
|
19117
19128
|
}
|
|
19118
|
-
|
|
19129
|
+
if (options.verbose)
|
|
19130
|
+
log.info("Adding certificate to system trust store (may require sudo permission)...");
|
|
19119
19131
|
let isTrusted = false;
|
|
19120
19132
|
if (process16.platform === "darwin") {
|
|
19121
19133
|
try {
|
|
19122
19134
|
const combinedCmd = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${hostConfig.caCertPath}" && security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${hostConfig.certPath}"`;
|
|
19123
19135
|
execSudoSync(combinedCmd);
|
|
19124
|
-
|
|
19136
|
+
if (options.verbose)
|
|
19137
|
+
log.success("Successfully added CA and host certificates to system trust store");
|
|
19125
19138
|
try {
|
|
19126
19139
|
execSync2(`security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain-db "${hostConfig.certPath}"`, { stdio: "pipe" });
|
|
19127
19140
|
} catch {}
|
|
@@ -19137,7 +19150,8 @@ echo "If you still see certificate warnings, type 'thisisunsafe' on the warning
|
|
|
19137
19150
|
`;
|
|
19138
19151
|
await fs3.writeFile(scriptPath, scriptContent, { mode: 493 });
|
|
19139
19152
|
} catch (err) {
|
|
19140
|
-
|
|
19153
|
+
if (options.verbose)
|
|
19154
|
+
log.warn(`Could not add certificate to trust store automatically: ${err}`);
|
|
19141
19155
|
const sslScriptDir = hostConfig.basePath || join7(homedir6(), ".stacks", "ssl");
|
|
19142
19156
|
const scriptPath = join7(sslScriptDir, "trust-rpx-cert.sh");
|
|
19143
19157
|
const scriptContent = `#!/bin/bash
|
|
@@ -19148,8 +19162,10 @@ echo "Certificates trusted! Please restart your browser."
|
|
|
19148
19162
|
echo "If you still see certificate warnings, type 'thisisunsafe' on the warning page in Chrome/Arc browsers."
|
|
19149
19163
|
`;
|
|
19150
19164
|
await fs3.writeFile(scriptPath, scriptContent, { mode: 493 });
|
|
19151
|
-
|
|
19152
|
-
|
|
19165
|
+
if (options.verbose) {
|
|
19166
|
+
log.info(`Created a trust helper script at: ${scriptPath}`);
|
|
19167
|
+
log.info(`If you're still having certificate issues, run: sh ${scriptPath}`);
|
|
19168
|
+
}
|
|
19153
19169
|
}
|
|
19154
19170
|
} else if (process16.platform === "linux") {
|
|
19155
19171
|
try {
|
|
@@ -19167,10 +19183,12 @@ echo "RPX certificates installed. Please restart your browser."
|
|
|
19167
19183
|
await new Promise((resolve9) => {
|
|
19168
19184
|
exec2(`sudo bash "${tmpScript}"`, (error) => {
|
|
19169
19185
|
if (error) {
|
|
19170
|
-
|
|
19186
|
+
if (options.verbose)
|
|
19187
|
+
log.warn(`Could not trust certificates: ${error}`);
|
|
19171
19188
|
resolve9(false);
|
|
19172
19189
|
} else {
|
|
19173
|
-
|
|
19190
|
+
if (options.verbose)
|
|
19191
|
+
log.success("Successfully added certificates to system trust store");
|
|
19174
19192
|
resolve9(true);
|
|
19175
19193
|
}
|
|
19176
19194
|
});
|
|
@@ -19178,7 +19196,8 @@ echo "RPX certificates installed. Please restart your browser."
|
|
|
19178
19196
|
await fs3.unlink(tmpScript).catch(() => {});
|
|
19179
19197
|
isTrusted = true;
|
|
19180
19198
|
} catch (error) {
|
|
19181
|
-
|
|
19199
|
+
if (options.verbose)
|
|
19200
|
+
log.warn(`Failed to trust certificates: ${error}`);
|
|
19182
19201
|
}
|
|
19183
19202
|
} else if (process16.platform === "win32") {
|
|
19184
19203
|
try {
|
|
@@ -19193,17 +19212,20 @@ Write-Host "Certificate trusted successfully!"
|
|
|
19193
19212
|
const psPath = join7(os2.tmpdir(), "rpx-trust.ps1");
|
|
19194
19213
|
await fs3.writeFile(psPath, winScript);
|
|
19195
19214
|
execSync2(`powershell -ExecutionPolicy Bypass -File "${psPath}"`);
|
|
19196
|
-
|
|
19215
|
+
if (options.verbose)
|
|
19216
|
+
log.success("Successfully added certificate to Windows trust store");
|
|
19197
19217
|
isTrusted = true;
|
|
19198
19218
|
} catch (error) {
|
|
19199
|
-
|
|
19219
|
+
if (options.verbose)
|
|
19220
|
+
log.warn(`Could not trust certificate: ${error}`);
|
|
19200
19221
|
}
|
|
19201
19222
|
} else {
|
|
19202
19223
|
try {
|
|
19203
19224
|
await UO(hostCert, caCert.certificate, hostConfig);
|
|
19204
19225
|
isTrusted = true;
|
|
19205
19226
|
} catch (err) {
|
|
19206
|
-
|
|
19227
|
+
if (options.verbose)
|
|
19228
|
+
log.warn(`Could not add certificate to trust store: ${err}`);
|
|
19207
19229
|
}
|
|
19208
19230
|
}
|
|
19209
19231
|
cachedSSLConfig = {
|
|
@@ -19211,8 +19233,9 @@ Write-Host "Certificate trusted successfully!"
|
|
|
19211
19233
|
cert: hostCert.certificate,
|
|
19212
19234
|
ca: caCert.certificate
|
|
19213
19235
|
};
|
|
19214
|
-
|
|
19215
|
-
|
|
19236
|
+
if (options.verbose)
|
|
19237
|
+
log.success(`Certificate generated successfully for ${domains.length} domain${domains.length > 1 ? "s" : ""}`);
|
|
19238
|
+
if (!isTrusted && options.verbose) {
|
|
19216
19239
|
log.warn('If you see certificate warnings in Chrome/Arc, type "thisisunsafe" on the warning page');
|
|
19217
19240
|
log.warn("This will bypass the warning and you should only need to do it once");
|
|
19218
19241
|
}
|
|
@@ -19230,10 +19253,13 @@ async function checkExistingCertificates(options) {
|
|
|
19230
19253
|
sslConfig.caCertPath ? fs3.access(sslConfig.caCertPath).then(() => true).catch(() => false) : Promise.resolve(false)
|
|
19231
19254
|
]);
|
|
19232
19255
|
if (!keyExists || !certExists) {
|
|
19233
|
-
debugLog("ssl", `Certificate files don't exist: key=${keyExists}, cert=${certExists}`, options.verbose);
|
|
19256
|
+
debugLog("ssl", `Certificate files don't exist: key=${keyExists}, cert=${certExists}, paths: ${sslConfig.keyPath}, ${sslConfig.certPath}`, options.verbose);
|
|
19234
19257
|
return null;
|
|
19235
19258
|
}
|
|
19236
|
-
const
|
|
19259
|
+
const hasFlag = "regenerateUntrustedCerts" in options;
|
|
19260
|
+
const flagValue = options.regenerateUntrustedCerts;
|
|
19261
|
+
const shouldCheckTrust = hasFlag ? flagValue !== false : true;
|
|
19262
|
+
debugLog("ssl", `Trust check: hasFlag=${hasFlag}, flagValue=${flagValue}, shouldCheckTrust=${shouldCheckTrust}`, options.verbose);
|
|
19237
19263
|
const certIsTrusted = shouldCheckTrust ? await isCertTrusted(sslConfig.certPath, options) : true;
|
|
19238
19264
|
if (!certIsTrusted) {
|
|
19239
19265
|
debugLog("ssl", "Certificate exists but is not trusted, will regenerate", options.verbose);
|
|
@@ -19244,10 +19270,25 @@ async function checkExistingCertificates(options) {
|
|
|
19244
19270
|
fs3.readFile(sslConfig.certPath, "utf8"),
|
|
19245
19271
|
caExists && sslConfig.caCertPath ? fs3.readFile(sslConfig.caCertPath, "utf8") : Promise.resolve(undefined)
|
|
19246
19272
|
]);
|
|
19247
|
-
if (ca && !
|
|
19248
|
-
debugLog("ssl", "Invalid root CA certificate, will regenerate", options.verbose);
|
|
19273
|
+
if (ca && !ca.includes("-----BEGIN CERTIFICATE-----")) {
|
|
19274
|
+
debugLog("ssl", "Invalid root CA certificate content, will regenerate", options.verbose);
|
|
19249
19275
|
return null;
|
|
19250
19276
|
}
|
|
19277
|
+
if (isMultiProxyOptions(options)) {
|
|
19278
|
+
try {
|
|
19279
|
+
const { X509Certificate } = await import("node:crypto");
|
|
19280
|
+
const x509 = new X509Certificate(cert);
|
|
19281
|
+
const san = x509.subjectAltName || "";
|
|
19282
|
+
const requiredDomains = options.proxies.map((p3) => p3.to);
|
|
19283
|
+
const missingDomains = requiredDomains.filter((d3) => !san.includes(`DNS:${d3}`));
|
|
19284
|
+
if (missingDomains.length > 0) {
|
|
19285
|
+
debugLog("ssl", `Certificate missing SANs for: ${missingDomains.join(", ")}, will regenerate`, options.verbose);
|
|
19286
|
+
return null;
|
|
19287
|
+
}
|
|
19288
|
+
} catch (err) {
|
|
19289
|
+
debugLog("ssl", `Could not verify cert SANs: ${err}`, options.verbose);
|
|
19290
|
+
}
|
|
19291
|
+
}
|
|
19251
19292
|
debugLog("ssl", "Successfully loaded existing certificates", options.verbose);
|
|
19252
19293
|
cachedSSLConfig = { key, cert, ca };
|
|
19253
19294
|
return cachedSSLConfig;
|
|
@@ -20079,7 +20120,8 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
20079
20120
|
vitePluginUsage,
|
|
20080
20121
|
listenPort,
|
|
20081
20122
|
ssl: true,
|
|
20082
|
-
cleanUrls
|
|
20123
|
+
cleanUrls,
|
|
20124
|
+
verbose
|
|
20083
20125
|
});
|
|
20084
20126
|
resolve9();
|
|
20085
20127
|
} catch (err) {
|
|
@@ -20099,7 +20141,8 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
20099
20141
|
vitePluginUsage,
|
|
20100
20142
|
listenPort,
|
|
20101
20143
|
ssl: !!ssl,
|
|
20102
|
-
cleanUrls
|
|
20144
|
+
cleanUrls,
|
|
20145
|
+
verbose
|
|
20103
20146
|
});
|
|
20104
20147
|
resolve9();
|
|
20105
20148
|
});
|
|
@@ -20132,16 +20175,14 @@ async function setupProxy(options) {
|
|
|
20132
20175
|
}
|
|
20133
20176
|
}
|
|
20134
20177
|
} else {
|
|
20135
|
-
if (to && to.includes("localhost") && !to.match(/^(localhost|127\.0\.0\.1)$/)) {
|
|
20178
|
+
if (process18.platform !== "darwin" && to && to.includes("localhost") && !to.match(/^(localhost|127\.0\.0\.1)$/)) {
|
|
20136
20179
|
const hostsExist = await checkHosts([to], verbose);
|
|
20137
20180
|
if (!hostsExist[0]) {
|
|
20138
|
-
|
|
20181
|
+
debugLog("hosts", `${to} not found in hosts file, adding...`, verbose);
|
|
20139
20182
|
try {
|
|
20140
20183
|
await addHosts([to], verbose);
|
|
20141
|
-
log.success(`Added ${to} to your hosts file.`);
|
|
20142
20184
|
} catch (error) {
|
|
20143
|
-
|
|
20144
|
-
log.info(`You may need to manually add '127.0.0.1 ${to}' to your /etc/hosts file.`);
|
|
20185
|
+
debugLog("hosts", `Failed to add ${to} to hosts file: ${error}`, verbose);
|
|
20145
20186
|
}
|
|
20146
20187
|
}
|
|
20147
20188
|
}
|
|
@@ -20154,30 +20195,32 @@ async function setupProxy(options) {
|
|
|
20154
20195
|
portManager2.usedPorts.add(httpPort);
|
|
20155
20196
|
} else {
|
|
20156
20197
|
debugLog("setup", "Port 80 is in use, skipping HTTP redirect", verbose);
|
|
20157
|
-
|
|
20198
|
+
if (verbose)
|
|
20199
|
+
log.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available");
|
|
20158
20200
|
}
|
|
20159
20201
|
}
|
|
20160
20202
|
const targetPort = ssl ? httpsPort : httpPort;
|
|
20161
20203
|
const isTargetPortBusy = await isPortInUse(targetPort, hostname, verbose);
|
|
20162
20204
|
let finalPort;
|
|
20163
20205
|
if (isTargetPortBusy) {
|
|
20164
|
-
|
|
20206
|
+
debugLog("setup", `Port ${targetPort} is already in use`, verbose);
|
|
20207
|
+
if (verbose)
|
|
20208
|
+
log.warn(`Port ${targetPort} is already in use. This may be another instance of rpx or another service.`);
|
|
20165
20209
|
if (targetPort === 443) {
|
|
20166
|
-
log.info("HTTPS requires port 443 for standard operation. Using an alternative port instead.");
|
|
20167
20210
|
finalPort = await portManager2.getNextAvailablePort(3443, true);
|
|
20168
|
-
|
|
20211
|
+
debugLog("setup", `Using port ${finalPort} instead of ${targetPort}`, verbose);
|
|
20212
|
+
if (verbose)
|
|
20213
|
+
log.info(`Using port ${finalPort} instead. Access your site at https://${to}:${finalPort}`);
|
|
20169
20214
|
} else {
|
|
20170
20215
|
finalPort = await portManager2.getNextAvailablePort(targetPort + 1000, true);
|
|
20171
|
-
|
|
20216
|
+
debugLog("setup", `Using port ${finalPort} instead of ${targetPort}`, verbose);
|
|
20217
|
+
if (verbose)
|
|
20218
|
+
log.info(`Using port ${finalPort} instead. Access your site at http://${to}:${finalPort}`);
|
|
20172
20219
|
}
|
|
20173
20220
|
} else {
|
|
20174
20221
|
finalPort = targetPort;
|
|
20175
20222
|
portManager2.usedPorts.add(finalPort);
|
|
20176
|
-
|
|
20177
|
-
log.info(`Using standard HTTPS port 443. Access your site at https://${to}`);
|
|
20178
|
-
} else {
|
|
20179
|
-
log.info(`Using standard HTTP port 80. Access your site at http://${to}`);
|
|
20180
|
-
}
|
|
20223
|
+
debugLog("setup", `Using standard ${targetPort === 443 ? "HTTPS" : "HTTP"} port ${targetPort} for ${to}`, verbose);
|
|
20181
20224
|
}
|
|
20182
20225
|
await createProxyServer(from, to, fromPort, finalPort, hostname, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin);
|
|
20183
20226
|
} catch (err) {
|
|
@@ -20326,8 +20369,8 @@ async function startProxies(options) {
|
|
|
20326
20369
|
const problematicTlds = ["dev", "app", "page", "new", "day", "foo"];
|
|
20327
20370
|
const reservedTlds = ["test", "localhost", "local", "example", "invalid"];
|
|
20328
20371
|
const uniqueTlds = [...new Set(customDomains.map((d3) => d3.split(".").pop()?.toLowerCase()))];
|
|
20329
|
-
const problematicFound = uniqueTlds.filter((t2) =>
|
|
20330
|
-
if (problematicFound.length > 0) {
|
|
20372
|
+
const problematicFound = uniqueTlds.filter((t2) => !!t2 && problematicTlds.includes(t2));
|
|
20373
|
+
if (problematicFound.length > 0 && verbose) {
|
|
20331
20374
|
log.warn(`The following TLDs may not work reliably for local development: ${problematicFound.map((t2) => `.${t2}`).join(", ")}`);
|
|
20332
20375
|
log.info(` These TLDs have HSTS preloading which can bypass local DNS`);
|
|
20333
20376
|
log.info(` Consider using reserved TLDs: .test, .localhost, or .local`);
|
|
@@ -20337,14 +20380,16 @@ async function startProxies(options) {
|
|
|
20337
20380
|
const dnsStarted = await startDnsServer2(customDomains, verbose);
|
|
20338
20381
|
if (dnsStarted) {
|
|
20339
20382
|
await setupResolver2(verbose, customDomains);
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
|
|
20383
|
+
if (verbose) {
|
|
20384
|
+
const hasReservedOnly = uniqueTlds.every((t2) => !!t2 && reservedTlds.includes(t2));
|
|
20385
|
+
if (hasReservedOnly) {
|
|
20386
|
+
log.success(`DNS server started for ${uniqueTlds.map((t2) => `.${t2}`).join(", ")} domains`);
|
|
20387
|
+
} else {
|
|
20388
|
+
log.success(`DNS server started for ${uniqueTlds.map((t2) => `.${t2}`).join(", ")} domains (hosts file entries also added)`);
|
|
20389
|
+
}
|
|
20345
20390
|
}
|
|
20346
20391
|
} else {
|
|
20347
|
-
|
|
20392
|
+
debugLog("dns", "Could not start DNS server - custom domains may not resolve", verbose);
|
|
20348
20393
|
}
|
|
20349
20394
|
}
|
|
20350
20395
|
const cleanupHandler = async () => {
|
|
@@ -20375,47 +20420,143 @@ async function startProxies(options) {
|
|
|
20375
20420
|
console.error("Uncaught exception:", err);
|
|
20376
20421
|
cleanupHandler();
|
|
20377
20422
|
});
|
|
20378
|
-
|
|
20379
|
-
|
|
20423
|
+
if (sslConfig && proxyOptions.length > 1) {
|
|
20424
|
+
debugLog("proxies", `Creating shared HTTPS server for ${proxyOptions.length} domains`, verbose);
|
|
20425
|
+
const routingTable = new Map;
|
|
20426
|
+
for (const option of proxyOptions) {
|
|
20380
20427
|
const domain = option.to || "rpx.localhost";
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
|
|
20428
|
+
const fromUrl = new URL(option.from?.startsWith("http") ? option.from : `http://${option.from}`);
|
|
20429
|
+
const fromPort = Number.parseInt(fromUrl.port) || 80;
|
|
20430
|
+
routingTable.set(domain, {
|
|
20431
|
+
fromPort,
|
|
20432
|
+
sourceHost: fromUrl.host,
|
|
20385
20433
|
cleanUrls: option.cleanUrls || false,
|
|
20386
|
-
https: option.https || false,
|
|
20387
|
-
cleanup: option.cleanup || false,
|
|
20388
|
-
vitePluginUsage: option.vitePluginUsage || false,
|
|
20389
|
-
verbose: option.verbose || false,
|
|
20390
|
-
_cachedSSLConfig: sslConfig,
|
|
20391
20434
|
changeOrigin: option.changeOrigin || false
|
|
20392
20435
|
});
|
|
20436
|
+
debugLog("proxies", `Route: ${domain} → ${fromUrl.host}`, verbose);
|
|
20437
|
+
if (!domain.includes("localhost") && !domain.includes("127.0.0.1")) {
|
|
20438
|
+
try {
|
|
20439
|
+
const hostsExist = await checkHosts([domain], verbose);
|
|
20440
|
+
if (!hostsExist[0]) {
|
|
20441
|
+
await addHosts([domain], verbose);
|
|
20442
|
+
}
|
|
20443
|
+
} catch {
|
|
20444
|
+
debugLog("hosts", `Could not add hosts entry for ${domain}`, verbose);
|
|
20445
|
+
}
|
|
20446
|
+
}
|
|
20447
|
+
}
|
|
20448
|
+
const isHttpPortBusy = await isPortInUse(80, "0.0.0.0", verbose);
|
|
20449
|
+
if (!isHttpPortBusy) {
|
|
20450
|
+
startHttpRedirectServer(verbose);
|
|
20451
|
+
}
|
|
20452
|
+
const listenPort = 443;
|
|
20453
|
+
const isPortBusy = await isPortInUse(listenPort, "0.0.0.0", verbose);
|
|
20454
|
+
if (isPortBusy) {
|
|
20455
|
+
debugLog("proxies", `Port ${listenPort} is already in use, cannot start shared proxy`, verbose);
|
|
20456
|
+
if (verbose)
|
|
20457
|
+
log.warn(`Port ${listenPort} is in use. Shared HTTPS proxy cannot start.`);
|
|
20458
|
+
return;
|
|
20459
|
+
}
|
|
20460
|
+
try {
|
|
20461
|
+
const bunServer = Bun.serve({
|
|
20462
|
+
port: listenPort,
|
|
20463
|
+
hostname: "0.0.0.0",
|
|
20464
|
+
tls: {
|
|
20465
|
+
key: sslConfig.key,
|
|
20466
|
+
cert: sslConfig.cert,
|
|
20467
|
+
ca: sslConfig.ca,
|
|
20468
|
+
requestCert: false,
|
|
20469
|
+
rejectUnauthorized: false
|
|
20470
|
+
},
|
|
20471
|
+
async fetch(req) {
|
|
20472
|
+
const url = new URL(req.url);
|
|
20473
|
+
const hostHeader = req.headers.get("host") || "";
|
|
20474
|
+
const hostname = hostHeader.split(":")[0];
|
|
20475
|
+
const route = routingTable.get(hostname);
|
|
20476
|
+
if (!route) {
|
|
20477
|
+
debugLog("request", `No route found for host: ${hostname}`, verbose);
|
|
20478
|
+
return new Response(`No proxy configured for ${hostname}`, { status: 404 });
|
|
20479
|
+
}
|
|
20480
|
+
const targetUrl = `http://${route.sourceHost}${url.pathname}${url.search}`;
|
|
20481
|
+
try {
|
|
20482
|
+
const headers = new Headers(req.headers);
|
|
20483
|
+
headers.set("host", route.sourceHost);
|
|
20484
|
+
if (route.changeOrigin) {
|
|
20485
|
+
headers.set("origin", `http://${route.sourceHost}`);
|
|
20486
|
+
}
|
|
20487
|
+
headers.set("x-forwarded-for", "127.0.0.1");
|
|
20488
|
+
headers.set("x-forwarded-proto", "https");
|
|
20489
|
+
headers.set("x-forwarded-host", hostname);
|
|
20490
|
+
const response = await fetch(targetUrl, {
|
|
20491
|
+
method: req.method,
|
|
20492
|
+
headers,
|
|
20493
|
+
body: req.body,
|
|
20494
|
+
redirect: "manual"
|
|
20495
|
+
});
|
|
20496
|
+
const responseHeaders = new Headers(response.headers);
|
|
20497
|
+
if (route.cleanUrls && url.pathname.endsWith(".html")) {
|
|
20498
|
+
const cleanPath = url.pathname.replace(/\.html$/, "");
|
|
20499
|
+
return new Response(null, {
|
|
20500
|
+
status: 301,
|
|
20501
|
+
headers: { Location: cleanPath }
|
|
20502
|
+
});
|
|
20503
|
+
}
|
|
20504
|
+
return new Response(response.body, {
|
|
20505
|
+
status: response.status,
|
|
20506
|
+
statusText: response.statusText,
|
|
20507
|
+
headers: responseHeaders
|
|
20508
|
+
});
|
|
20509
|
+
} catch (err) {
|
|
20510
|
+
debugLog("request", `Proxy error for ${hostname}: ${err}`, verbose);
|
|
20511
|
+
return new Response(`Proxy Error: ${err}`, { status: 502 });
|
|
20512
|
+
}
|
|
20513
|
+
},
|
|
20514
|
+
error(err) {
|
|
20515
|
+
debugLog("server", `Shared proxy server error: ${err}`, verbose);
|
|
20516
|
+
return new Response(`Server Error: ${err.message}`, { status: 500 });
|
|
20517
|
+
}
|
|
20518
|
+
});
|
|
20519
|
+
activeServers.add(bunServer);
|
|
20520
|
+
debugLog("proxies", `Shared HTTPS proxy listening on port ${listenPort} for ${routingTable.size} domains`, verbose);
|
|
20393
20521
|
} catch (err) {
|
|
20394
|
-
debugLog("proxies", `Failed to start proxy
|
|
20395
|
-
console.error(
|
|
20522
|
+
debugLog("proxies", `Failed to start shared proxy: ${err}`, verbose);
|
|
20523
|
+
console.error("Failed to start shared HTTPS proxy:", err);
|
|
20396
20524
|
cleanupHandler();
|
|
20397
20525
|
}
|
|
20526
|
+
} else {
|
|
20527
|
+
for (const option of proxyOptions) {
|
|
20528
|
+
try {
|
|
20529
|
+
const domain = option.to || "rpx.localhost";
|
|
20530
|
+
debugLog("proxy", `Starting proxy for ${domain} with SSL config: ${!!sslConfig}`, option.verbose);
|
|
20531
|
+
await startServer({
|
|
20532
|
+
from: option.from || "localhost:5173",
|
|
20533
|
+
to: domain,
|
|
20534
|
+
cleanUrls: option.cleanUrls || false,
|
|
20535
|
+
https: option.https || false,
|
|
20536
|
+
cleanup: option.cleanup || false,
|
|
20537
|
+
vitePluginUsage: option.vitePluginUsage || false,
|
|
20538
|
+
verbose: option.verbose || false,
|
|
20539
|
+
_cachedSSLConfig: sslConfig,
|
|
20540
|
+
changeOrigin: option.changeOrigin || false
|
|
20541
|
+
});
|
|
20542
|
+
} catch (err) {
|
|
20543
|
+
debugLog("proxies", `Failed to start proxy for ${option.to}: ${err}`, option.verbose);
|
|
20544
|
+
console.error(`Failed to start proxy for ${option.to}:`, err);
|
|
20545
|
+
cleanupHandler();
|
|
20546
|
+
}
|
|
20547
|
+
}
|
|
20398
20548
|
}
|
|
20399
20549
|
}
|
|
20400
20550
|
function logToConsole(options) {
|
|
20401
|
-
if (!options?.
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
console.log(` - TLS 1.2/1.3`);
|
|
20411
|
-
console.log(` - Modern cipher suite`);
|
|
20412
|
-
console.log(` - HTTP/2 enabled`);
|
|
20413
|
-
console.log(` - HSTS enabled`);
|
|
20414
|
-
}
|
|
20415
|
-
if (options?.cleanUrls) {
|
|
20416
|
-
console.log(` ${import_picocolors.default.green("➜")} Clean URLs enabled`);
|
|
20417
|
-
}
|
|
20418
|
-
}
|
|
20551
|
+
if (options?.vitePluginUsage || !options?.verbose)
|
|
20552
|
+
return;
|
|
20553
|
+
console.log("");
|
|
20554
|
+
console.log(` ${import_picocolors.default.green(import_picocolors.default.bold("rpx"))} ${import_picocolors.default.green(`v${version}`)}`);
|
|
20555
|
+
console.log(` ${import_picocolors.default.green("➜")} ${import_picocolors.default.dim(options?.from ?? "")} ${import_picocolors.default.dim("➜")} ${import_picocolors.default.cyan(options?.ssl ? `https://${options?.to}` : `http://${options?.to}`)}`);
|
|
20556
|
+
if (options?.listenPort !== (options?.ssl ? 443 : 80))
|
|
20557
|
+
console.log(` ${import_picocolors.default.green("➜")} Listening on port ${options?.listenPort}`);
|
|
20558
|
+
if (options?.cleanUrls)
|
|
20559
|
+
console.log(` ${import_picocolors.default.green("➜")} Clean URLs enabled`);
|
|
20419
20560
|
}
|
|
20420
20561
|
|
|
20421
20562
|
// ../rpx/src/index.ts
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import type { VitePluginRpxOptions } from './types';
|
|
3
3
|
export declare function SimplifiedVitePlugin(options?: SimplifiedPluginOptions): Plugin;
|
|
4
|
-
export declare interface SimplifiedPluginOptions extends VitePluginRpxOptions {
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
export declare interface SimplifiedPluginOptions extends VitePluginRpxOptions {}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,19 +6,11 @@ export declare interface VitePluginRpxOptions {
|
|
|
6
6
|
https?: boolean | TlsConfig
|
|
7
7
|
cleanUrls?: boolean
|
|
8
8
|
cleanup?: boolean | {
|
|
9
|
-
/**
|
|
10
|
-
* Whether to clean up hosts file entries
|
|
11
|
-
* @default true
|
|
12
|
-
*/
|
|
13
9
|
hosts?: boolean
|
|
14
|
-
/**
|
|
15
|
-
* Whether to clean up SSL certificates
|
|
16
|
-
* @default false
|
|
17
|
-
*/
|
|
18
10
|
certs?: boolean
|
|
19
11
|
}
|
|
20
12
|
changeOrigin?: boolean
|
|
21
13
|
verbose?: boolean
|
|
22
14
|
regenerateUntrustedCerts?: boolean
|
|
23
15
|
enableHmr?: boolean
|
|
24
|
-
}
|
|
16
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { SingleReverseProxyConfig } from '@stacksjs/rpx';
|
|
2
2
|
import type { VitePluginRpxOptions } from './types';
|
|
3
3
|
export declare function getDefaultSSLConfig(): { caCertPath: string, certPath: string, keyPath: string };
|
|
4
|
-
export declare function buildConfig(options: VitePluginRpxOptions, serverUrl?: string): SingleReverseProxyConfig;
|
|
4
|
+
export declare function buildConfig(options: VitePluginRpxOptions, serverUrl?: string): SingleReverseProxyConfig;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-rpx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.1",
|
|
5
5
|
"description": "A modern and smart reverse proxy. Vite plugin.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.org>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typecheck": "bunx tsc --noEmit"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@stacksjs/rpx": "0.11.
|
|
50
|
+
"@stacksjs/rpx": "0.11.1",
|
|
51
51
|
"@stacksjs/tlsx": "^0.10.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|