unplugin-https-reverse-proxy 2.0.2 → 2.0.3-beta.2

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-E6C23MZG.js";
3
+ } from "./chunk-XGTVAT6M.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.2",
4
+ "version": "2.0.3-beta.2",
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,78 @@
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
+ health_check: 'boolean',
11
+ })
12
+
13
+ const CaddyContext = type({
14
+ caddy_root: 'string',
15
+ proxies: ProxyType.array(),
16
+ })
17
+
18
+ export function validateTemplateContext(ctx: unknown): typeof CaddyContext.infer {
19
+ const out = CaddyContext(ctx as any)
20
+ if (out instanceof type.errors)
21
+ throw new Error(out.summary)
22
+
23
+ return ctx as typeof CaddyContext.infer
24
+ }
25
+
26
+ export const caddyTemplate = `{
27
+ debug
28
+ auto_https disable_redirects
29
+ }
30
+
31
+ :7601 {
32
+ root * "{{ caddy_root }}"
33
+ file_server browse
34
+ }
35
+
36
+ # 正向代理配置
37
+ :7600 {
38
+ bind 0.0.0.0
39
+
40
+ forward_proxy {
41
+ hide_ip
42
+ hide_via
43
+ acl {
44
+ allow all
45
+ }
46
+ }
47
+ }
48
+
49
+ {% for p in proxies %}
50
+ {{ p.target }}{{ p.port_suffix }} {
51
+ {{ p.tls }}
52
+ reverse_proxy http://{{ p.source }} {% if p.health_check %} {
53
+ health_uri {{ p.base }}
54
+ health_interval 2s
55
+ health_timeout 5s
56
+ fail_duration 2s
57
+ unhealthy_status 502 503 504 404
58
+ lb_try_duration 3s
59
+ lb_try_interval 300ms
60
+
61
+ @error status 502 503 504 404
62
+ handle_response @error {
63
+ respond * 503 {
64
+ body "Service Unavailable"
65
+ close
66
+ }
67
+ }
68
+ }
69
+ {% endif %}
70
+ }
71
+ {% endfor %}
72
+ `
73
+
74
+ const liquid = new Liquid()
75
+
76
+ export async function genCaddyfile(ctx: typeof CaddyContext.infer): Promise<string> {
77
+ return await liquid.parseAndRender(caddyTemplate, ctx)
78
+ }
@@ -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
 
@@ -99,13 +102,16 @@ interface RunOptions {
99
102
  base: string
100
103
  showCaddyLog: boolean
101
104
  https: boolean
105
+ health_check?: boolean
102
106
  }
103
107
 
108
+ const { cleanUp, configJsonRef, writeCaddyConfig } = useCaddyConfig()
109
+
104
110
  export class CaddyInstant {
105
111
  private inited = false
106
112
  private stoped = false
107
113
 
108
- /** has locked */
114
+ /** has locked when start */
109
115
  private locked = false
110
116
  private caddyfile: string | undefined
111
117
 
@@ -113,11 +119,10 @@ export class CaddyInstant {
113
119
 
114
120
  private _source: string | undefined
115
121
  private _target: string | undefined
122
+ private _stopEffects: Array< (() => void)> = []
116
123
 
117
124
  constructor() {
118
125
  testCaddy().then(async () => {
119
- await this.getCaddyfile()
120
-
121
126
  this.inited = true
122
127
  })
123
128
 
@@ -131,45 +136,22 @@ export class CaddyInstant {
131
136
  }
132
137
 
133
138
  async getCaddyfile(): Promise<string> {
134
- if (!existsSync(caddyFilePath))
135
- await this.initCaddyfile()
139
+ if (!existsSync(caddyFilePath)) {
140
+ this.caddyfile = ''
141
+ return ''
142
+ }
136
143
 
137
144
  this.caddyfile = await readFile(caddyFilePath, 'utf-8')
138
145
  return this.caddyfile
139
146
  }
140
147
 
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
148
+ get caddyRoot(): string {
149
+ return join(
150
+ process.platform === 'win32'
151
+ ? `${process.env.AppData!}/Caddy`
152
+ : `${process.env.HOME!}/Library/Application Support/Caddy`,
153
+ './pki/authorities/local',
154
+ )
173
155
  }
174
156
 
175
157
  async writeCaddyfile(): Promise<void> {
@@ -196,6 +178,7 @@ export class CaddyInstant {
196
178
  base = '/',
197
179
  showCaddyLog = false,
198
180
  https = false,
181
+ health_check = true,
199
182
  } = options || {}
200
183
 
201
184
  this._source = source
@@ -208,38 +191,36 @@ export class CaddyInstant {
208
191
  throw new Error('update host failed')
209
192
  consola.success('update host success\n')
210
193
 
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
- }
194
+ const targetHost = target.split(':')[0]
229
195
 
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
- `
196
+ const proxies = [
197
+ {
198
+ target: targetHost,
199
+ port_suffix: https ? '' : ':80',
200
+ tls: https ? 'tls internal' : '',
201
+ source,
202
+ base,
203
+ health_check,
204
+ },
205
+ ]
206
+
207
+ writeCaddyConfig(proxies)
208
+
209
+ this._stopEffects.push(
210
+ effect(async () => {
211
+ const proxies = Object.values(configJsonRef() || {})
212
+ if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) {
213
+ const ctx = validateTemplateContext({
214
+ caddy_root: this.caddyRoot,
215
+ proxies,
216
+ })
217
+
218
+ this.caddyfile = await genCaddyfile(ctx)
219
+ await this.writeCaddyfile()
220
+ }
221
+ }),
222
+ )
240
223
 
241
- await this.writeCaddyfile()
242
- }
243
224
  if (!existsSync(caddyLockFilePath))
244
225
  writeFileSync(caddyLockFilePath, '')
245
226
 
@@ -366,6 +347,8 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
366
347
  }
367
348
 
368
349
  async baseCleanup(restore: boolean = true): Promise<void> {
350
+ this._stopEffects.forEach(fn => fn())
351
+
369
352
  try {
370
353
  if (restore && this._source && this._target) {
371
354
  if (await removeHost(this._source.split(':')[0], this._target.split(':')[0]))
@@ -385,6 +368,7 @@ ${target.split(':')[0]}${https ? '' : ':80'} {
385
368
  await lockfile.unlock(caddyLockFilePath).catch(() => { })
386
369
  await unlink(caddyFilePath).catch(() => { })
387
370
  await unlink(caddyLockFilePath).catch(() => { })
371
+ await cleanUp()
388
372
  }
389
373
  catch (e) {
390
374
  consola.error(e)
package/src/types.ts CHANGED
@@ -5,4 +5,5 @@ export interface Options {
5
5
  showCaddyLog?: boolean
6
6
  /** default `false` */
7
7
  https?: boolean
8
+ health_check?: boolean
8
9
  }