@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
@@ -1,69 +0,0 @@
1
- import { execSync } from 'node:child_process'
2
-
3
- /**
4
- * Normalize openssl / security fingerprint output to uppercase hex without separators.
5
- */
6
- export function normalizeSha256Fingerprint(raw: string): string {
7
- const value = raw.includes('=') ? raw.split('=').pop()! : raw
8
- return value.replace(/SHA-256\s+hash:\s*/gi, '').replace(/:/g, '').trim().toUpperCase()
9
- }
10
-
11
- export function readCertSha256Fingerprint(certPath: string): string | null {
12
- try {
13
- const out = execSync(`openssl x509 -noout -fingerprint -sha256 -in "${certPath}"`, { encoding: 'utf8' })
14
- return normalizeSha256Fingerprint(out)
15
- }
16
- catch {
17
- return null
18
- }
19
- }
20
-
21
- export function readCertCommonName(certPath: string): string | null {
22
- try {
23
- const subject = execSync(`openssl x509 -in "${certPath}" -noout -subject -nameopt RFC2253`, { encoding: 'utf8' })
24
- const match = subject.match(/CN=([^,/]+)/)
25
- return match?.[1]?.trim() ?? null
26
- }
27
- catch {
28
- return null
29
- }
30
- }
31
-
32
- export function certIncludesSanHostnames(certPath: string, hostnames: string[]): boolean {
33
- try {
34
- const text = execSync(`openssl x509 -in "${certPath}" -noout -text`, { encoding: 'utf8' })
35
- return hostnames.every(host => text.includes(`DNS:${host}`))
36
- }
37
- catch {
38
- return false
39
- }
40
- }
41
-
42
- /**
43
- * True when :443 (or `port`) presents a chain trusted by `caPath` for `domain`.
44
- */
45
- export function verifyHttpsChain(domain: string, caPath: string, port = 443): boolean {
46
- try {
47
- const out = execSync(
48
- `echo | openssl s_client -connect ${domain}:${port} -servername ${domain} -CAfile "${caPath}" 2>/dev/null | grep "Verify return code"`,
49
- { encoding: 'utf8', timeout: 4000 },
50
- )
51
- return out.includes(': 0 (ok)')
52
- }
53
- catch {
54
- return false
55
- }
56
- }
57
-
58
- /**
59
- * Parse `security find-certificate -Z` listing lines into SHA-256 hashes.
60
- */
61
- export function parseSha256HashesFromSecurityListing(listing: string): string[] {
62
- const hashes: string[] = []
63
- for (const line of listing.split('\n')) {
64
- const match = line.match(/SHA-256 hash:\s*([A-F0-9]+)/i)
65
- if (match)
66
- hashes.push(match[1]!.toUpperCase())
67
- }
68
- return hashes
69
- }
package/src/colors.ts DELETED
@@ -1,13 +0,0 @@
1
- const c = (open: number, close: number): (str: string) => string => (str: string): string => `\x1B[${open}m${str}\x1B[${close}m`
2
-
3
- export const colors: {
4
- bold: (str: string) => string
5
- dim: (str: string) => string
6
- green: (str: string) => string
7
- cyan: (str: string) => string
8
- } = {
9
- bold: c(1, 22),
10
- dim: c(2, 22),
11
- green: c(32, 39),
12
- cyan: c(36, 39),
13
- }
package/src/config.ts DELETED
@@ -1,45 +0,0 @@
1
- import type { ProxyConfig } from './types'
2
- import { homedir } from 'node:os'
3
- import { join, resolve } from 'node:path'
4
- import { loadConfig } from 'bunfig'
5
-
6
- export const defaultConfig: ProxyConfig = {
7
- from: 'localhost:5173',
8
- to: 'stacks.localhost',
9
- cleanUrls: false,
10
- https: {
11
- basePath: '',
12
- caCertPath: join(homedir(), '.stacks', 'ssl', `stacks.localhost.ca.crt`),
13
- certPath: join(homedir(), '.stacks', 'ssl', `stacks.localhost.crt`),
14
- keyPath: join(homedir(), '.stacks', 'ssl', `stacks.localhost.crt.key`),
15
- },
16
- cleanup: {
17
- certs: false,
18
- hosts: false,
19
- },
20
- vitePluginUsage: false,
21
- verbose: true,
22
- changeOrigin: false,
23
- /**
24
- * If true, will regenerate and re-trust certs that exist but are not trusted by the system.
25
- * If false, will use the existing cert even if not trusted (may result in browser warnings).
26
- */
27
- regenerateUntrustedCerts: true,
28
- }
29
-
30
- // Lazy-loaded config to avoid top-level await (enables bun --compile)
31
- let _config: ProxyConfig | null = null
32
-
33
- export async function getConfig(): Promise<ProxyConfig> {
34
- if (!_config) {
35
- _config = await loadConfig({
36
- name: 'rpx',
37
- cwd: resolve(__dirname, '..'),
38
- defaultConfig,
39
- })
40
- }
41
- return _config
42
- }
43
-
44
- // For backwards compatibility - synchronous access with default fallback
45
- export const config: ProxyConfig = defaultConfig
@@ -1,180 +0,0 @@
1
- /**
2
- * Bridges the `startProxy` / `startProxies` entrypoints to the long-running
3
- * rpx daemon. When `viaDaemon: true` is set on a proxy config (or
4
- * `--via-daemon` is passed to the CLI), we don't bind our own `:443` —
5
- * instead we:
6
- *
7
- * 1. Write a registry entry per proxy under `~/.stacks/rpx/registry.d`.
8
- * 2. Ensure the daemon is running (lazy-spawn if needed).
9
- * 3. Block until SIGINT/SIGTERM, then unregister our entries.
10
- *
11
- * The daemon's PID-GC reaps anything we miss if this process dies `kill -9`.
12
- */
13
- import type { PathRewrite, StaticRouteConfig } from './types'
14
- import * as fs from 'node:fs'
15
- import * as path from 'node:path'
16
- import * as process from 'node:process'
17
- import { ensureDaemonRunning } from './daemon'
18
- import { log } from './logger'
19
- import { getRegistryDir, isValidId, removeEntry, writeEntry } from './registry'
20
- import { debugLog } from './utils'
21
-
22
- export interface DaemonRunnerProxy {
23
- id?: string
24
- /** Upstream `host:port`. Optional when `static` is set. */
25
- from?: string
26
- to: string
27
- /**
28
- * Optional path prefix this route owns under the host `to` (e.g. `'/api'`).
29
- * Lets several routes share one host on different paths. When two proxies
30
- * share `to` but differ by `path`, give each an explicit `id` (or rely on the
31
- * path being folded into the derived id).
32
- */
33
- path?: string
34
- cleanUrls?: boolean
35
- changeOrigin?: boolean
36
- pathRewrites?: PathRewrite[]
37
- /** Serve a local directory for this route instead of proxying. */
38
- static?: string | StaticRouteConfig
39
- }
40
-
41
- export interface DaemonRunnerOptions {
42
- proxies: DaemonRunnerProxy[]
43
- verbose?: boolean
44
- /** Override the registry dir (tests). Defaults to `~/.stacks/rpx/registry.d`. */
45
- registryDir?: string
46
- /** Override the rpx state dir (tests). Defaults to `~/.stacks/rpx`. */
47
- rpxDir?: string
48
- /**
49
- * Skip the blocking await + signal handlers. Tests use this to register
50
- * entries, verify, and tear down without keeping the test runner alive.
51
- */
52
- detached?: boolean
53
- /** Override the daemon spawn command (tests). */
54
- spawnCommand?: string[]
55
- /** Passed through to `ensureDaemonRunning` (default 5000ms). */
56
- startupTimeoutMs?: number
57
- /** Extra env for the daemon child (e.g. `SUDO_PASSWORD`). */
58
- spawnEnv?: Record<string, string>
59
- /**
60
- * When true, registry entries omit `pid` so they persist until
61
- * `rpx unregister` — avoids PID-GC dropping routes when the registering
62
- * process is short-lived (e.g. a CLI wrapper).
63
- */
64
- persistent?: boolean
65
- }
66
-
67
- /**
68
- * Sanitize an arbitrary `to` host into a valid registry id. Drops anything
69
- * that isn't `[a-zA-Z0-9._-]`, collapses runs to a single dash, and trims
70
- * leading/trailing dashes. Falls back to `'rpx'` if nothing's left.
71
- */
72
- export function deriveIdFromTarget(to: string, routePath?: string): string {
73
- // Fold the path into the id so several routes on the same host don't collide
74
- // (e.g. `stacksjs.com` + `/api` and `stacksjs.com` + `/docs`).
75
- const base = routePath && routePath !== '/' ? `${to}${routePath}` : to
76
- const cleaned = base.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 128)
77
- return cleaned.length > 0 ? cleaned : 'rpx'
78
- }
79
-
80
- /**
81
- * Register every proxy with the daemon and (unless `detached`) block until a
82
- * shutdown signal arrives. Throws if any id collides or the daemon fails to
83
- * spawn.
84
- */
85
- export async function runViaDaemon(opts: DaemonRunnerOptions): Promise<void> {
86
- if (opts.proxies.length === 0)
87
- throw new Error('runViaDaemon: no proxies provided')
88
-
89
- const verbose = opts.verbose ?? false
90
- const registryDir = opts.registryDir
91
- const ids = new Set<string>()
92
-
93
- // Resolve and validate all ids up front so we don't half-register and bail.
94
- const resolved = opts.proxies.map((p) => {
95
- const id = p.id ?? deriveIdFromTarget(p.to, p.path)
96
- if (!isValidId(id))
97
- throw new Error(`invalid registry id "${id}" derived from to="${p.to}"`)
98
- if (ids.has(id))
99
- throw new Error(`duplicate registry id "${id}" — set an explicit \`id\` on one of the proxies`)
100
- ids.add(id)
101
- return { ...p, id }
102
- })
103
-
104
- const createdAt = new Date().toISOString()
105
- for (const p of resolved) {
106
- await writeEntry({
107
- id: p.id,
108
- from: p.from,
109
- to: p.to,
110
- path: p.path,
111
- pid: opts.persistent ? undefined : process.pid,
112
- cwd: process.cwd(),
113
- createdAt,
114
- cleanUrls: p.cleanUrls,
115
- changeOrigin: p.changeOrigin,
116
- pathRewrites: p.pathRewrites,
117
- static: p.static,
118
- }, registryDir, verbose)
119
- }
120
-
121
- const result = await ensureDaemonRunning({
122
- rpxDir: opts.rpxDir,
123
- verbose,
124
- spawnCommand: opts.spawnCommand,
125
- startupTimeoutMs: opts.startupTimeoutMs,
126
- spawnEnv: opts.spawnEnv,
127
- })
128
-
129
- for (const p of resolved) {
130
- const target = p.static
131
- ? `static ${typeof p.static === 'string' ? p.static : p.static.dir}`
132
- : p.from
133
- log.success(`https://${p.to} → ${target}`)
134
- }
135
- log.info(`(via rpx daemon pid=${result.pid}; \`rpx daemon:status\` to inspect)`)
136
-
137
- if (opts.detached)
138
- return
139
-
140
- // Cleanup registry entries on shutdown so the daemon's routing table reflects
141
- // reality immediately (its PID-GC would catch us eventually, but this is
142
- // faster and avoids a stale-route window).
143
- let cleaned = false
144
- const dirForCleanup = registryDir ?? getRegistryDir()
145
- const idsForCleanup = resolved.map(p => p.id)
146
-
147
- const cleanup = async (): Promise<void> => {
148
- if (cleaned)
149
- return
150
- cleaned = true
151
- for (const id of idsForCleanup) {
152
- await removeEntry(id, registryDir, verbose).catch((err) => {
153
- debugLog('runner', `removeEntry(${id}) failed: ${err}`, verbose)
154
- })
155
- }
156
- }
157
-
158
- const onSignal = (sig: NodeJS.Signals): void => {
159
- debugLog('runner', `received ${sig}, unregistering ${idsForCleanup.length} entries`, verbose)
160
- cleanup().finally(() => process.exit(0))
161
- }
162
- process.once('SIGINT', onSignal)
163
- process.once('SIGTERM', onSignal)
164
-
165
- // Last-resort sync cleanup if the process exits without our signal handlers
166
- // running (e.g. a thrown uncaught exception or `process.exit()` from elsewhere).
167
- process.once('exit', () => {
168
- if (cleaned)
169
- return
170
- for (const id of idsForCleanup) {
171
- try {
172
- fs.unlinkSync(path.join(dirForCleanup, `${id}.json`))
173
- }
174
- catch {}
175
- }
176
- })
177
-
178
- // Park forever; signal handlers do the actual exiting.
179
- await new Promise<void>(() => {})
180
- }