@stacksjs/rpx 0.5.0 → 0.6.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.
package/dist/cli.js CHANGED
@@ -20461,7 +20461,7 @@ var quotes = collect([
20461
20461
  ]);
20462
20462
  var export_prompts = import_prompts.default;
20463
20463
  // package.json
20464
- var version = "0.5.0";
20464
+ var version = "0.6.0";
20465
20465
 
20466
20466
  // src/config.ts
20467
20467
  import { homedir } from "os";
@@ -20520,6 +20520,7 @@ async function loadConfig({ name, cwd, defaultConfig }) {
20520
20520
  var defaultConfig = {
20521
20521
  from: "localhost:5173",
20522
20522
  to: "stacks.localhost",
20523
+ cleanUrls: false,
20523
20524
  https: {
20524
20525
  basePath: "",
20525
20526
  caCertPath: join2(homedir(), ".stacks", "ssl", `stacks.localhost.ca.crt`),
@@ -57099,9 +57100,15 @@ function extractHostname(options3) {
57099
57100
  function isValidRootCA(value) {
57100
57101
  return typeof value === "object" && value !== null && "certificate" in value && "privateKey" in value && typeof value.certificate === "string" && typeof value.privateKey === "string";
57101
57102
  }
57102
-
57103
- // src/https.ts
57104
- var cachedSSLConfig = null;
57103
+ function getPrimaryDomain(options3) {
57104
+ if (!options3)
57105
+ return "stacks.localhost";
57106
+ if (isMultiProxyOptions(options3) && options3.proxies.length > 0)
57107
+ return options3.proxies[0].to || "stacks.localhost";
57108
+ if (isSingleProxyOptions(options3))
57109
+ return options3.to || "stacks.localhost";
57110
+ return "stacks.localhost";
57111
+ }
57105
57112
  function isMultiProxyConfig(options3) {
57106
57113
  return "proxies" in options3 && Array.isArray(options3.proxies);
57107
57114
  }
@@ -57111,6 +57118,9 @@ function isMultiProxyOptions(options3) {
57111
57118
  function isSingleProxyOptions(options3) {
57112
57119
  return "to" in options3 && typeof options3.to === "string";
57113
57120
  }
57121
+
57122
+ // src/https.ts
57123
+ var cachedSSLConfig = null;
57114
57124
  function resolveSSLPaths(options3, defaultConfig3) {
57115
57125
  const domain = isMultiProxyConfig(options3) ? options3.proxies[0].to || "stacks.localhost" : options3.to || "stacks.localhost";
57116
57126
  if (typeof options3.https === "object" && typeof defaultConfig3.https === "object") {
@@ -57154,15 +57164,6 @@ function generateWildcardPatterns(domain) {
57154
57164
  patterns.add(`*.${parts.slice(1).join(".")}`);
57155
57165
  return Array.from(patterns);
57156
57166
  }
57157
- function getPrimaryDomain(options3) {
57158
- if (!options3)
57159
- return "stacks.localhost";
57160
- if (isMultiProxyOptions(options3) && options3.proxies.length > 0)
57161
- return options3.proxies[0].to || "stacks.localhost";
57162
- if (isSingleProxyOptions(options3))
57163
- return options3.to || "stacks.localhost";
57164
- return "stacks.localhost";
57165
- }
57166
57167
  function generateSSLPaths(options3) {
57167
57168
  const domain = getPrimaryDomain(options3);
57168
57169
  let basePath = "";
@@ -57244,7 +57245,6 @@ async function generateCertificate2(options3) {
57244
57245
  log.info("Generating Root CA certificate...");
57245
57246
  const caCert = await createRootCA(rootCAConfig);
57246
57247
  const hostConfig = httpsConfig(options3, options3.verbose);
57247
- console.log("hostConfig", hostConfig);
57248
57248
  log.info(`Generating host certificate for: ${domains.join(", ")}`);
57249
57249
  const hostCert = await generateCertificate({
57250
57250
  ...hostConfig,
@@ -57348,49 +57348,22 @@ import * as net from "net";
57348
57348
  import process9 from "process";
57349
57349
 
57350
57350
  // src/hosts.ts
57351
- import { spawn } from "child_process";
57351
+ import { exec as exec2 } from "child_process";
57352
57352
  import fs5 from "fs";
57353
57353
  import os3 from "os";
57354
57354
  import path4 from "path";
57355
57355
  import process3 from "process";
57356
+ import { promisify } from "util";
57357
+ var execAsync = promisify(exec2);
57356
57358
  var hostsFilePath = process3.platform === "win32" ? path4.join(process3.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
57357
- async function sudoWrite(operation, content) {
57358
- return new Promise((resolve4, reject) => {
57359
- if (process3.platform === "win32") {
57360
- reject(new Error("Administrator privileges required on Windows"));
57361
- return;
57362
- }
57363
- const tmpFile = path4.join(os3.tmpdir(), "hosts.tmp");
57364
- try {
57365
- if (operation === "append") {
57366
- const currentContent = fs5.readFileSync(hostsFilePath, "utf8");
57367
- fs5.writeFileSync(tmpFile, currentContent + content, "utf8");
57368
- } else {
57369
- fs5.writeFileSync(tmpFile, content, "utf8");
57370
- }
57371
- const sudo = spawn("sudo", ["cp", tmpFile, hostsFilePath]);
57372
- sudo.on("close", (code) => {
57373
- try {
57374
- fs5.unlinkSync(tmpFile);
57375
- if (code === 0)
57376
- resolve4();
57377
- else
57378
- reject(new Error(`sudo process exited with code ${code}`));
57379
- } catch (err3) {
57380
- reject(err3);
57381
- }
57382
- });
57383
- sudo.on("error", (err3) => {
57384
- try {
57385
- fs5.unlinkSync(tmpFile);
57386
- } catch {
57387
- }
57388
- reject(err3);
57389
- });
57390
- } catch (err3) {
57391
- reject(err3);
57392
- }
57393
- });
57359
+ async function execSudo(command) {
57360
+ if (process3.platform === "win32")
57361
+ throw new Error("Administrator privileges required on Windows");
57362
+ try {
57363
+ await execAsync(`sudo ${command}`);
57364
+ } catch (error) {
57365
+ throw new Error(`Failed to execute sudo command: ${error.message}`);
57366
+ }
57394
57367
  }
57395
57368
  async function addHosts(hosts, verbose) {
57396
57369
  debugLog2("hosts", `Adding hosts: ${hosts.join(", ")}`, verbose);
@@ -57412,35 +57385,29 @@ async function addHosts(hosts, verbose) {
57412
57385
  127.0.0.1 ${host}
57413
57386
  ::1 ${host}`).join(`
57414
57387
  `);
57388
+ const tmpFile = path4.join(os3.tmpdir(), "hosts.tmp");
57389
+ await fs5.promises.writeFile(tmpFile, existingContent + hostEntries, "utf8");
57415
57390
  try {
57416
- await fs5.promises.appendFile(hostsFilePath, hostEntries, { flag: "a" });
57391
+ await execSudo(`cp "${tmpFile}" "${hostsFilePath}"`);
57417
57392
  log.success(`Added new hosts: ${newEntries.join(", ")}`);
57418
- } catch (writeErr) {
57419
- if (writeErr.code === "EACCES") {
57420
- debugLog2("hosts", "Permission denied, attempting with sudo", verbose);
57421
- try {
57422
- await sudoWrite("append", hostEntries);
57423
- log.success(`Added new hosts with sudo: ${newEntries.join(", ")}`);
57424
- } catch (sudoErr) {
57425
- log.error("Failed to modify hosts file automatically");
57426
- log.warn("Please add these entries to your hosts file manually:");
57427
- hostEntries.split(`
57393
+ } catch (error) {
57394
+ log.error("Failed to modify hosts file automatically");
57395
+ log.warn("Please add these entries to your hosts file manually:");
57396
+ hostEntries.split(`
57428
57397
  `).forEach((entry) => log.warn(entry));
57429
- if (process3.platform === "win32") {
57430
- log.warn(`
57398
+ if (process3.platform === "win32") {
57399
+ log.warn(`
57431
57400
  On Windows:`);
57432
- log.warn("1. Run notepad as administrator");
57433
- log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
57434
- } else {
57435
- log.warn(`
57436
- On Unix systems:`);
57437
- log.warn(`sudo nano ${hostsFilePath}`);
57438
- }
57439
- throw new Error("Failed to modify hosts file: manual intervention required");
57440
- }
57401
+ log.warn("1. Run notepad as administrator");
57402
+ log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
57441
57403
  } else {
57442
- throw writeErr;
57404
+ log.warn(`
57405
+ On Unix systems:`);
57406
+ log.warn(`sudo nano ${hostsFilePath}`);
57443
57407
  }
57408
+ throw new Error("Failed to modify hosts file: manual intervention required");
57409
+ } finally {
57410
+ fs5.unlinkSync(tmpFile);
57444
57411
  }
57445
57412
  } catch (err3) {
57446
57413
  const error = err3;
@@ -57466,38 +57433,32 @@ async function removeHosts(hosts, verbose) {
57466
57433
  const newContent = `${filteredLines.join(`
57467
57434
  `)}
57468
57435
  `;
57436
+ const tmpFile = path4.join(os3.tmpdir(), "hosts.tmp");
57437
+ await fs5.promises.writeFile(tmpFile, newContent, "utf8");
57469
57438
  try {
57470
- await fs5.promises.writeFile(hostsFilePath, newContent);
57439
+ await execSudo(`cp "${tmpFile}" "${hostsFilePath}"`);
57471
57440
  log.success("Hosts removed successfully");
57472
- } catch (writeErr) {
57473
- if (writeErr.code === "EACCES") {
57474
- debugLog2("hosts", "Permission denied, attempting with sudo", verbose);
57475
- try {
57476
- await sudoWrite("write", newContent);
57477
- log.success("Hosts removed successfully with sudo");
57478
- } catch (sudoErr) {
57479
- log.error("Failed to modify hosts file automatically");
57480
- log.warn("Please remove these entries from your hosts file manually:");
57481
- hosts.forEach((host) => {
57482
- log.warn("# Added by rpx");
57483
- log.warn(`127.0.0.1 ${host}`);
57484
- log.warn(`::1 ${host}`);
57485
- });
57486
- if (process3.platform === "win32") {
57487
- log.warn(`
57441
+ } catch (error) {
57442
+ log.error("Failed to modify hosts file automatically");
57443
+ log.warn("Please remove these entries from your hosts file manually:");
57444
+ hosts.forEach((host) => {
57445
+ log.warn("# Added by rpx");
57446
+ log.warn(`127.0.0.1 ${host}`);
57447
+ log.warn(`::1 ${host}`);
57448
+ });
57449
+ if (process3.platform === "win32") {
57450
+ log.warn(`
57488
57451
  On Windows:`);
57489
- log.warn("1. Run notepad as administrator");
57490
- log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
57491
- } else {
57492
- log.warn(`
57493
- On Unix systems:`);
57494
- log.warn(`sudo nano ${hostsFilePath}`);
57495
- }
57496
- throw new Error("Failed to modify hosts file: manual intervention required");
57497
- }
57452
+ log.warn("1. Run notepad as administrator");
57453
+ log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
57498
57454
  } else {
57499
- throw writeErr;
57455
+ log.warn(`
57456
+ On Unix systems:`);
57457
+ log.warn(`sudo nano ${hostsFilePath}`);
57500
57458
  }
57459
+ throw new Error("Failed to modify hosts file: manual intervention required");
57460
+ } finally {
57461
+ fs5.unlinkSync(tmpFile);
57501
57462
  }
57502
57463
  } catch (err3) {
57503
57464
  const error = err3;
@@ -57711,14 +57672,24 @@ async function startServer(options3) {
57711
57672
  ssl: sslConfig
57712
57673
  });
57713
57674
  }
57714
- async function createProxyServer(from, to, fromPort, listenPort, hostname, sourceUrl, ssl, verbose) {
57715
- debugLog2("proxy", `Creating proxy server ${from} -> ${to}`, verbose);
57675
+ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourceUrl, ssl, verbose, cleanUrls) {
57676
+ debugLog2("proxy", `Creating proxy server ${from} -> ${to} with cleanUrls: ${cleanUrls}`, verbose);
57716
57677
  const requestHandler = (req, res) => {
57717
57678
  debugLog2("request", `Incoming request: ${req.method} ${req.url}`, verbose);
57679
+ let path5 = req.url || "/";
57680
+ if (cleanUrls) {
57681
+ if (!path5.match(/\.[a-z0-9]+$/i)) {
57682
+ if (path5.endsWith("/")) {
57683
+ path5 = `${path5}index.html`;
57684
+ } else {
57685
+ path5 = `${path5}.html`;
57686
+ }
57687
+ }
57688
+ }
57718
57689
  const proxyOptions = {
57719
57690
  hostname: sourceUrl.hostname,
57720
57691
  port: fromPort,
57721
- path: req.url,
57692
+ path: path5,
57722
57693
  method: req.method,
57723
57694
  headers: {
57724
57695
  ...req.headers,
@@ -57728,6 +57699,42 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
57728
57699
  debugLog2("request", `Proxy request options: ${JSON.stringify(proxyOptions)}`, verbose);
57729
57700
  const proxyReq = http.request(proxyOptions, (proxyRes) => {
57730
57701
  debugLog2("response", `Proxy response received with status ${proxyRes.statusCode}`, verbose);
57702
+ if (cleanUrls && proxyRes.statusCode === 404) {
57703
+ const alternativePaths = [];
57704
+ if (path5.endsWith(".html")) {
57705
+ alternativePaths.push(path5.slice(0, -5));
57706
+ } else if (!path5.match(/\.[a-z0-9]+$/i)) {
57707
+ alternativePaths.push(`${path5}.html`);
57708
+ }
57709
+ if (!path5.endsWith("/")) {
57710
+ alternativePaths.push(`${path5}/index.html`);
57711
+ }
57712
+ if (alternativePaths.length > 0) {
57713
+ debugLog2("cleanUrls", `Trying alternative paths: ${alternativePaths.join(", ")}`, verbose);
57714
+ const tryNextPath = (paths) => {
57715
+ if (paths.length === 0) {
57716
+ res.writeHead(proxyRes.statusCode || 404, proxyRes.headers);
57717
+ proxyRes.pipe(res);
57718
+ return;
57719
+ }
57720
+ const altPath = paths[0];
57721
+ const altOptions = { ...proxyOptions, path: altPath };
57722
+ const altReq = http.request(altOptions, (altRes) => {
57723
+ if (altRes.statusCode === 200) {
57724
+ debugLog2("cleanUrls", `Found matching path: ${altPath}`, verbose);
57725
+ res.writeHead(altRes.statusCode, altRes.headers);
57726
+ altRes.pipe(res);
57727
+ } else {
57728
+ tryNextPath(paths.slice(1));
57729
+ }
57730
+ });
57731
+ altReq.on("error", () => tryNextPath(paths.slice(1)));
57732
+ altReq.end();
57733
+ };
57734
+ tryNextPath(alternativePaths);
57735
+ return;
57736
+ }
57737
+ }
57731
57738
  const headers = {
57732
57739
  ...proxyRes.headers,
57733
57740
  "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
@@ -57793,6 +57800,9 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
57793
57800
  console.log(` - HTTP/2 enabled`);
57794
57801
  console.log(` - HSTS enabled`);
57795
57802
  }
57803
+ if (cleanUrls) {
57804
+ console.log(` ${green("\u279C")} Clean URLs enabled`);
57805
+ }
57796
57806
  resolve4();
57797
57807
  });
57798
57808
  server.on("error", (err3) => {
@@ -57864,6 +57874,7 @@ function startProxy(options3) {
57864
57874
  const serverOptions = {
57865
57875
  from: mergedOptions.from,
57866
57876
  to: mergedOptions.to,
57877
+ cleanUrls: mergedOptions.cleanUrls,
57867
57878
  https: httpsConfig(mergedOptions),
57868
57879
  etcHostsCleanup: mergedOptions.etcHostsCleanup,
57869
57880
  verbose: mergedOptions.verbose
@@ -57905,11 +57916,13 @@ async function startProxies(options3) {
57905
57916
  ...proxy,
57906
57917
  https: mergedOptions.https,
57907
57918
  etcHostsCleanup: mergedOptions.etcHostsCleanup,
57919
+ cleanUrls: mergedOptions.cleanUrls,
57908
57920
  verbose: mergedOptions.verbose,
57909
57921
  _cachedSSLConfig: mergedOptions._cachedSSLConfig
57910
57922
  })) : [{
57911
57923
  from: mergedOptions.from || "localhost:5173",
57912
57924
  to: mergedOptions.to || "stacks.localhost",
57925
+ cleanUrls: mergedOptions.cleanUrls || false,
57913
57926
  https: mergedOptions.https,
57914
57927
  etcHostsCleanup: mergedOptions.etcHostsCleanup,
57915
57928
  verbose: mergedOptions.verbose,
@@ -57936,7 +57949,8 @@ async function startProxies(options3) {
57936
57949
  await startServer({
57937
57950
  from: option.from || "localhost:5173",
57938
57951
  to: domain,
57939
- https: option.https ?? false,
57952
+ cleanUrls: option.cleanUrls || false,
57953
+ https: option.https || false,
57940
57954
  etcHostsCleanup: option.etcHostsCleanup || false,
57941
57955
  verbose: option.verbose || false,
57942
57956
  _cachedSSLConfig: sslConfig
@@ -57953,7 +57967,6 @@ async function startProxies(options3) {
57953
57967
  var cli = new CAC("reverse-proxy");
57954
57968
  cli.command("start", "Start the Reverse Proxy Server").option("--from <from>", "The URL to proxy from").option("--to <to>", "The URL to proxy to").option("--key-path <path>", "Absolute path to the SSL key").option("--cert-path <path>", "Absolute path to the SSL certificate").option("--ca-cert-path <path>", "Absolute path to the SSL CA certificate").option("--etc-hosts-cleanup", "Cleanup /etc/hosts on exit").option("--verbose", "Enable verbose logging").example("reverse-proxy start --from localhost:5173 --to my-project.localhost").example("reverse-proxy start --from localhost:3000 --to my-project.localhost/api").example("reverse-proxy start --from localhost:3000 --to localhost:3001").example("reverse-proxy start --from localhost:5173 --to my-project.test --key-path /absolute/path/to/key --cert-path /absolute/path/to/cert").action(async (options3) => {
57955
57969
  if (!options3?.from || !options3.to) {
57956
- console.log("in here", config4);
57957
57970
  return startProxies(config4);
57958
57971
  }
57959
57972
  return startProxy({
package/dist/hosts.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ declare const execAsync: unknown;
1
2
  export declare const hostsFilePath: string;
2
- declare function sudoWrite(operation: 'append' | 'write', content: string): Promise<void>;
3
+ declare function execSudo(command: string): Promise<void>;
3
4
  export declare function addHosts(hosts: string[], verbose?: boolean): Promise<void>;
4
5
  export declare function removeHosts(hosts: string[], verbose?: boolean): Promise<void>;
5
6
  export declare function checkHosts(hosts: string[], verbose?: boolean): Promise<boolean[]>;
package/dist/https.d.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import type { ReverseProxyConfigs, ReverseProxyOption, ReverseProxyOptions, TlsConfig } from './types';
2
2
 
3
- export declare function isMultiProxyConfig(options: ReverseProxyConfigs): options is MultiReverseProxyConfig;
4
- export declare function isMultiProxyOptions(options: ReverseProxyOption | ReverseProxyOptions): options is MultiReverseProxyConfig;
5
- export declare function isSingleProxyOptions(options: ReverseProxyOption | ReverseProxyOptions): options is SingleReverseProxyConfig;
6
3
  export declare function resolveSSLPaths(options: ReverseProxyConfigs, defaultConfig: typeof config): TlsConfig;
7
4
  export declare function generateWildcardPatterns(domain: string): string[];
8
- export declare function getPrimaryDomain(options?: ReverseProxyOption | ReverseProxyOptions): string;
9
5
  export declare function generateSSLPaths(options?: ReverseProxyOptions): void;
10
6
  export declare function getAllDomains(options: ReverseProxyOption | ReverseProxyOptions): Set<string>;
11
7
  export declare function loadSSLConfig(options: ReverseProxyOption): Promise<SSLConfig | null>;