@stacksjs/rpx 0.11.3 → 0.11.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.
@@ -1,2 +1,2 @@
1
1
  import{createRequire as a}from"node:module";var c=a(import.meta.url);
2
- export{c as H};
2
+ export{c as I};
@@ -0,0 +1 @@
1
+ import{A as e,B as f,C as g,D as h,E as i,F as j,G as k,H as l,w as a,x as b,y as c,z as d}from"./chunk-cvt0dqrv.js";import"./chunk-sqn04kae.js";export{l as safeDeleteFile,k as resolvePathRewrite,e as isValidRootCA,i as isSingleProxyOptions,j as isSingleProxyConfig,h as isMultiProxyOptions,g as isMultiProxyConfig,a as getSudoPassword,f as getPrimaryDomain,d as extractHostname,b as execSudoSync,c as debugLog};
package/dist/https.d.ts CHANGED
@@ -24,16 +24,3 @@ export declare function loadSSLConfig(options: ProxyOption): Promise<SSLConfig |
24
24
  */
25
25
  export declare function forceTrustCertificate(certPath: string): Promise<boolean>;
26
26
  export declare function generateCertificate(options: ProxyOptions): Promise<void>;
27
- export declare function getSSLConfig(): { key: string, cert: string, ca?: string } | null;
28
- // needs to accept the options
29
- export declare function checkExistingCertificates(options?: ProxyOptions): Promise<SSLConfig | null>;
30
- export declare function httpsConfig(options: ProxyOption | ProxyOptions, verbose?: boolean): TlsConfig;
31
- /**
32
- * Clean up SSL certificates for a specific domain
33
- */
34
- export declare function cleanupCertificates(domain: string, verbose?: boolean): Promise<void>;
35
- /**
36
- * Checks if a certificate is trusted by the system (macOS only for now)
37
- * If options.regenerateUntrustedCerts is false, always returns true (skips trust check)
38
- */
39
- export declare function isCertTrusted(certPath: string, options?: { verbose?: boolean, regenerateUntrustedCerts?: boolean }): Promise<boolean>;
@@ -1,3 +1,4 @@
1
+ import * as process from 'node:process';
1
2
  import type { ChildProcess } from 'node:child_process';
2
3
  import type { StartOptions } from './types';
3
4
  export declare const processManager: ProcessManager;
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- import{a as t,c as r,d as e,e as f,f as a,g as i,h as s,i as p,j as c,k as x,l as m,m as n,n as g,o as C,p as l,q as P,s as d,t as u,u as v,v as o}from"../chunk-8mnzvjyr.js";import"../chunk-3y886wa5.js";import{A as G,B as I,C as J,D as K,E as N,F as O,G as Q,w as j,x as q,y as z,z as B}from"../chunk-g5db14m7.js";import"../chunk-gbny098p.js";var U=o;export{u as startServer,v as startProxy,o as startProxies,Q as safeDeleteFile,f as removeHosts,P as portManager,i as loadSSLConfig,G as isValidRootCA,N as isSingleProxyOptions,O as isSingleProxyConfig,g as isPortInUse,K as isMultiProxyOptions,J as isMultiProxyConfig,n as isCertTrusted,x as httpsConfig,j as getSudoPassword,I as getPrimaryDomain,p as generateCertificate,s as forceTrustCertificate,C as findAvailablePort,B as extractHostname,q as execSudoSync,r as defaultConfig,U as default,z as debugLog,r as config,t as colors,m as cleanupCertificates,d as cleanup,a as checkHosts,c as checkExistingCertificates,e as addHosts,l as DefaultPortManager};
1
+ import{a as t,c as r,d as e,e as f,f as a,g as i,h as s,i as p,j as c,k as x,l as m,m as n,n as g,o as C,p as l,q as P,s as d,t as u,u as v,v as o}from"../chunk-grcvjvzg.js";import"../chunk-hj5q1vd6.js";import{A as G,B as I,C as J,D as K,E as N,F as O,G as Q,H as R,w as j,x as q,y as z,z as B}from"../chunk-cvt0dqrv.js";import"../chunk-sqn04kae.js";var U=o;export{u as startServer,v as startProxy,o as startProxies,R as safeDeleteFile,Q as resolvePathRewrite,f as removeHosts,P as portManager,i as loadSSLConfig,G as isValidRootCA,N as isSingleProxyOptions,O as isSingleProxyConfig,g as isPortInUse,K as isMultiProxyOptions,J as isMultiProxyConfig,n as isCertTrusted,x as httpsConfig,j as getSudoPassword,I as getPrimaryDomain,p as generateCertificate,s as forceTrustCertificate,C as findAvailablePort,B as extractHostname,q as execSudoSync,r as defaultConfig,U as default,z as debugLog,r as config,t as colors,m as cleanupCertificates,d as cleanup,a as checkHosts,c as checkExistingCertificates,e as addHosts,l as DefaultPortManager};
package/dist/start.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import * as http from 'node:http';
2
+ import * as http2 from 'node:http2';
3
+ import * as https from 'node:https';
1
4
  import type { CleanupOptions, ProxyOption, ProxyOptions, ProxySetupOptions, SingleProxyConfig } from './types';
2
5
  export declare function cleanup(options?: CleanupOptions): Promise<void>;
3
6
  export declare function startServer(options: SingleProxyConfig): Promise<void>;
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MultiProxyConfig, ProxyConfigs, ProxyOption, ProxyOptions, SingleProxyConfig } from './types';
1
+ import type { MultiProxyConfig, PathRewrite, ProxyConfigs, ProxyOption, ProxyOptions, SingleProxyConfig } from './types';
2
2
  /**
3
3
  * Get sudo password from environment variable if set
4
4
  */
@@ -27,6 +27,16 @@ export declare function isMultiProxyOptions(options: ProxyOption | ProxyOptions)
27
27
  */
28
28
  export declare function isSingleProxyOptions(options: ProxyOption | ProxyOptions): options is SingleProxyConfig;
29
29
  export declare function isSingleProxyConfig(options: ProxyConfigs | ProxyOptions): options is SingleProxyConfig;
30
+ /**
31
+ * Resolve a path against a list of `pathRewrites`.
32
+ *
33
+ * Returns `null` if no rewrite matches; otherwise returns `{ targetHost, targetPath }`
34
+ * with the prefix preserved by default (or stripped when `stripPrefix === true`).
35
+ *
36
+ * Matching rule: rewrite matches if `pathname` is exactly `from` OR starts with
37
+ * `from + '/'`. So `/api` matches `/api`, `/api/`, `/api/cart` — but not `/apidocs`.
38
+ */
39
+ export declare function resolvePathRewrite(pathname: string, rewrites: PathRewrite[] | undefined): { targetHost: string, targetPath: string } | null;
30
40
  /**
31
41
  * Safely delete a file if it exists
32
42
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/rpx",
3
3
  "type": "module",
4
- "version": "0.11.3",
4
+ "version": "0.11.5",
5
5
  "description": "A modern and smart reverse proxy.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -31,15 +31,6 @@
31
31
  "import": "./dist/src/index.js"
32
32
  }
33
33
  },
34
- "publishConfig": {
35
- "exports": {
36
- ".": {
37
- "types": "./dist/index.d.ts",
38
- "bun": "./dist/src/index.js",
39
- "import": "./dist/src/index.js"
40
- }
41
- }
42
- },
43
34
  "module": "./dist/src/index.js",
44
35
  "types": "./dist/index.d.ts",
45
36
  "bin": {
@@ -48,7 +39,8 @@
48
39
  },
49
40
  "files": [
50
41
  "README.md",
51
- "dist"
42
+ "dist",
43
+ "src"
52
44
  ],
53
45
  "scripts": {
54
46
  "build": "bun build.ts && bun run compile",
package/src/colors.ts ADDED
@@ -0,0 +1,13 @@
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 ADDED
@@ -0,0 +1,45 @@
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
package/src/dns.ts ADDED
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Minimal DNS server for local development
3
+ * Handles DNS queries for configured domains and responds with localhost IPs
4
+ */
5
+ import dgram from 'node:dgram'
6
+ import { debugLog } from './utils'
7
+
8
+ // Use a high port that doesn't require root
9
+ const DNS_PORT = 15353
10
+
11
+ interface DnsHeader {
12
+ id: number
13
+ flags: number
14
+ qdcount: number
15
+ ancount: number
16
+ nscount: number
17
+ arcount: number
18
+ }
19
+
20
+ interface DnsQuestion {
21
+ name: string
22
+ type: number
23
+ class: number
24
+ }
25
+
26
+ /**
27
+ * Parse DNS header from buffer
28
+ */
29
+ function parseHeader(buffer: Buffer): DnsHeader {
30
+ return {
31
+ id: buffer.readUInt16BE(0),
32
+ flags: buffer.readUInt16BE(2),
33
+ qdcount: buffer.readUInt16BE(4),
34
+ ancount: buffer.readUInt16BE(6),
35
+ nscount: buffer.readUInt16BE(8),
36
+ arcount: buffer.readUInt16BE(10),
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Parse domain name from DNS message
42
+ */
43
+ function parseName(buffer: Buffer, offset: number): { name: string, newOffset: number } {
44
+ const labels: string[] = []
45
+ let currentOffset = offset
46
+
47
+ while (true) {
48
+ const length = buffer[currentOffset]
49
+
50
+ if (length === 0) {
51
+ currentOffset++
52
+ break
53
+ }
54
+
55
+ // Check for pointer (compression)
56
+ if ((length & 0xC0) === 0xC0) {
57
+ const pointer = buffer.readUInt16BE(currentOffset) & 0x3FFF
58
+ const { name } = parseName(buffer, pointer)
59
+ labels.push(name)
60
+ currentOffset += 2
61
+ break
62
+ }
63
+
64
+ currentOffset++
65
+ labels.push(buffer.subarray(currentOffset, currentOffset + length).toString('ascii'))
66
+ currentOffset += length
67
+ }
68
+
69
+ return { name: labels.join('.'), newOffset: currentOffset }
70
+ }
71
+
72
+ /**
73
+ * Parse DNS question section
74
+ */
75
+ function parseQuestion(buffer: Buffer, offset: number): { question: DnsQuestion, newOffset: number } {
76
+ const { name, newOffset } = parseName(buffer, offset)
77
+ const type = buffer.readUInt16BE(newOffset)
78
+ const qclass = buffer.readUInt16BE(newOffset + 2)
79
+
80
+ return {
81
+ question: { name, type, class: qclass },
82
+ newOffset: newOffset + 4,
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Encode domain name for DNS response
88
+ */
89
+ function encodeName(name: string): Buffer {
90
+ const labels = name.split('.')
91
+ const parts: Buffer[] = []
92
+
93
+ for (const label of labels) {
94
+ parts.push(Buffer.from([label.length]))
95
+ parts.push(Buffer.from(label, 'ascii'))
96
+ }
97
+ parts.push(Buffer.from([0])) // Null terminator
98
+
99
+ return Buffer.concat(parts)
100
+ }
101
+
102
+ /**
103
+ * Build DNS response
104
+ */
105
+ function buildResponse(
106
+ queryId: number,
107
+ question: DnsQuestion,
108
+ ip: string,
109
+ ): Buffer {
110
+ const parts: Buffer[] = []
111
+
112
+ // Header
113
+ const header = Buffer.alloc(12)
114
+ header.writeUInt16BE(queryId, 0) // ID
115
+ header.writeUInt16BE(0x8180, 2) // Flags: Response, Authoritative, No error
116
+ header.writeUInt16BE(1, 4) // Questions: 1
117
+ header.writeUInt16BE(1, 6) // Answers: 1
118
+ header.writeUInt16BE(0, 8) // Authority: 0
119
+ header.writeUInt16BE(0, 10) // Additional: 0
120
+ parts.push(header)
121
+
122
+ // Question section (echo back)
123
+ parts.push(encodeName(question.name))
124
+ const qtype = Buffer.alloc(4)
125
+ qtype.writeUInt16BE(question.type, 0)
126
+ qtype.writeUInt16BE(question.class, 2)
127
+ parts.push(qtype)
128
+
129
+ // Answer section
130
+ parts.push(encodeName(question.name))
131
+
132
+ const answer = Buffer.alloc(10)
133
+ answer.writeUInt16BE(question.type, 0) // Type
134
+ answer.writeUInt16BE(1, 2) // Class: IN
135
+ answer.writeUInt32BE(300, 4) // TTL: 5 minutes
136
+
137
+ if (question.type === 1) {
138
+ // A record (IPv4)
139
+ answer.writeUInt16BE(4, 8) // Data length
140
+ parts.push(answer)
141
+ const ipParts = ip.split('.').map(p => Number.parseInt(p, 10))
142
+ parts.push(Buffer.from(ipParts))
143
+ }
144
+ else if (question.type === 28) {
145
+ // AAAA record (IPv6)
146
+ answer.writeUInt16BE(16, 8) // Data length
147
+ parts.push(answer)
148
+ // ::1 as bytes
149
+ parts.push(Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]))
150
+ }
151
+ else {
152
+ // Unsupported type - return NXDOMAIN
153
+ header.writeUInt16BE(0x8183, 2) // Flags with NXDOMAIN
154
+ header.writeUInt16BE(0, 6) // No answers
155
+ return Buffer.concat([header, encodeName(question.name), qtype])
156
+ }
157
+
158
+ return Buffer.concat(parts)
159
+ }
160
+
161
+ /**
162
+ * Build NXDOMAIN response for unknown domains
163
+ */
164
+ function buildNxdomainResponse(queryId: number, question: DnsQuestion): Buffer {
165
+ const parts: Buffer[] = []
166
+
167
+ // Header with NXDOMAIN
168
+ const header = Buffer.alloc(12)
169
+ header.writeUInt16BE(queryId, 0) // ID
170
+ header.writeUInt16BE(0x8183, 2) // Flags: Response, Authoritative, NXDOMAIN
171
+ header.writeUInt16BE(1, 4) // Questions: 1
172
+ header.writeUInt16BE(0, 6) // Answers: 0
173
+ header.writeUInt16BE(0, 8) // Authority: 0
174
+ header.writeUInt16BE(0, 10) // Additional: 0
175
+ parts.push(header)
176
+
177
+ // Question section (echo back)
178
+ parts.push(encodeName(question.name))
179
+ const qtype = Buffer.alloc(4)
180
+ qtype.writeUInt16BE(question.type, 0)
181
+ qtype.writeUInt16BE(question.class, 2)
182
+ parts.push(qtype)
183
+
184
+ return Buffer.concat(parts)
185
+ }
186
+
187
+ let dnsServer: dgram.Socket | null = null
188
+ let configuredDomains: Set<string> = new Set()
189
+
190
+ /**
191
+ * Start the DNS server
192
+ */
193
+ export async function startDnsServer(domains: string[], verbose?: boolean): Promise<boolean> {
194
+ if (dnsServer) {
195
+ debugLog('dns', 'DNS server already running', verbose)
196
+ return true
197
+ }
198
+
199
+ configuredDomains = new Set(domains.map(d => d.toLowerCase()))
200
+
201
+ return new Promise((resolve) => {
202
+ dnsServer = dgram.createSocket('udp4')
203
+
204
+ dnsServer.on('error', (err) => {
205
+ debugLog('dns', `DNS server error: ${err.message}`, verbose)
206
+ if (err.message.includes('EACCES') || err.message.includes('permission')) {
207
+ debugLog('dns', 'DNS server requires root privileges to bind to port 53', verbose)
208
+ }
209
+ dnsServer?.close()
210
+ dnsServer = null
211
+ resolve(false)
212
+ })
213
+
214
+ dnsServer.on('message', (msg, rinfo) => {
215
+ try {
216
+ const header = parseHeader(msg)
217
+ const { question } = parseQuestion(msg, 12)
218
+
219
+ debugLog('dns', `Query for ${question.name} type ${question.type} from ${rinfo.address}`, verbose)
220
+
221
+ // Check if this domain should be handled
222
+ const domainLower = question.name.toLowerCase()
223
+ let shouldHandle = false
224
+
225
+ for (const configured of configuredDomains) {
226
+ if (domainLower === configured || domainLower.endsWith(`.${configured}`)) {
227
+ shouldHandle = true
228
+ break
229
+ }
230
+ }
231
+
232
+ // Note: Only configured domains are handled, no hardcoded TLDs
233
+
234
+ let response: Buffer
235
+ if (shouldHandle && (question.type === 1 || question.type === 28)) {
236
+ response = buildResponse(header.id, question, '127.0.0.1')
237
+ debugLog('dns', `Responding with localhost for ${question.name}`, verbose)
238
+ }
239
+ else {
240
+ response = buildNxdomainResponse(header.id, question)
241
+ debugLog('dns', `NXDOMAIN for ${question.name}`, verbose)
242
+ }
243
+
244
+ dnsServer?.send(response, rinfo.port, rinfo.address)
245
+ }
246
+ catch (err) {
247
+ debugLog('dns', `Error processing DNS query: ${err}`, verbose)
248
+ }
249
+ })
250
+
251
+ dnsServer.on('listening', () => {
252
+ const address = dnsServer?.address()
253
+ debugLog('dns', `DNS server listening on ${address?.address}:${address?.port}`, verbose)
254
+ resolve(true)
255
+ })
256
+
257
+ // Try to bind to port 53 with sudo
258
+ try {
259
+ dnsServer.bind(DNS_PORT, '127.0.0.1')
260
+ }
261
+ catch (err) {
262
+ debugLog('dns', `Failed to bind DNS server: ${err}`, verbose)
263
+ resolve(false)
264
+ }
265
+ })
266
+ }
267
+
268
+ /**
269
+ * Stop the DNS server
270
+ */
271
+ export function stopDnsServer(verbose?: boolean): void {
272
+ if (dnsServer) {
273
+ debugLog('dns', 'Stopping DNS server', verbose)
274
+ dnsServer.close()
275
+ dnsServer = null
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Check if DNS server is running
281
+ */
282
+ export function isDnsServerRunning(): boolean {
283
+ return dnsServer !== null
284
+ }
285
+
286
+ /**
287
+ * Extract unique TLDs from domain list
288
+ */
289
+ function extractTLDs(domains: string[]): string[] {
290
+ const tlds = new Set<string>()
291
+ for (const domain of domains) {
292
+ const parts = domain.split('.')
293
+ if (parts.length >= 2) {
294
+ tlds.add(parts[parts.length - 1])
295
+ }
296
+ }
297
+ return Array.from(tlds)
298
+ }
299
+
300
+ // Track which TLDs we've created resolvers for
301
+ const createdResolvers = new Set<string>()
302
+
303
+ /**
304
+ * Flush macOS DNS cache to ensure resolver changes take effect
305
+ */
306
+ async function flushDnsCache(verbose?: boolean): Promise<void> {
307
+ if (process.platform !== 'darwin') {
308
+ return
309
+ }
310
+
311
+ const { execSudoSync, getSudoPassword } = await import('./utils')
312
+ const sudoPassword = getSudoPassword()
313
+
314
+ if (!sudoPassword) {
315
+ debugLog('dns', 'Cannot flush DNS cache without SUDO_PASSWORD', verbose)
316
+ return
317
+ }
318
+
319
+ try {
320
+ // Flush DNS cache and restart mDNSResponder
321
+ execSudoSync('dscacheutil -flushcache')
322
+ execSudoSync('killall -HUP mDNSResponder 2>/dev/null || true')
323
+ debugLog('dns', 'DNS cache flushed', verbose)
324
+ }
325
+ catch (err) {
326
+ // Non-fatal - DNS cache flush failure shouldn't block startup
327
+ debugLog('dns', `Could not flush DNS cache: ${err}`, verbose)
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Set up the macOS resolver for configured domains
333
+ * Creates /etc/resolver/<tld> files pointing to our local DNS server
334
+ */
335
+ export async function setupResolver(verbose?: boolean, domains?: string[]): Promise<boolean> {
336
+ if (process.platform !== 'darwin') {
337
+ debugLog('dns', 'Resolver setup only needed on macOS', verbose)
338
+ return true
339
+ }
340
+
341
+ const { execSudoSync, getSudoPassword } = await import('./utils')
342
+ const sudoPassword = getSudoPassword()
343
+
344
+ if (!sudoPassword) {
345
+ debugLog('dns', 'SUDO_PASSWORD not set, cannot create resolver files', verbose)
346
+ return false
347
+ }
348
+
349
+ // Get TLDs from configured domains
350
+ const tlds = domains ? extractTLDs(domains) : ['test']
351
+
352
+ try {
353
+ for (const tld of tlds) {
354
+ if (createdResolvers.has(tld)) {
355
+ continue
356
+ }
357
+
358
+ // Use bash -c to properly handle the echo with newlines
359
+ const cmd = `bash -c 'mkdir -p /etc/resolver && echo -e "nameserver 127.0.0.1\\nport ${DNS_PORT}" > /etc/resolver/${tld}'`
360
+ execSudoSync(cmd)
361
+ createdResolvers.add(tld)
362
+ debugLog('dns', `Created /etc/resolver/${tld} for .${tld} TLD`, verbose)
363
+ }
364
+
365
+ // Flush DNS cache to ensure new resolver files take effect immediately
366
+ await flushDnsCache(verbose)
367
+
368
+ return true
369
+ }
370
+ catch (err) {
371
+ debugLog('dns', `Failed to create resolver file: ${err}`, verbose)
372
+ return false
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Remove the macOS resolver files we created
378
+ */
379
+ export async function removeResolver(verbose?: boolean): Promise<void> {
380
+ if (process.platform !== 'darwin') {
381
+ return
382
+ }
383
+
384
+ const { execSudoSync, getSudoPassword } = await import('./utils')
385
+
386
+ try {
387
+ const sudoPassword = getSudoPassword()
388
+ if (sudoPassword) {
389
+ for (const tld of createdResolvers) {
390
+ execSudoSync(`rm -f /etc/resolver/${tld}`)
391
+ debugLog('dns', `Removed /etc/resolver/${tld}`, verbose)
392
+ }
393
+ createdResolvers.clear()
394
+ }
395
+ }
396
+ catch (err) {
397
+ debugLog('dns', `Failed to remove resolver files: ${err}`, verbose)
398
+ }
399
+ }