unplugin-https-reverse-proxy 4.0.3 → 4.1.0-beta.10
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/README.md +30 -6
- package/dist/{chunk-RSBXFJ3Z.js → chunk-C5A7XLKL.js} +9 -2
- package/dist/{chunk-2P7SUCQ3.js → chunk-EY3HXBJ4.js} +0 -26
- package/dist/chunk-RKHJN6KY.js +53 -0
- package/dist/{chunk-MD5CVZP7.js → chunk-U7KAIPZC.js} +982 -797
- package/dist/chunk-WOMR3ZLM.js +17 -0
- package/dist/esbuild.cjs +1086 -867
- package/dist/esbuild.js +5 -3
- package/dist/hmr-sse-bridge.cjs +12 -4
- package/dist/hmr-sse-bridge.d.cts +4 -3
- package/dist/hmr-sse-bridge.d.ts +4 -3
- package/dist/hmr-sse-bridge.js +5 -3
- package/dist/index.cjs +1086 -867
- package/dist/index.js +5 -3
- package/dist/nuxt.cjs +1057 -852
- package/dist/nuxt.js +6 -9
- package/dist/privilege-state.cjs +44 -0
- package/dist/privilege-state.d.cts +7 -0
- package/dist/privilege-state.d.ts +7 -0
- package/dist/privilege-state.js +10 -0
- package/dist/privileged-helper.cjs +824 -0
- package/dist/privileged-helper.d.cts +2 -0
- package/dist/privileged-helper.d.ts +2 -0
- package/dist/privileged-helper.js +797 -0
- package/dist/rollup.cjs +1086 -867
- package/dist/rollup.js +5 -3
- package/dist/rspack.cjs +1086 -867
- package/dist/rspack.js +5 -3
- package/dist/utils.cjs +0 -28
- package/dist/utils.d.cts +1 -3
- package/dist/utils.d.ts +1 -3
- package/dist/utils.js +1 -5
- package/dist/vite.cjs +1086 -867
- package/dist/vite.js +5 -3
- package/dist/webpack.cjs +1086 -867
- package/dist/webpack.js +5 -3
- package/package.json +1 -4
- package/src/caddy/caddyfile.ts +3 -0
- package/src/caddy/config.ts +195 -53
- package/src/caddy/constants.ts +30 -1
- package/src/caddy/index.ts +239 -206
- package/src/caddy/privileged.ts +124 -0
- package/src/caddy/utils.ts +0 -43
- package/src/hmr-sse-bridge.ts +14 -3
- package/src/index.ts +52 -25
- package/src/nuxt.ts +1 -6
- package/src/privilege-state.ts +23 -0
- package/src/privileged/bootstrap.ts +289 -0
- package/src/privileged/client.ts +288 -0
- package/src/privileged/entry.ts +73 -0
- package/src/privileged/hosts-file.ts +142 -0
- package/src/privileged/launch.ts +332 -0
- package/src/privileged/path-guard.ts +74 -0
- package/src/privileged/ports.ts +45 -0
- package/src/privileged/protocol.ts +111 -0
- package/src/privileged/server.ts +578 -0
- package/src/privileged/session.ts +169 -0
- package/src/privileged/validation.ts +38 -0
- package/src/privileged/windows-elevation.ts +80 -0
- package/src/privileged-helper.ts +123 -0
- package/src/utils.ts +0 -28
- package/src/host/index.ts +0 -52
package/dist/webpack.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
unpluginFactory
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-U7KAIPZC.js";
|
|
4
|
+
import "./chunk-EY3HXBJ4.js";
|
|
5
|
+
import "./chunk-C5A7XLKL.js";
|
|
6
|
+
import "./chunk-WOMR3ZLM.js";
|
|
7
|
+
import "./chunk-RKHJN6KY.js";
|
|
6
8
|
|
|
7
9
|
// src/webpack.ts
|
|
8
10
|
import { createWebpackPlugin } from "unplugin";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unplugin-https-reverse-proxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.1.0-beta.10",
|
|
5
5
|
"description": "",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/zcf0508/unplugin-https-reverse-proxy#readme",
|
|
@@ -119,7 +119,6 @@
|
|
|
119
119
|
"console-table-printer": "^2.12.1",
|
|
120
120
|
"dotenv-flow": "^4.1.0",
|
|
121
121
|
"got-cjs": "^12.5.4",
|
|
122
|
-
"hostile": "^1.4.0",
|
|
123
122
|
"http-proxy-agent": "^7.0.2",
|
|
124
123
|
"https-proxy-agent": "^7.0.6",
|
|
125
124
|
"ora": "5",
|
|
@@ -133,8 +132,6 @@
|
|
|
133
132
|
"@nuxt/schema": "^3.16.1",
|
|
134
133
|
"@rsbuild/core": "^0.4.15",
|
|
135
134
|
"@rspack/core": "^0.5.9",
|
|
136
|
-
"@types/hostile": "^1.3.5",
|
|
137
|
-
"@types/kill-port": "^2.0.3",
|
|
138
135
|
"@types/node": "^20.11.19",
|
|
139
136
|
"@types/proper-lockfile": "^4.1.4",
|
|
140
137
|
"@types/qrcode-terminal": "^0.12.2",
|
package/src/caddy/caddyfile.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const ProxyType = type({
|
|
|
12
12
|
|
|
13
13
|
const CaddyContext = type({
|
|
14
14
|
caddy_root: 'string',
|
|
15
|
+
admin_socket: 'string',
|
|
15
16
|
proxies: ProxyType.array(),
|
|
16
17
|
})
|
|
17
18
|
|
|
@@ -26,6 +27,8 @@ export function validateTemplateContext(ctx: unknown): typeof CaddyContext.infer
|
|
|
26
27
|
export const caddyTemplate = `{
|
|
27
28
|
debug
|
|
28
29
|
auto_https disable_redirects ignore_loaded_certs
|
|
30
|
+
skip_install_trust
|
|
31
|
+
admin unix/{{ admin_socket }}
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
:7601 {
|
package/src/caddy/config.ts
CHANGED
|
@@ -1,72 +1,214 @@
|
|
|
1
1
|
import type { ProxyType } from './caddyfile'
|
|
2
|
-
import { existsSync, readFileSync,
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import * as lockfile from 'proper-lockfile'
|
|
5
5
|
import { caddyConfigPath } from './constants'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/** One project's route, plus the pid that owns it. */
|
|
8
|
+
type ProxyEntry = typeof ProxyType.infer & { ownerPid?: number }
|
|
9
|
+
|
|
10
|
+
type ProxyMap = Record<string, ProxyEntry>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A file lock held around every read-modify-write of the shared config.
|
|
14
|
+
*
|
|
15
|
+
* Several dev servers run as independent processes, so a read-then-write from
|
|
16
|
+
* two of them can interleave and drop an entry. Synchronous calls are only
|
|
17
|
+
* atomic within one event loop, which is why the lock has to be a real file
|
|
18
|
+
* lock rather than an in-process queue.
|
|
19
|
+
*/
|
|
20
|
+
interface ConfigLock {
|
|
21
|
+
acquire: () => Promise<() => void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CaddyConfigApi {
|
|
25
|
+
configJsonRef: () => ProxyMap | undefined
|
|
26
|
+
/** Adds or replaces this project's routes, keeping everyone else's. */
|
|
27
|
+
writeCaddyConfig: (proxies: Array<typeof ProxyType.infer>, key: string) => Promise<void>
|
|
28
|
+
/** Drops one project's routes; the file goes away when nothing is left. */
|
|
29
|
+
removeProxy: (key: string) => Promise<void>
|
|
30
|
+
/** Reads the routes currently on disk, for regenerating the Caddyfile. */
|
|
31
|
+
readProxies: () => ProxyMap
|
|
10
32
|
cleanUp: () => Promise<void>
|
|
11
|
-
}
|
|
12
|
-
const configContentRef = signal('{}')
|
|
33
|
+
}
|
|
13
34
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
35
|
+
/** Owner pids that have exited can never clean up after themselves. */
|
|
36
|
+
function isAlive(pid: number | undefined): boolean {
|
|
37
|
+
if (pid === undefined)
|
|
38
|
+
return true
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0)
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
22
47
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Reads the shared config, dropping entries whose owner process is gone.
|
|
50
|
+
*
|
|
51
|
+
* A SIGKILLed dev server cannot remove its own route, so without this sweep its
|
|
52
|
+
* domain would keep resolving to a proxy with no upstream behind it.
|
|
53
|
+
*/
|
|
54
|
+
function readConfig(path: string = caddyConfigPath): ProxyMap {
|
|
55
|
+
if (!existsSync(path))
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
let parsed: ProxyMap
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(readFileSync(path, 'utf-8')) as ProxyMap
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// A half-written file is not worth preserving: it has no usable routes.
|
|
64
|
+
return {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const alive: ProxyMap = {}
|
|
68
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
69
|
+
if (isAlive(entry?.ownerPid))
|
|
70
|
+
alive[key] = entry
|
|
71
|
+
}
|
|
72
|
+
return alive
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Writes through a temporary file so a concurrent reader never sees a partial
|
|
77
|
+
* document. `rename` is atomic within a filesystem, which keeps the shared
|
|
78
|
+
* config readable at all times.
|
|
79
|
+
*/
|
|
80
|
+
function writeConfig(proxies: ProxyMap, path: string = caddyConfigPath): void {
|
|
81
|
+
const content = JSON.stringify(proxies, null, 2)
|
|
82
|
+
const temp = `${path}.${process.pid}.tmp`
|
|
83
|
+
writeFileSync(temp, content)
|
|
84
|
+
renameSync(temp, path)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CaddyConfigOptions {
|
|
88
|
+
/** Injected in tests so they do not need the real lock. */
|
|
89
|
+
lock?: ConfigLock
|
|
90
|
+
/**
|
|
91
|
+
* Config file to operate on; defaults to the shared runtime path.
|
|
92
|
+
*
|
|
93
|
+
* Injectable so a test can own a file without racing every other test that
|
|
94
|
+
* happens to run against the same module-level constant.
|
|
95
|
+
*/
|
|
96
|
+
configPath?: string
|
|
97
|
+
/**
|
|
98
|
+
* Renders the Caddyfile from the routes, called inside the config lock.
|
|
99
|
+
*
|
|
100
|
+
* Rendering here rather than in the caller is what keeps the file in step
|
|
101
|
+
* with the JSON: two projects writing concurrently would otherwise render
|
|
102
|
+
* from their own stale view and the later write would win.
|
|
103
|
+
*/
|
|
104
|
+
renderCaddyfile?: (proxies: ProxyMap) => Promise<void>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useCaddyConfig(options: CaddyConfigOptions = {}): CaddyConfigApi {
|
|
108
|
+
const lock = options.lock ?? createFileLock()
|
|
109
|
+
const configPath = options.configPath ?? caddyConfigPath
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Applies the change, then re-renders while still holding the lock.
|
|
113
|
+
*
|
|
114
|
+
* The Caddyfile has to follow every change: leaving a stopped project's site
|
|
115
|
+
* in it would keep proxying to an upstream that is gone. Doing it inside the
|
|
116
|
+
* lock also picks up routes whose owner process died, since `readConfig`
|
|
117
|
+
* drops those.
|
|
118
|
+
*/
|
|
119
|
+
async function mutate(
|
|
120
|
+
apply: (proxies: ProxyMap) => void,
|
|
121
|
+
): Promise<ProxyMap> {
|
|
122
|
+
const release = await lock.acquire()
|
|
27
123
|
try {
|
|
28
|
-
|
|
124
|
+
const proxies = readConfig(configPath)
|
|
125
|
+
apply(proxies)
|
|
126
|
+
writeConfig(proxies, configPath)
|
|
127
|
+
await options.renderCaddyfile?.(proxies)
|
|
128
|
+
return proxies
|
|
29
129
|
}
|
|
30
|
-
|
|
31
|
-
|
|
130
|
+
finally {
|
|
131
|
+
release()
|
|
32
132
|
}
|
|
33
133
|
}
|
|
34
134
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
135
|
+
return {
|
|
136
|
+
configJsonRef: () => (existsSync(configPath) ? readConfig(configPath) : undefined),
|
|
137
|
+
|
|
138
|
+
async writeCaddyConfig(_proxies, key) {
|
|
139
|
+
await mutate((proxies) => {
|
|
140
|
+
for (const p of _proxies)
|
|
141
|
+
proxies[routeKey(p, key)] = { ...p, base: normalizeBase(p.base), ownerPid: process.pid }
|
|
142
|
+
})
|
|
143
|
+
},
|
|
38
144
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const content = JSON.stringify(proxies, null, 2)
|
|
57
|
-
configContentRef(content)
|
|
58
|
-
return writeFileSync(caddyConfigPath, content)
|
|
145
|
+
async removeProxy(key) {
|
|
146
|
+
// The empty config is written inside the lock rather than unlinked after
|
|
147
|
+
// it: between the release and the unlink another project can add its
|
|
148
|
+
// route, and the unlink would then drop a route that is in use.
|
|
149
|
+
await mutate((current) => {
|
|
150
|
+
for (const route of Object.keys(current)) {
|
|
151
|
+
if (route.endsWith(`#${key}`))
|
|
152
|
+
delete current[route]
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
readProxies: () => readConfig(configPath),
|
|
158
|
+
|
|
159
|
+
async cleanUp() {
|
|
160
|
+
removeConfigFile(configPath)
|
|
161
|
+
},
|
|
59
162
|
}
|
|
163
|
+
}
|
|
60
164
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
165
|
+
function removeConfigFile(path: string = caddyConfigPath): void {
|
|
166
|
+
try {
|
|
167
|
+
if (existsSync(path))
|
|
168
|
+
unlinkSync(path)
|
|
65
169
|
}
|
|
170
|
+
catch {}
|
|
171
|
+
}
|
|
66
172
|
|
|
173
|
+
function normalizeBase(base: string | undefined): string {
|
|
174
|
+
let out = base || '/'
|
|
175
|
+
if (!out.startsWith('/'))
|
|
176
|
+
out = `/${out}`
|
|
177
|
+
if (out !== '/' && !out.endsWith('/'))
|
|
178
|
+
out = `${out}/`
|
|
179
|
+
return out
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Route key: the domain it answers on, plus the owning project.
|
|
184
|
+
*
|
|
185
|
+
* Two projects can legitimately serve the same domain on different bases, so
|
|
186
|
+
* the key has to carry the base as well as the owner.
|
|
187
|
+
*/
|
|
188
|
+
export function routeKey(proxy: typeof ProxyType.infer, ownerKey: string): string {
|
|
189
|
+
return `${proxy.target}${proxy.portSuffix}${normalizeBase(proxy.base)}#${ownerKey}`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Builds the on-disk lock.
|
|
194
|
+
*
|
|
195
|
+
* `proper-lockfile` retries because the winner may hold the lock for a moment;
|
|
196
|
+
* `realpath: false` keeps it working when the temp directory is a symlink.
|
|
197
|
+
*/
|
|
198
|
+
function createFileLock(): ConfigLock {
|
|
67
199
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
200
|
+
async acquire() {
|
|
201
|
+
if (!existsSync(caddyConfigPath))
|
|
202
|
+
writeFileSync(caddyConfigPath, '{}')
|
|
203
|
+
|
|
204
|
+
const release = await lockfile.lock(caddyConfigPath, {
|
|
205
|
+
realpath: false,
|
|
206
|
+
stale: 10_000,
|
|
207
|
+
retries: { retries: 20, minTimeout: 10, maxTimeout: 100 },
|
|
208
|
+
})
|
|
209
|
+
return () => {
|
|
210
|
+
void release().catch(() => {})
|
|
211
|
+
}
|
|
212
|
+
},
|
|
71
213
|
}
|
|
72
214
|
}
|
package/src/caddy/constants.ts
CHANGED
|
@@ -13,13 +13,42 @@ function resolvePath(path: string): string {
|
|
|
13
13
|
return path
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Directory for the Caddy binary, Caddyfile and the admin socket.
|
|
18
|
+
*
|
|
19
|
+
* The Caddy binary is executed as root, so this must not be a directory other
|
|
20
|
+
* users can write to. macOS gives every user a private `TMPDIR`, and `TEMP` on
|
|
21
|
+
* Windows is per-user too. On Linux `TMPDIR` is usually unset and `/tmp` is
|
|
22
|
+
* world-writable (sticky bit only prevents deleting other users' files, not
|
|
23
|
+
* creating `/tmp/caddy` first), so the fallback goes under the user's own cache
|
|
24
|
+
* directory instead.
|
|
25
|
+
*/
|
|
26
|
+
function defaultTempDir(): string {
|
|
27
|
+
const fromEnv = process.env.TEMP || process.env.TMPDIR
|
|
28
|
+
if (fromEnv)
|
|
29
|
+
return resolvePath(fromEnv)
|
|
30
|
+
if (process.platform === 'linux')
|
|
31
|
+
return join(process.env.XDG_CACHE_HOME || join(os.homedir(), '.cache'), 'uhrp')
|
|
32
|
+
return '/tmp'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const TEMP_DIR = resolvePath(process.env.UHRP_CADDY_PATH || defaultTempDir())
|
|
17
36
|
export const caddyPath = join(TEMP_DIR, `caddy${process.platform === 'win32' ? '.exe' : ''}`)
|
|
18
37
|
export const caddyFilePath = join(TEMP_DIR, 'CADDYFILE')
|
|
19
38
|
|
|
20
39
|
export const caddyConfigPath = join(TEMP_DIR, '.CADDYFILE.config.json')
|
|
21
40
|
export const caddyLockFilePath = join(TEMP_DIR, 'caddylock')
|
|
22
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Admin API socket, shared by every project using this helper.
|
|
44
|
+
*
|
|
45
|
+
* Caddy runs as root, so its admin API must not listen on a TCP port any local
|
|
46
|
+
* user can reach: `POST /load` there reconfigures the proxy with no further
|
|
47
|
+
* check. A unix socket inside the runtime directory is reachable only by whoever
|
|
48
|
+
* can open that directory, and the directory is private on macOS.
|
|
49
|
+
*/
|
|
50
|
+
export const caddyAdminSocketPath = join(TEMP_DIR, 'caddy-admin.sock')
|
|
51
|
+
|
|
23
52
|
export const supportList = [
|
|
24
53
|
{
|
|
25
54
|
platform: 'dragonfly',
|