@stacksjs/rpx 0.11.19 → 0.11.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/acme-challenge.d.ts +13 -0
  2. package/dist/auth.d.ts +21 -0
  3. package/dist/bin/cli.js +252 -200
  4. package/dist/cert-inspect.d.ts +5 -3
  5. package/dist/{chunk-83dqq28c.js → chunk-1108y1dk.js} +1 -1
  6. package/dist/{chunk-0f32jmrb.js → chunk-c9brkawa.js} +1 -1
  7. package/dist/{chunk-w888yhnp.js → chunk-cqkz06bg.js} +1 -1
  8. package/dist/chunk-qs36t2rf.js +224 -0
  9. package/dist/daemon.d.ts +4 -1
  10. package/dist/https.d.ts +1 -0
  11. package/dist/index.d.ts +33 -1
  12. package/dist/index.js +7 -7
  13. package/dist/proxy-handler.d.ts +18 -1
  14. package/dist/proxy-pool.d.ts +5 -0
  15. package/dist/redirect.d.ts +40 -0
  16. package/dist/registry.d.ts +2 -1
  17. package/dist/site-resolver.d.ts +106 -0
  18. package/dist/site-splash.d.ts +19 -0
  19. package/dist/site-supervisor.d.ts +56 -0
  20. package/dist/start.d.ts +31 -8
  21. package/dist/types.d.ts +64 -0
  22. package/package.json +6 -7
  23. package/dist/chunk-rs8gqpax.js +0 -174
  24. package/src/cert-inspect.ts +0 -69
  25. package/src/colors.ts +0 -13
  26. package/src/config.ts +0 -45
  27. package/src/daemon-runner.ts +0 -180
  28. package/src/daemon.ts +0 -1209
  29. package/src/dns-state.ts +0 -116
  30. package/src/dns.ts +0 -568
  31. package/src/host-match.ts +0 -52
  32. package/src/host-routes.ts +0 -147
  33. package/src/hosts.ts +0 -283
  34. package/src/https.ts +0 -905
  35. package/src/index.ts +0 -161
  36. package/src/logger.ts +0 -19
  37. package/src/macos-trust.ts +0 -175
  38. package/src/on-demand.ts +0 -264
  39. package/src/origin-guard.ts +0 -127
  40. package/src/port-manager.ts +0 -183
  41. package/src/process-manager.ts +0 -164
  42. package/src/proxy-handler.ts +0 -387
  43. package/src/proxy-pool.ts +0 -1003
  44. package/src/registry.ts +0 -366
  45. package/src/sni.ts +0 -93
  46. package/src/start.ts +0 -1421
  47. package/src/static-files.ts +0 -201
  48. package/src/types.ts +0 -267
  49. package/src/utils.ts +0 -243
package/src/utils.ts DELETED
@@ -1,243 +0,0 @@
1
- import type { MultiProxyConfig, PathRewrite, ProxyConfigs, ProxyOption, ProxyOptions, SingleProxyConfig } from './types'
2
- import { execSync } from 'node:child_process'
3
- import * as fs from 'node:fs/promises'
4
- import { Logger } from '@stacksjs/clarity'
5
-
6
- const logger = new Logger('rpx', {
7
- showTags: false,
8
- })
9
-
10
- /**
11
- * Get sudo password from environment variable if set
12
- */
13
- export function getSudoPassword(): string | undefined {
14
- return process.env.SUDO_PASSWORD
15
- }
16
-
17
- /** True when the process can write system paths without `sudo` (e.g. rpx daemon started via sudo). */
18
- export function isProcessElevated(): boolean {
19
- if (process.platform === 'win32')
20
- return false
21
- try {
22
- return typeof process.getuid === 'function' && process.getuid() === 0
23
- }
24
- catch {
25
- return false
26
- }
27
- }
28
-
29
- /**
30
- * Execute a command with sudo, using SUDO_PASSWORD if available
31
- */
32
- export function execSudoSync(command: string): string {
33
- if (isProcessElevated()) {
34
- return execSync(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] })
35
- }
36
-
37
- const sudoPassword = getSudoPassword()
38
- const escaped = command.replace(/'/g, `'\\''`)
39
-
40
- if (sudoPassword) {
41
- return execSync(`echo '${sudoPassword}' | sudo -S sh -c '${escaped}' 2>/dev/null`, {
42
- encoding: 'utf-8',
43
- stdio: ['pipe', 'pipe', 'pipe'],
44
- })
45
- }
46
-
47
- // Never block on an interactive password prompt — dev servers must start headlessly.
48
- try {
49
- return execSync(`sudo -n sh -c '${escaped}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] })
50
- }
51
- catch {
52
- throw new Error('sudo required but no cached credentials (set SUDO_PASSWORD in .env or run sudo -v)')
53
- }
54
- }
55
-
56
- export function debugLog(category: string, message: string, verbose?: boolean): void {
57
- if (verbose)
58
- logger.debug(`[rpx:${category}] ${message}`)
59
- }
60
-
61
- /**
62
- * Whether the proxy's listeners should set `reusePort`. Off by default: rpx is a
63
- * single-instance local-dev proxy, and one instance gains nothing from
64
- * `SO_REUSEPORT` while losing the helpful "port 443 already in use" guard.
65
- *
66
- * Opt in with `RPX_REUSE_PORT=1` when running **multiple** rpx instances behind a
67
- * process manager (systemd/pm2/k8s) on **Linux**, where the kernel load-balances
68
- * accepted connections across them for multi-core scaling. It is a no-op on
69
- * macOS/BSD, whose `SO_REUSEPORT` does not load-balance across processes — so
70
- * rpx never spawns a worker cluster itself; scaling is left to the OS + your
71
- * supervisor, which is where it belongs for a dev proxy.
72
- */
73
- export function shouldReusePort(): boolean {
74
- const v = process.env.RPX_REUSE_PORT
75
- return v === '1' || v === 'true'
76
- }
77
-
78
- const REDACTED = '[redacted]'
79
- const SENSITIVE_KEYS = new Set([
80
- 'certificate',
81
- 'privatekey',
82
- 'key',
83
- 'cert',
84
- 'ca',
85
- 'rootca',
86
- 'password',
87
- 'sudo_password',
88
- ])
89
- const PEM_BLOCK_RE = /-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/
90
-
91
- function isSensitiveKey(key: string): boolean {
92
- const normalized = key.toLowerCase()
93
- return SENSITIVE_KEYS.has(normalized)
94
- || normalized.endsWith('password')
95
- || normalized.includes('secret')
96
- || normalized.includes('token')
97
- }
98
-
99
- export function redactSensitive(value: unknown): unknown {
100
- if (Array.isArray(value))
101
- return value.map(item => redactSensitive(item))
102
-
103
- if (typeof value === 'string')
104
- return PEM_BLOCK_RE.test(value) ? REDACTED : value
105
-
106
- if (!value || typeof value !== 'object')
107
- return value
108
-
109
- const output: Record<string, unknown> = {}
110
- for (const [key, nested] of Object.entries(value)) {
111
- if (isSensitiveKey(key)) {
112
- output[key] = REDACTED
113
- continue
114
- }
115
-
116
- output[key] = redactSensitive(nested)
117
- }
118
-
119
- return output
120
- }
121
-
122
- export function safeStringify(value: unknown, space?: number): string {
123
- return JSON.stringify(redactSensitive(value), null, space)
124
- }
125
-
126
- /**
127
- * Extracts hostnames from proxy configuration
128
- */
129
- export function extractHostname(options: ProxyOption | ProxyOptions): string[] {
130
- if (isMultiProxyOptions(options)) {
131
- return options.proxies.map((proxy) => {
132
- const domain = proxy.to || 'stacks.localhost'
133
- return domain.startsWith('http') ? new URL(domain).hostname : domain
134
- })
135
- }
136
-
137
- if (isSingleProxyOptions(options)) {
138
- const domain = options.to || 'stacks.localhost'
139
- return [domain.startsWith('http') ? new URL(domain).hostname : domain]
140
- }
141
-
142
- return ['stacks.localhost']
143
- }
144
-
145
- interface RootCA {
146
- certificate: string
147
- privateKey: string
148
- }
149
-
150
- export function isValidRootCA(value: unknown): value is RootCA {
151
- return (
152
- typeof value === 'object'
153
- && value !== null
154
- && 'certificate' in value
155
- && 'privateKey' in value
156
- && typeof (value as RootCA).certificate === 'string'
157
- && typeof (value as RootCA).privateKey === 'string'
158
- )
159
- }
160
-
161
- export function getPrimaryDomain(options?: ProxyOption | ProxyOptions): string {
162
- if (!options)
163
- return 'stacks.localhost'
164
-
165
- if (isMultiProxyOptions(options) && options.proxies.length > 0)
166
- return options.proxies[0].to || 'stacks.localhost'
167
-
168
- if (isSingleProxyOptions(options))
169
- return options.to || 'stacks.localhost'
170
-
171
- return 'stacks.localhost'
172
- }
173
-
174
- /**
175
- * Type guard for multi-proxy configuration
176
- */
177
- export function isMultiProxyConfig(options: ProxyConfigs | ProxyOptions): options is MultiProxyConfig {
178
- return !!(options && 'proxies' in options && Array.isArray((options as MultiProxyConfig).proxies))
179
- }
180
-
181
- /**
182
- * Type guard to check if options are for multi-proxy configuration
183
- */
184
- export function isMultiProxyOptions(options: ProxyOption | ProxyOptions): options is MultiProxyConfig {
185
- return 'proxies' in options && Array.isArray((options as MultiProxyConfig).proxies)
186
- }
187
-
188
- /**
189
- * Type guard to check if options are for single-proxy configuration
190
- */
191
- export function isSingleProxyOptions(options: ProxyOption | ProxyOptions): options is SingleProxyConfig {
192
- return 'to' in options && typeof (options as SingleProxyConfig).to === 'string'
193
- }
194
-
195
- export function isSingleProxyConfig(options: ProxyConfigs | ProxyOptions): options is SingleProxyConfig {
196
- return !!(options && 'to' in options && !('proxies' in options))
197
- }
198
-
199
- /**
200
- * Resolve a path against a list of `pathRewrites`.
201
- *
202
- * Returns `null` if no rewrite matches; otherwise returns `{ targetHost, targetPath }`
203
- * with the prefix preserved by default (or stripped when `stripPrefix === true`).
204
- *
205
- * Matching rule: rewrite matches if `pathname` is exactly `from` OR starts with
206
- * `from + '/'`. So `/api` matches `/api`, `/api/`, `/api/cart` — but not `/apidocs`.
207
- */
208
- export function resolvePathRewrite(
209
- pathname: string,
210
- rewrites: PathRewrite[] | undefined,
211
- ): { targetHost: string, targetPath: string } | null {
212
- if (!rewrites || rewrites.length === 0)
213
- return null
214
-
215
- for (const rewrite of rewrites) {
216
- if (pathname === rewrite.from || pathname.startsWith(`${rewrite.from}/`)) {
217
- const targetHost = rewrite.to.startsWith('http') ? new URL(rewrite.to).host : rewrite.to
218
- const targetPath = rewrite.stripPrefix === true
219
- ? (pathname.slice(rewrite.from.length) || '/')
220
- : pathname
221
- return { targetHost, targetPath }
222
- }
223
- }
224
-
225
- return null
226
- }
227
-
228
- /**
229
- * Safely delete a file if it exists
230
- */
231
- export async function safeDeleteFile(filePath: string, verbose?: boolean): Promise<void> {
232
- try {
233
- // Try to delete the file directly without checking existence first
234
- await fs.unlink(filePath)
235
- debugLog('certificates', `Successfully deleted: ${filePath}`, verbose)
236
- }
237
- catch (err) {
238
- // Ignore errors where file doesn't exist
239
- if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
240
- debugLog('certificates', `Warning: Could not delete ${filePath}: ${err}`, verbose)
241
- }
242
- }
243
- }