unplugin-https-reverse-proxy 2.0.1 → 2.0.3-beta.1

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/webpack.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  unpluginFactory
3
- } from "./chunk-DKHKFLLC.js";
3
+ } from "./chunk-6ESO3Y2K.js";
4
4
  import "./chunk-2P7SUCQ3.js";
5
5
 
6
6
  // src/webpack.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unplugin-https-reverse-proxy",
3
3
  "type": "module",
4
- "version": "2.0.1",
4
+ "version": "2.0.3-beta.1",
5
5
  "description": "",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/zcf0508/unplugin-https-reverse-proxy#readme",
@@ -75,7 +75,6 @@
75
75
  },
76
76
  "files": [
77
77
  "dist",
78
- "scripts/postinstall.cjs",
79
78
  "src"
80
79
  ],
81
80
  "peerDependencies": {
@@ -140,11 +139,14 @@
140
139
  "@types/proper-lockfile": "^4.1.4",
141
140
  "@types/qrcode-terminal": "^0.12.2",
142
141
  "@vue/cli": "^5.0.8",
142
+ "alien-signals": "^2.0.7",
143
+ "arktype": "^2.1.20",
143
144
  "bumpp": "^9.11.1",
144
145
  "chalk": "^5.4.1",
145
146
  "cross-env": "^7.0.3",
146
147
  "eslint": "^9.20.0",
147
148
  "fast-glob": "^3.3.3",
149
+ "liquidjs": "^10.21.1",
148
150
  "nodemon": "^3.1.9",
149
151
  "proper-lockfile": "^4.1.2",
150
152
  "rimraf": "^5.0.10",
@@ -157,7 +159,6 @@
157
159
  "webpack": "^5.90.3"
158
160
  },
159
161
  "scripts": {
160
- "postinstall": "node scripts/postinstall.cjs",
161
162
  "build": "tsup",
162
163
  "dev": "tsup --watch src",
163
164
  "build:fix": "tsx scripts/postbuild.ts",
@@ -0,0 +1,81 @@
1
+ import { type } from 'arktype'
2
+ import { Liquid } from 'liquidjs'
3
+
4
+ export const ProxyType = type({
5
+ target: 'string',
6
+ port_suffix: 'string',
7
+ tls: 'string',
8
+ source: 'string',
9
+ base: 'string',
10
+ })
11
+
12
+ const CaddyContext = type({
13
+ caddy_root: 'string',
14
+ proxies: ProxyType.array(),
15
+ })
16
+
17
+ export function validateTemplateContext(ctx: unknown): typeof CaddyContext.infer {
18
+ const out = CaddyContext(ctx as any)
19
+ if (out instanceof type.errors)
20
+ throw new Error(out.summary)
21
+
22
+ return ctx as typeof CaddyContext.infer
23
+ }
24
+
25
+ export const caddyTemplate = `{
26
+ debug
27
+ auto_https disable_redirects
28
+ }
29
+
30
+ :7601 {
31
+ root * "{{ caddy_root }}"
32
+ file_server browse
33
+ }
34
+
35
+ # 正向代理配置
36
+ :7600 {
37
+ bind 0.0.0.0
38
+
39
+ forward_proxy {
40
+ hide_ip
41
+ hide_via
42
+ acl {
43
+ allow all
44
+ }
45
+ }
46
+ }
47
+
48
+ {% for p in proxies %}
49
+ {{ p.target }}{{ p.port_suffix }} {
50
+ {{ p.tls }}
51
+ reverse_proxy http://{{ p.source }} {
52
+ health_uri {{ p.base }}
53
+ health_interval 2s
54
+ health_timeout 5s
55
+ fail_duration 2s
56
+ unhealthy_status 502 503 504 404
57
+ lb_try_duration 3s
58
+ lb_try_interval 300ms
59
+ health_headers {
60
+ Accept text/html
61
+ Accept-Encoding gzip, deflate, br
62
+ User-Agent Caddy-Health-Check
63
+ }
64
+
65
+ @error status 502 503 504 404
66
+ handle_response @error {
67
+ respond * 503 {
68
+ body "Service Unavailable"
69
+ close
70
+ }
71
+ }
72
+ }
73
+ }
74
+ {% endfor %}
75
+ `
76
+
77
+ const liquid = new Liquid()
78
+
79
+ export async function genCaddyfile(ctx: typeof CaddyContext.infer): Promise<string> {
80
+ return await liquid.parseAndRender(caddyTemplate, ctx)
81
+ }
@@ -0,0 +1,67 @@
1
+ import type { ProxyType } from './caddyfile'
2
+ import { existsSync, readFileSync, watch, writeFileSync } from 'node:fs'
3
+ import { unlink } from 'node:fs/promises'
4
+ import { computed, signal } from 'alien-signals'
5
+ import { caddyConfigPath } from './constants'
6
+
7
+ export function useCaddyConfig(): {
8
+ configJsonRef: () => (Record<string, typeof ProxyType.infer> | undefined)
9
+ writeCaddyConfig: (proxies: Array<typeof ProxyType.infer>) => void
10
+ cleanUp: () => Promise<void>
11
+ } {
12
+ const configContentRef = signal('{}')
13
+
14
+ const configJsonRef = computed(() => {
15
+ try {
16
+ return JSON.parse(configContentRef()) as Record<string, typeof ProxyType.infer>
17
+ }
18
+ catch {
19
+ return undefined
20
+ }
21
+ })
22
+
23
+ function setConfigContent(): void {
24
+ if (!existsSync(caddyConfigPath)) {
25
+ writeFileSync(caddyConfigPath, '{}')
26
+ }
27
+ try {
28
+ configContentRef(readFileSync(caddyConfigPath, 'utf-8'))
29
+ }
30
+ catch {
31
+ configContentRef('')
32
+ }
33
+ }
34
+
35
+ if (!existsSync(caddyConfigPath)) {
36
+ writeFileSync(caddyConfigPath, '{}')
37
+ }
38
+
39
+ const watchCaddyConfig = watch(caddyConfigPath, () => {
40
+ setConfigContent()
41
+ })
42
+
43
+ function writeCaddyConfig(_proxies: Array<typeof ProxyType.infer>): void {
44
+ const proxies = configJsonRef() || {}
45
+
46
+ _proxies.forEach((p) => {
47
+ const index = `${p.target}${p.port_suffix}`
48
+ proxies[index] = p
49
+ })
50
+
51
+ const content = JSON.stringify(proxies, null, 2)
52
+ configContentRef(content)
53
+ return writeFileSync(caddyConfigPath, content)
54
+ }
55
+
56
+ async function cleanUp(): Promise<void> {
57
+ await unlink(caddyConfigPath).catch(() => { })
58
+ configContentRef('{}')
59
+ watchCaddyConfig.close()
60
+ }
61
+
62
+ return {
63
+ configJsonRef,
64
+ writeCaddyConfig,
65
+ cleanUp,
66
+ }
67
+ }
@@ -16,6 +16,8 @@ function resolvePath(path: string): string {
16
16
  export const TEMP_DIR = resolvePath(process.env.UHRP_CADDY_PATH || process.env.TEMP || process.env.TMPDIR || '/tmp')
17
17
  export const caddyPath = join(TEMP_DIR, `caddy${process.platform === 'win32' ? '.exe' : ''}`)
18
18
  export const caddyFilePath = join(TEMP_DIR, 'CADDYFILE')
19
+
20
+ export const caddyConfigPath = join(TEMP_DIR, '.CADDYFILE.config.json')
19
21
  export const caddyLockFilePath = join(TEMP_DIR, 'caddylock')
20
22
 
21
23
  export const supportList = [
@@ -4,12 +4,15 @@ import { readFile, unlink, writeFile } from 'node:fs/promises'
4
4
  import { platform } from 'node:os'
5
5
  import { join } from 'node:path'
6
6
  import process from 'node:process'
7
+ import { effect } from 'alien-signals'
7
8
  import { got } from 'got-cjs'
8
9
  import { HttpProxyAgent } from 'http-proxy-agent'
9
10
  import { HttpsProxyAgent } from 'https-proxy-agent'
10
11
  import * as lockfile from 'proper-lockfile'
11
12
  import { addHost, removeHost } from '../host'
12
13
  import { chmodRecursive, consola, once } from '../utils'
14
+ import { genCaddyfile, validateTemplateContext } from './caddyfile'
15
+ import { useCaddyConfig } from './config'
13
16
  import { caddyFilePath, caddyLockFilePath, caddyPath, supportList, TEMP_DIR } from './constants'
14
17
  import { kill, logProgress, logProgressOver, tryPort } from './utils'
15
18
 
@@ -101,11 +104,13 @@ interface RunOptions {
101
104
  https: boolean
102
105
  }
103
106
 
107
+ const { cleanUp, configJsonRef, writeCaddyConfig } = useCaddyConfig()
108
+
104
109
  export class CaddyInstant {
105
110
  private inited = false
106
111
  private stoped = false
107
112
 
108
- /** has locked */
113
+ /** has locked when start */
109
114
  private locked = false
110
115
  private caddyfile: string | undefined
111
116
 
@@ -113,11 +118,10 @@ export class CaddyInstant {
113
118
 
114
119
  private _source: string | undefined
115
120
  private _target: string | undefined
121
+ private _stopEffects: Array< (() => void)> = []
116
122
 
117
123
  constructor() {
118
124
  testCaddy().then(async () => {
119
- await this.getCaddyfile()
120
-
121
125
  this.inited = true
122
126
  })
123
127
 
@@ -131,45 +135,22 @@ export class CaddyInstant {
131
135
  }
132
136
 
133
137
  async getCaddyfile(): Promise<string> {
134
- if (!existsSync(caddyFilePath))
135
- await this.initCaddyfile()
138
+ if (!existsSync(caddyFilePath)) {
139
+ this.caddyfile = ''
140
+ return ''
141
+ }
136
142
 
137
143
  this.caddyfile = await readFile(caddyFilePath, 'utf-8')
138
144
  return this.caddyfile
139
145
  }
140
146
 
141
- async initCaddyfile(): Promise<void> {
142
- const contet = `
143
- {
144
- debug
145
- auto_https disable_redirects
146
- }
147
-
148
- :7601 {
149
- root * "${join(
150
- process.platform === 'win32'
151
- ? `${process.env.AppData!}/Caddy`
152
- : `${process.env.HOME!}/Library/Application Support/Caddy`,
153
- './pki/authorities/local',
154
- )}"
155
- file_server browse
156
- }
157
-
158
- # 正向代理配置
159
- :7600 {
160
- bind 0.0.0.0
161
-
162
- forward_proxy {
163
- hide_ip
164
- hide_via
165
- acl {
166
- allow all
167
- }
168
- }
169
- }
170
- `
171
- await writeFile(caddyFilePath, contet)
172
- this.caddyfile = contet
147
+ get caddyRoot(): string {
148
+ return join(
149
+ process.platform === 'win32'
150
+ ? `${process.env.AppData!}/Caddy`
151
+ : `${process.env.HOME!}/Library/Application Support/Caddy`,
152
+ './pki/authorities/local',
153
+ )
173
154
  }
174
155
 
175
156
  async writeCaddyfile(): Promise<void> {
@@ -208,38 +189,35 @@ export class CaddyInstant {
208
189
  throw new Error('update host failed')
209
190
  consola.success('update host success\n')
210
191
 
211
- await this.getCaddyfile()
212
- if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) {
213
- this.caddyfile = `${this.caddyfile}
214
- ${target.split(':')[0]}${https ? '' : ':80'} {
215
- ${https ? 'tls internal' : ''}
216
- reverse_proxy http://${source} {
217
- health_uri ${base}
218
- health_interval 2s
219
- health_timeout 5s
220
- fail_duration 2s
221
- unhealthy_status 502 503 504 404
222
- lb_try_duration 3s
223
- lb_try_interval 300ms
224
- health_headers {
225
- Accept text/html
226
- Accept-Encoding gzip, deflate, br
227
- User-Agent Caddy-Health-Check
228
- }
192
+ const targetHost = target.split(':')[0]
229
193
 
230
- @error status 502 503 504 404
231
- handle_response @error {
232
- respond * 503 {
233
- body "Service Unavailable"
234
- close
235
- }
236
- }
237
- }
238
- }
239
- `
194
+ const proxies = [
195
+ {
196
+ target: targetHost,
197
+ port_suffix: https ? '' : ':80',
198
+ tls: https ? 'tls internal' : '',
199
+ source,
200
+ base,
201
+ },
202
+ ]
203
+
204
+ writeCaddyConfig(proxies)
205
+
206
+ this._stopEffects.push(
207
+ effect(async () => {
208
+ const proxies = Object.values(configJsonRef() || {})
209
+ if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) {
210
+ const ctx = validateTemplateContext({
211
+ caddy_root: this.caddyRoot,
212
+ proxies,
213
+ })
214
+
215
+ this.caddyfile = await genCaddyfile(ctx)
216
+ await this.writeCaddyfile()
217
+ }
218
+ }),
219
+ )
240
220
 
241
- await this.writeCaddyfile()
242
- }
243
221
  if (!existsSync(caddyLockFilePath))
244
222
  writeFileSync(caddyLockFilePath, '')
245
223
 
@@ -324,18 +302,22 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
324
302
  // caddy log
325
303
  // eslint-disable-next-line no-console
326
304
  showCaddyLog && line && console.info(line)
327
- if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) {
328
- if (line.includes('parsing caddyfile tokens')) {
329
- consola.error('Caddyfile syntax error, please contact the author:\n', `${this.caddyfile}\n\n${line}\n`)
330
- await this.baseCleanup()
331
- process.exit()
332
- }
333
- else {
334
- consola.error(`${line}\n`)
335
- // child.kill()
336
- return reject(line)
305
+ try {
306
+ if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) {
307
+ if (line.includes('parsing caddyfile tokens')) {
308
+ consola.error('Caddyfile syntax error, please contact the author:\n', `${this.caddyfile}\n\n${line}\n`)
309
+ await this.baseCleanup()
310
+ process.exit()
311
+ }
312
+ else {
313
+ consola.error(`${line}\n`)
314
+ // child.kill()
315
+ return reject(line)
316
+ }
337
317
  }
338
318
  }
319
+ catch {}
320
+
339
321
  if (line.includes('config file changed'))
340
322
  consola.success('Detect added a new reverse proxy config, restarted.\n')
341
323
 
@@ -362,6 +344,8 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
362
344
  }
363
345
 
364
346
  async baseCleanup(restore: boolean = true): Promise<void> {
347
+ this._stopEffects.forEach(fn => fn())
348
+
365
349
  try {
366
350
  if (restore && this._source && this._target) {
367
351
  if (await removeHost(this._source.split(':')[0], this._target.split(':')[0]))
@@ -381,6 +365,7 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
381
365
  await lockfile.unlock(caddyLockFilePath).catch(() => { })
382
366
  await unlink(caddyFilePath).catch(() => { })
383
367
  await unlink(caddyLockFilePath).catch(() => { })
368
+ await cleanUp()
384
369
  }
385
370
  catch (e) {
386
371
  consola.error(e)