From 9aa43b529c3431105342a96823302f9b2ffa7dec Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Wed, 30 Jul 2025 13:43:39 +0800 Subject: [PATCH 01/15] feat: remove postinstall https://github.com/pnpm/pnpm/pull/8897 --- README.md | 4 - eslint.config.js | 1 - package.json | 2 - scripts/postinstall.cjs | 314 ---------------------------------------- scripts/postinstall.ts | 19 --- tsup.config.ts | 13 +- 6 files changed, 1 insertion(+), 352 deletions(-) delete mode 100644 scripts/postinstall.cjs delete mode 100644 scripts/postinstall.ts diff --git a/README.md b/README.md index e1ad2a0..61c57a0 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,7 @@ UHRP_CADDY_PATH=~/tmp/.uhrp ``` ```bash -# not auto install caddy when postinstall npm i unplugin-https-reverse-proxy --save-dev - -# auto install -UHRP_AUTO_INSTALL_CADDY=true npm i unplugin-https-reverse-proxy --save-dev ``` ```ts diff --git a/eslint.config.js b/eslint.config.js index 452d056..e9e8e85 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,7 +7,6 @@ export default antfu({ ignores: [ 'docs/**/*', 'examples/**/*', - 'scripts/postinstall.cjs', ], }, ]) diff --git a/package.json b/package.json index 9fbc57f..a9c1d19 100644 --- a/package.json +++ b/package.json @@ -75,11 +75,9 @@ }, "files": [ "dist", - "scripts/postinstall.cjs", "src" ], "scripts": { - "postinstall": "node scripts/postinstall.cjs", "build": "tsup", "dev": "tsup --watch src", "build:fix": "tsx scripts/postbuild.ts", diff --git a/scripts/postinstall.cjs b/scripts/postinstall.cjs deleted file mode 100644 index 5820aa1..0000000 --- a/scripts/postinstall.cjs +++ /dev/null @@ -1,314 +0,0 @@ -"use strict"; -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __getProtoOf = Object.getPrototypeOf; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, - mod -)); - -// scripts/postinstall.ts -var import_node_fs3 = require("fs"); -var import_node_process5 = __toESM(require("process"), 1); - -// src/caddy/index.ts -var import_node_child_process4 = require("child_process"); -var import_node_fs2 = require("fs"); -var import_promises2 = require("fs/promises"); -var import_node_os3 = require("os"); -var import_node_path3 = require("path"); -var import_node_process4 = __toESM(require("process"), 1); -var import_got_cjs = require("got-cjs"); -var import_http_proxy_agent = require("http-proxy-agent"); -var import_https_proxy_agent = require("https-proxy-agent"); -var lockfile = __toESM(require("proper-lockfile"), 1); - -// src/host/index.ts -var import_node_child_process2 = require("child_process"); -var import_node_fs = require("fs"); -var import_node_process2 = __toESM(require("process"), 1); -var import_node_util = require("util"); -var import_hostile = __toESM(require("hostile"), 1); - -// src/utils.ts -var import_node_child_process = require("child_process"); -var import_promises = require("fs/promises"); -var import_node_os = require("os"); -var import_node_path = __toESM(require("path"), 1); -var import_node_process = __toESM(require("process"), 1); -var import_consola = require("consola"); -var import_picocolors = __toESM(require("picocolors"), 1); -var import_qrcode_terminal = __toESM(require("qrcode-terminal"), 1); -var consola = (0, import_consola.createConsola)({ - level: 3 -}).withTag("reverse-proxy"); -function once(fn) { - let called = false; - return (...args) => { - if (called) - return; - called = true; - return fn(...args); - }; -} - -// src/host/index.ts -var _setHost = (0, import_node_util.promisify)(import_hostile.default.set); -var _removeHost = (0, import_node_util.promisify)(import_hostile.default.remove); - -// src/caddy/constants.ts -var import_node_os2 = __toESM(require("os"), 1); -var import_node_path2 = require("path"); -var import_node_process3 = __toESM(require("process"), 1); -var import_dotenv_flow = __toESM(require("dotenv-flow"), 1); -import_dotenv_flow.default.config({ - silent: true -}); -function resolvePath(path2) { - if (path2.startsWith("~")) - return (0, import_node_path2.join)(import_node_os2.default.homedir() || "", path2.slice(1)); - return path2; -} -var TEMP_DIR = resolvePath(import_node_process3.default.env.UHRP_CADDY_PATH || import_node_process3.default.env.TEMP || import_node_process3.default.env.TMPDIR || "/tmp"); -var caddyPath = (0, import_node_path2.join)(TEMP_DIR, `caddy${import_node_process3.default.platform === "win32" ? ".exe" : ""}`); -var caddyFilePath = (0, import_node_path2.join)(TEMP_DIR, "CADDYFILE"); -var caddyLockFilePath = (0, import_node_path2.join)(TEMP_DIR, "caddylock"); -var supportList = [ - { - platform: "dragonfly", - arch: "amd64" - }, - { - platform: "freebsd", - arch: "amd64" - }, - { - platform: "freebsd", - arch: "arm-6" - }, - { - platform: "freebsd", - arch: "arm-7" - }, - { - platform: "freebsd", - arch: "arm64" - }, - { - platform: "linux", - arch: "amd64" - }, - { - platform: "linux", - arch: "arm-5" - }, - { - platform: "linux", - arch: "arm-6" - }, - { - platform: "linux", - arch: "arm-7" - }, - { - platform: "linux", - arch: "arm64" - }, - { - platform: "linux", - arch: "mips" - }, - { - platform: "linux", - arch: "mips64" - }, - { - platform: "linux", - arch: "mips64le" - }, - { - platform: "linux", - arch: "mipsle" - }, - { - platform: "linux", - arch: "ppc64" - }, - { - platform: "linux", - arch: "ppc64le" - }, - { - platform: "linux", - arch: "s390x" - }, - { - platform: "darwin", - arch: "amd64" - }, - { - platform: "darwin", - arch: "arm64" - }, - { - platform: "openbsd", - arch: "amd64" - }, - { - platform: "openbsd", - arch: "arm-6" - }, - { - platform: "openbsd", - arch: "arm-7" - }, - { - platform: "openbsd", - arch: "arm64" - }, - { - platform: "windows", - arch: "amd64" - }, - { - platform: "windows", - arch: "arm-6" - }, - { - platform: "windows", - arch: "arm-7" - }, - { - platform: "windows", - arch: "arm64" - } -]; - -// src/caddy/utils.ts -var import_node_child_process3 = require("child_process"); -var import_node_net = require("net"); -var import_ora = __toESM(require("ora"), 1); -var spinner; -function getNumber(number) { - if (number <= 0) - return "0%"; - else if (number < 100) - return `${number.toFixed(2)}%`; - else - return "100%"; -} -function logProgress(percent) { - if (!spinner) { - consola.log(""); - spinner = (0, import_ora.default)({ - text: "Start Download Caddy: 0%", - indent: 2 - }); - spinner.start(); - } else { - spinner.text = `Download Caddy: ${getNumber(Number(percent) * 100)}`; - } -} -function logProgressOver() { - if (!spinner) { - spinner = (0, import_ora.default)({ - text: "Start Download Caddy: 100%", - indent: 2 - }); - spinner.start(); - } else { - spinner.succeed(); - consola.log(""); - } -} - -// src/caddy/index.ts -async function download() { - if (await testCaddy()) - return caddyPath; - return new Promise((resolve, reject) => { - if (!(0, import_node_fs2.existsSync)(TEMP_DIR)) { - (0, import_node_fs2.mkdirSync)(TEMP_DIR, { recursive: true }); - (0, import_node_fs2.chmodSync)(TEMP_DIR, 511); - } - const file = (0, import_node_fs2.createWriteStream)(caddyPath); - let _platform = (0, import_node_os3.platform)().toString(); - _platform === "win32" && (_platform = "windows"); - let _arch = import_node_process4.default.arch.toString(); - _arch === "x64" && (_arch = "amd64"); - const support = supportList.find((item) => item.platform === _platform && item.arch === _arch); - if (!support) - return reject(new Error("not support")); - const dowmloadLink = `https://caddyserver.com/api/download?os=${support.platform}&arch=${support.arch}&p=github.com%2Fcaddyserver%2Fforwardproxy`; - (0, import_node_fs2.existsSync)(caddyPath) && (0, import_node_fs2.unlinkSync)(caddyPath); - const httpProxy = import_node_process4.default.env.HTTP_PROXY || import_node_process4.default.env.http_proxy; - const httpsProxy = import_node_process4.default.env.HTTPS_PROXY || import_node_process4.default.env.https_proxy; - const httpAgent = httpProxy ? new import_http_proxy_agent.HttpProxyAgent(httpProxy) : void 0; - const httpsAgent = httpsProxy ? new import_https_proxy_agent.HttpsProxyAgent(httpsProxy) : void 0; - const chmodCaddyOnce = once(() => { - (0, import_node_fs2.chmodSync)(caddyPath, 511); - }); - import_got_cjs.got.stream(dowmloadLink, { - agent: { - http: httpAgent, - https: httpsAgent - } - }).on("downloadProgress", (progress) => { - logProgress(progress.percent); - chmodCaddyOnce(); - }).pipe(file).on("finish", () => { - logProgressOver(); - if (import_node_process4.default.platform === "win32") - return resolve(caddyPath); - (0, import_node_fs2.chmodSync)(caddyPath, 511); - resolve(caddyPath); - }).on("error", (err) => { - reject(err); - }); - }); -} -function testCaddy() { - return new Promise((resolve, reject) => { - if (!(0, import_node_fs2.existsSync)(caddyPath)) - return resolve(false); - (0, import_node_fs2.chmodSync)(caddyPath, 511); - const child = import_node_process4.default.platform === "win32" ? (0, import_node_child_process4.spawn)(caddyPath, []) : (0, import_node_child_process4.spawn)("sudo", ["-E", caddyPath]); - child.on("close", () => { - return resolve(false); - }); - child.on("error", (err) => { - return reject(err); - }); - child.stdout.on("data", (_data) => { - return resolve(true); - }); - }); -} - -// scripts/postinstall.ts -async function run() { - try { - if (import_node_process5.default.env.UHRP_AUTO_INSTALL_CADDY) { - (0, import_node_fs3.existsSync)(caddyPath) && (0, import_node_fs3.unlinkSync)(caddyPath); - await download(); - } - } catch (e) { - consola.error(e.code); - } -} -run(); diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts deleted file mode 100644 index 5f1a9df..0000000 --- a/scripts/postinstall.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { existsSync, unlinkSync } from 'node:fs' -import process from 'node:process' -import { download } from '../src/caddy' -import { caddyPath } from '../src/caddy/constants' -import { consola } from '../src/utils' - -async function run() { - try { - if (process.env.UHRP_AUTO_INSTALL_CADDY) { - existsSync(caddyPath) && unlinkSync(caddyPath) - await download() - } - } - catch (e: any) { - consola.error(e.code) - } -} - -run() diff --git a/tsup.config.ts b/tsup.config.ts index 7a828ee..38681c5 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -12,16 +12,5 @@ export default [ dts: true, onSuccess: 'npm run build:fix', external: ['proper-lockfile'], - }, - { - entryPoints: [ - 'scripts/postinstall.ts', - ], - target: 'node16', - platform: 'node', - outDir: 'scripts', - format: ['cjs'], - onSuccess: 'npm run build:fix', - external: ['proper-lockfile'], - }, + } ] satisfies Options[] From 3022ab6676d37bce4d960609a3979130dac6cf59 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Wed, 30 Jul 2025 13:45:03 +0800 Subject: [PATCH 02/15] chore: fix lint error --- tsup.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsup.config.ts b/tsup.config.ts index 38681c5..4024e4f 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -12,5 +12,5 @@ export default [ dts: true, onSuccess: 'npm run build:fix', external: ['proper-lockfile'], - } + }, ] satisfies Options[] From 01201c00b64b432d073c060088163f28a92351e8 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 2 Sep 2025 19:20:43 +0800 Subject: [PATCH 03/15] refactor: render the Caddyfile using `liquidjs` and validate it with `arktype` --- package.json | 2 + pnpm-lock.yaml | 43 +++++++++++++++++++++ src/caddy/caddyfile.ts | 80 +++++++++++++++++++++++++++++++++++++++ src/caddy/index.ts | 85 +++++++++++++++--------------------------- 4 files changed, 155 insertions(+), 55 deletions(-) create mode 100644 src/caddy/caddyfile.ts diff --git a/package.json b/package.json index a9c1d19..dafca7a 100644 --- a/package.json +++ b/package.json @@ -154,11 +154,13 @@ "@types/proper-lockfile": "^4.1.4", "@types/qrcode-terminal": "^0.12.2", "@vue/cli": "^5.0.8", + "arktype": "^2.1.20", "bumpp": "^9.11.1", "chalk": "^5.4.1", "cross-env": "^7.0.3", "eslint": "^9.20.0", "fast-glob": "^3.3.3", + "liquidjs": "^10.21.1", "nodemon": "^3.1.9", "proper-lockfile": "^4.1.2", "rimraf": "^5.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8fdf5d..b450361 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: '@vue/cli': specifier: ^5.0.8 version: 5.0.8(encoding@0.1.13) + arktype: + specifier: ^2.1.20 + version: 2.1.20 bumpp: specifier: ^9.11.1 version: 9.11.1 @@ -93,6 +96,9 @@ importers: fast-glob: specifier: ^3.3.3 version: 3.3.3 + liquidjs: + specifier: ^10.21.1 + version: 10.21.1 nodemon: specifier: ^3.1.9 version: 3.1.9 @@ -329,6 +335,12 @@ packages: '@apollographql/graphql-playground-html@1.6.29': resolution: {integrity: sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==} + '@ark/schema@0.46.0': + resolution: {integrity: sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==} + + '@ark/util@0.46.0': + resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} + '@babel/code-frame@7.22.13': resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} @@ -3481,6 +3493,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arktype@2.1.20: + resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + arr-diff@4.0.0: resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} engines: {node: '>=0.10.0'} @@ -4204,6 +4219,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@2.17.1: resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==} @@ -7082,6 +7101,11 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + liquidjs@10.21.1: + resolution: {integrity: sha512-NZXmCwv3RG5nire3fmIn9HsOyJX3vo+ptp0yaXUHAMzSNBhx74Hm+dAGJvscUA6lNqbLuYfXgNavRQ9UbUJhQQ==} + engines: {node: '>=14'} + hasBin: true + listhen@1.5.6: resolution: {integrity: sha512-gTpEJhT5L85L0bFgmu+Boqu5rP4DwDtEb4Exq5gdQUxWRwx4jbzdInZkmyLONo5EwIcQB0k7ZpWlpCDPdL77EQ==} hasBin: true @@ -9337,6 +9361,7 @@ packages: rspack-resolver@1.2.2: resolution: {integrity: sha512-Fwc19jMBA3g+fxDJH2B4WxwZjE0VaaOL7OX/A4Wn5Zv7bOD/vyPZhzXfaO73Xc2GAlfi96g5fGUa378WbIGfFw==} + deprecated: Please migrate to the brand new `@rspack/resolver` or `unrs-resolver` instead rss-parser@3.13.0: resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==} @@ -9685,6 +9710,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -11450,6 +11476,12 @@ snapshots: dependencies: xss: 1.0.14 + '@ark/schema@0.46.0': + dependencies: + '@ark/util': 0.46.0 + + '@ark/util@0.46.0': {} + '@babel/code-frame@7.22.13': dependencies: '@babel/highlight': 7.22.20 @@ -15288,6 +15320,11 @@ snapshots: argparse@2.0.1: {} + arktype@2.1.20: + dependencies: + '@ark/schema': 0.46.0 + '@ark/util': 0.46.0 + arr-diff@4.0.0: {} arr-flatten@1.1.0: {} @@ -16237,6 +16274,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@10.0.1: {} + commander@2.17.1: {} commander@2.19.0: {} @@ -19505,6 +19544,10 @@ snapshots: lines-and-columns@1.2.4: {} + liquidjs@10.21.1: + dependencies: + commander: 10.0.1 + listhen@1.5.6: dependencies: '@parcel/watcher': 2.3.0 diff --git a/src/caddy/caddyfile.ts b/src/caddy/caddyfile.ts new file mode 100644 index 0000000..adb8c86 --- /dev/null +++ b/src/caddy/caddyfile.ts @@ -0,0 +1,80 @@ +import { type } from 'arktype' + +const ProxyType = type({ + target: 'string', + port_suffix: 'string', + tls: 'string', + source: 'string', + base: 'string', +}) + +const CaddyContext = type({ + include_base: 'boolean', + caddy_root: 'string?', + + proxies: ProxyType.array(), +}).narrow((data, ctx) => { + if (data.include_base && (!data.caddy_root || data.caddy_root.length === 0)) { + return ctx.reject({ expected: 'non-empty caddy_root when include_base is true', actual: String(data.caddy_root) }) + } + return true +}) + +export function validateTemplateContext(ctx: unknown): void { + const out = CaddyContext(ctx as any) + if (out instanceof type.errors) + throw new Error(out.summary) +} + +export const caddyTemplate = `{% if include_base %}{ + debug + auto_https disable_redirects +} + +:7601 { + root * "{{ caddy_root }}" + file_server browse +} + +# 正向代理配置 +:7600 { + bind 0.0.0.0 + + forward_proxy { + hide_ip + hide_via + acl { + allow all + } + } +} +{% endif %} + +{% for p in proxies %} +{{ p.target }}{{ p.port_suffix }} { + {{ p.tls }} + reverse_proxy http://{{ p.source }} { + health_uri {{ p.base }} + health_interval 2s + health_timeout 5s + fail_duration 2s + unhealthy_status 502 503 504 404 + lb_try_duration 3s + lb_try_interval 300ms + health_headers { + Accept text/html + Accept-Encoding gzip, deflate, br + User-Agent Caddy-Health-Check + } + + @error status 502 503 504 404 + handle_response @error { + respond * 503 { + body "Service Unavailable" + close + } + } + } +} +{% endfor %} +` diff --git a/src/caddy/index.ts b/src/caddy/index.ts index 47b0eed..3db0f87 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -7,12 +7,16 @@ import process from 'node:process' import { got } from 'got-cjs' import { HttpProxyAgent } from 'http-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent' +import { Liquid } from 'liquidjs' import * as lockfile from 'proper-lockfile' import { addHost, removeHost } from '../host' import { chmodRecursive, consola, once } from '../utils' +import { caddyTemplate, validateTemplateContext } from './caddyfile' import { caddyFilePath, caddyLockFilePath, caddyPath, supportList, TEMP_DIR } from './constants' import { kill, logProgress, logProgressOver, tryPort } from './utils' +const liquid = new Liquid() + export async function download(): Promise { if (await testCaddy()) return caddyPath @@ -139,35 +143,16 @@ export class CaddyInstant { } async initCaddyfile(): Promise { - const contet = ` -{ - debug - auto_https disable_redirects -} - -:7601 { - root * "${join( - process.platform === 'win32' - ? `${process.env.AppData!}/Caddy` - : `${process.env.HOME!}/Library/Application Support/Caddy`, - './pki/authorities/local', - )}" - file_server browse -} - -# 正向代理配置 -:7600 { - bind 0.0.0.0 - - forward_proxy { - hide_ip - hide_via - acl { - allow all - } - } -} -` + const caddyRoot = join( + process.platform === 'win32' + ? `${process.env.AppData!}/Caddy` + : `${process.env.HOME!}/Library/Application Support/Caddy`, + './pki/authorities/local', + ) + + const ctx = { include_base: true, caddy_root: caddyRoot, proxies: [] } + validateTemplateContext(ctx) + const contet = await liquid.parseAndRender(caddyTemplate, ctx) await writeFile(caddyFilePath, contet) this.caddyfile = contet } @@ -210,36 +195,26 @@ export class CaddyInstant { await this.getCaddyfile() if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) { - this.caddyfile = `${this.caddyfile} -${target.split(':')[0]}${https ? '' : ':80'} { - ${https ? 'tls internal' : ''} - reverse_proxy http://${source} { - health_uri ${base} - health_interval 2s - health_timeout 5s - fail_duration 2s - unhealthy_status 502 503 504 404 - lb_try_duration 3s - lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } - - @error status 502 503 504 404 - handle_response @error { - respond * 503 { - body "Service Unavailable" - close + const targetHost = target.split(':')[0] + const ctx = { + include_base: false, + proxies: [ + { + target: targetHost, + port_suffix: https ? '' : ':80', + tls: https ? 'tls internal' : '', + source, + base, + }, + ], } - } - } -} -` + validateTemplateContext(ctx) + const block = await liquid.parseAndRender(caddyTemplate, ctx) + this.caddyfile = `${this.caddyfile}\n${block}` await this.writeCaddyfile() } + if (!existsSync(caddyLockFilePath)) writeFileSync(caddyLockFilePath, '') From ec311571769950e8380478cd172adb1192269be6 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 2 Sep 2025 19:29:38 +0800 Subject: [PATCH 04/15] chore: fix test and playground error --- examples/vite/package.json | 1 + test/caddy/index.test.ts | 54 +++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/examples/vite/package.json b/examples/vite/package.json index 159fb57..52beb0e 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -1,5 +1,6 @@ { "private": true, + "type": "module", "scripts": { "dev": "nodemon -w '../src/**/*.ts' -e .ts -x vite" }, diff --git a/test/caddy/index.test.ts b/test/caddy/index.test.ts index 796b6d4..0fb814a 100644 --- a/test/caddy/index.test.ts +++ b/test/caddy/index.test.ts @@ -39,10 +39,10 @@ describe('caddy', () => { const caddyfile = await caddy.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` - " - { + "{ debug auto_https disable_redirects + } :7601 { @@ -63,6 +63,11 @@ describe('caddy', () => { } } + + + + + test-1.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -88,6 +93,7 @@ describe('caddy', () => { } } } + " `) @@ -105,10 +111,10 @@ describe('caddy', () => { const caddyfile = await caddy.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` - " - { + "{ debug auto_https disable_redirects + } :7601 { @@ -129,6 +135,11 @@ describe('caddy', () => { } } + + + + + test-2.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { @@ -154,6 +165,7 @@ describe('caddy', () => { } } } + " `) await caddy.baseCleanup() @@ -168,10 +180,10 @@ describe('caddy', () => { let caddyfile = await caddy_80.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` - " - { + "{ debug auto_https disable_redirects + } :7601 { @@ -192,6 +204,11 @@ describe('caddy', () => { } } + + + + + test-3.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -217,6 +234,7 @@ describe('caddy', () => { } } } + " `) @@ -230,10 +248,10 @@ describe('caddy', () => { caddyfile = await caddy_443.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` - " - { + "{ debug auto_https disable_redirects + } :7601 { @@ -254,6 +272,11 @@ describe('caddy', () => { } } + + + + + test-3.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -280,6 +303,10 @@ describe('caddy', () => { } } + + + + test-4.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { @@ -305,6 +332,7 @@ describe('caddy', () => { } } } + " `) @@ -324,10 +352,10 @@ describe('caddy', () => { const caddyfile = await caddy.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` - " - { + "{ debug auto_https disable_redirects + } :7601 { @@ -348,6 +376,11 @@ describe('caddy', () => { } } + + + + + test-2.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { @@ -373,6 +406,7 @@ describe('caddy', () => { } } } + " `) From bf9e840215368359cbdae0bcc5b81a6fc1d07c64 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Fri, 5 Sep 2025 18:11:54 +0800 Subject: [PATCH 05/15] refactor: refactor the logic for generating Caddyfile using `alien-signals` --- package.json | 1 + pnpm-lock.yaml | 8 ++++ src/caddy/caddyfile.ts | 25 +++++++------ src/caddy/config.ts | 67 +++++++++++++++++++++++++++++++++ src/caddy/constants.ts | 2 + src/caddy/index.ts | 80 +++++++++++++++++++++------------------- test/caddy/index.test.ts | 34 +++-------------- 7 files changed, 139 insertions(+), 78 deletions(-) create mode 100644 src/caddy/config.ts diff --git a/package.json b/package.json index dafca7a..90d5748 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ } }, "dependencies": { + "alien-signals": "^2.0.7", "consola": "^3.4.2", "console-table-printer": "^2.12.1", "dotenv-flow": "^4.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b450361..d1713b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + alien-signals: + specifier: ^2.0.7 + version: 2.0.7 consola: specifier: ^3.4.2 version: 3.4.2 @@ -3337,6 +3340,9 @@ packages: ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + alien-signals@2.0.7: + resolution: {integrity: sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==} + alphanum-sort@1.0.2: resolution: {integrity: sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==} @@ -15140,6 +15146,8 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + alien-signals@2.0.7: {} + alphanum-sort@1.0.2: {} ansi-align@3.0.1: diff --git a/src/caddy/caddyfile.ts b/src/caddy/caddyfile.ts index adb8c86..8415455 100644 --- a/src/caddy/caddyfile.ts +++ b/src/caddy/caddyfile.ts @@ -1,6 +1,7 @@ import { type } from 'arktype' +import { Liquid } from 'liquidjs' -const ProxyType = type({ +export const ProxyType = type({ target: 'string', port_suffix: 'string', tls: 'string', @@ -9,24 +10,19 @@ const ProxyType = type({ }) const CaddyContext = type({ - include_base: 'boolean', - caddy_root: 'string?', - + caddy_root: 'string', proxies: ProxyType.array(), -}).narrow((data, ctx) => { - if (data.include_base && (!data.caddy_root || data.caddy_root.length === 0)) { - return ctx.reject({ expected: 'non-empty caddy_root when include_base is true', actual: String(data.caddy_root) }) - } - return true }) -export function validateTemplateContext(ctx: unknown): void { +export function validateTemplateContext(ctx: unknown): typeof CaddyContext.infer { const out = CaddyContext(ctx as any) if (out instanceof type.errors) throw new Error(out.summary) + + return ctx as typeof CaddyContext.infer } -export const caddyTemplate = `{% if include_base %}{ +export const caddyTemplate = `{ debug auto_https disable_redirects } @@ -48,7 +44,6 @@ export const caddyTemplate = `{% if include_base %}{ } } } -{% endif %} {% for p in proxies %} {{ p.target }}{{ p.port_suffix }} { @@ -78,3 +73,9 @@ export const caddyTemplate = `{% if include_base %}{ } {% endfor %} ` + +const liquid = new Liquid() + +export async function genCaddyfile(ctx: typeof CaddyContext.infer): Promise { + return await liquid.parseAndRender(caddyTemplate, ctx) +} diff --git a/src/caddy/config.ts b/src/caddy/config.ts new file mode 100644 index 0000000..fea35d6 --- /dev/null +++ b/src/caddy/config.ts @@ -0,0 +1,67 @@ +import type { ProxyType } from './caddyfile' +import { existsSync, readFileSync, watch, writeFileSync } from 'node:fs' +import { unlink } from 'node:fs/promises' +import { computed, signal } from 'alien-signals' +import { caddyConfigPath } from './constants' + +export function useCaddyConfig(): { + configJsonRef: () => (Record | undefined) + writeCaddyConfig: (proxies: Array) => void + cleanUp: () => Promise +} { + const configContentRef = signal('{}') + + const configJsonRef = computed(() => { + try { + return JSON.parse(configContentRef()) as Record + } + catch { + return undefined + } + }) + + function setConfigContent(): void { + if (!existsSync(caddyConfigPath)) { + writeFileSync(caddyConfigPath, '{}') + } + try { + configContentRef(readFileSync(caddyConfigPath, 'utf-8')) + } + catch { + configContentRef('') + } + } + + if (!existsSync(caddyConfigPath)) { + writeFileSync(caddyConfigPath, '{}') + } + + const watchCaddyConfig = watch(caddyConfigPath, () => { + setConfigContent() + }) + + function writeCaddyConfig(_proxies: Array): void { + const proxies = configJsonRef() || {} + + _proxies.forEach((p) => { + const index = `${p.target}${p.port_suffix}` + proxies[index] = p + }) + + const content = JSON.stringify(proxies, null, 2) + configContentRef(content) + return writeFileSync(caddyConfigPath, content) + } + + async function cleanUp(): Promise { + await unlink(caddyConfigPath).catch(() => { }) + configContentRef('{}') + watchCaddyConfig.close() + } + + return { + configJsonRef, + writeCaddyConfig, + cleanUp, + } +} diff --git a/src/caddy/constants.ts b/src/caddy/constants.ts index a7b40fa..f62cf4c 100644 --- a/src/caddy/constants.ts +++ b/src/caddy/constants.ts @@ -16,6 +16,8 @@ function resolvePath(path: string): string { export const TEMP_DIR = resolvePath(process.env.UHRP_CADDY_PATH || process.env.TEMP || process.env.TMPDIR || '/tmp') export const caddyPath = join(TEMP_DIR, `caddy${process.platform === 'win32' ? '.exe' : ''}`) export const caddyFilePath = join(TEMP_DIR, 'CADDYFILE') + +export const caddyConfigPath = join(TEMP_DIR, '.CADDYFILE.config.json') export const caddyLockFilePath = join(TEMP_DIR, 'caddylock') export const supportList = [ diff --git a/src/caddy/index.ts b/src/caddy/index.ts index 3db0f87..6f74086 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -4,19 +4,18 @@ import { readFile, unlink, writeFile } from 'node:fs/promises' import { platform } from 'node:os' import { join } from 'node:path' import process from 'node:process' +import { effect } from 'alien-signals' import { got } from 'got-cjs' import { HttpProxyAgent } from 'http-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent' -import { Liquid } from 'liquidjs' import * as lockfile from 'proper-lockfile' import { addHost, removeHost } from '../host' import { chmodRecursive, consola, once } from '../utils' -import { caddyTemplate, validateTemplateContext } from './caddyfile' +import { genCaddyfile, validateTemplateContext } from './caddyfile' +import { useCaddyConfig } from './config' import { caddyFilePath, caddyLockFilePath, caddyPath, supportList, TEMP_DIR } from './constants' import { kill, logProgress, logProgressOver, tryPort } from './utils' -const liquid = new Liquid() - export async function download(): Promise { if (await testCaddy()) return caddyPath @@ -105,11 +104,13 @@ interface RunOptions { https: boolean } +const { cleanUp, configJsonRef, writeCaddyConfig } = useCaddyConfig() + export class CaddyInstant { private inited = false private stoped = false - /** has locked */ + /** has locked when start */ private locked = false private caddyfile: string | undefined @@ -117,11 +118,10 @@ export class CaddyInstant { private _source: string | undefined private _target: string | undefined + private _stopEffects: Array< (() => void)> = [] constructor() { testCaddy().then(async () => { - await this.getCaddyfile() - this.inited = true }) @@ -135,26 +135,22 @@ export class CaddyInstant { } async getCaddyfile(): Promise { - if (!existsSync(caddyFilePath)) - await this.initCaddyfile() + if (!existsSync(caddyFilePath)) { + this.caddyfile = '' + return '' + } this.caddyfile = await readFile(caddyFilePath, 'utf-8') return this.caddyfile } - async initCaddyfile(): Promise { - const caddyRoot = join( + get caddyRoot(): string { + return join( process.platform === 'win32' ? `${process.env.AppData!}/Caddy` : `${process.env.HOME!}/Library/Application Support/Caddy`, './pki/authorities/local', ) - - const ctx = { include_base: true, caddy_root: caddyRoot, proxies: [] } - validateTemplateContext(ctx) - const contet = await liquid.parseAndRender(caddyTemplate, ctx) - await writeFile(caddyFilePath, contet) - this.caddyfile = contet } async writeCaddyfile(): Promise { @@ -193,27 +189,34 @@ export class CaddyInstant { throw new Error('update host failed') consola.success('update host success\n') - await this.getCaddyfile() - if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) { - const targetHost = target.split(':')[0] - const ctx = { - include_base: false, - proxies: [ - { - target: targetHost, - port_suffix: https ? '' : ':80', - tls: https ? 'tls internal' : '', - source, - base, - }, - ], - } - validateTemplateContext(ctx) - const block = await liquid.parseAndRender(caddyTemplate, ctx) + const targetHost = target.split(':')[0] - this.caddyfile = `${this.caddyfile}\n${block}` - await this.writeCaddyfile() - } + const proxies = [ + { + target: targetHost, + port_suffix: https ? '' : ':80', + tls: https ? 'tls internal' : '', + source, + base, + }, + ] + + writeCaddyConfig(proxies) + + this._stopEffects.push( + effect(async () => { + const proxies = Object.values(configJsonRef() || {}) + if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) { + const ctx = validateTemplateContext({ + caddy_root: this.caddyRoot, + proxies, + }) + + this.caddyfile = await genCaddyfile(ctx) + await this.writeCaddyfile() + } + }), + ) if (!existsSync(caddyLockFilePath)) writeFileSync(caddyLockFilePath, '') @@ -341,6 +344,8 @@ export class CaddyInstant { } async baseCleanup(restore: boolean = true): Promise { + this._stopEffects.forEach(fn => fn()) + try { if (restore && this._source && this._target) { if (await removeHost(this._source.split(':')[0], this._target.split(':')[0])) @@ -360,6 +365,7 @@ export class CaddyInstant { await lockfile.unlock(caddyLockFilePath).catch(() => { }) await unlink(caddyFilePath).catch(() => { }) await unlink(caddyLockFilePath).catch(() => { }) + await cleanUp() } catch (e) { consola.error(e) diff --git a/test/caddy/index.test.ts b/test/caddy/index.test.ts index 0fb814a..3b6a6a0 100644 --- a/test/caddy/index.test.ts +++ b/test/caddy/index.test.ts @@ -14,6 +14,10 @@ beforeAll(async () => { await unlink(resolve(baseTestUrl, TEMP_DIR)) }) +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + describe('caddy', () => { it('download', async () => { expect(await download()).toBe(caddyPath) @@ -42,7 +46,6 @@ describe('caddy', () => { "{ debug auto_https disable_redirects - } :7601 { @@ -64,10 +67,6 @@ describe('caddy', () => { } - - - - test-1.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -114,7 +113,6 @@ describe('caddy', () => { "{ debug auto_https disable_redirects - } :7601 { @@ -136,10 +134,6 @@ describe('caddy', () => { } - - - - test-2.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { @@ -183,7 +177,6 @@ describe('caddy', () => { "{ debug auto_https disable_redirects - } :7601 { @@ -205,10 +198,6 @@ describe('caddy', () => { } - - - - test-3.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -246,12 +235,12 @@ describe('caddy', () => { expect(caddy_443.checkLock()).toBe(true) + await sleep(50) caddyfile = await caddy_443.getCaddyfile() expect(caddyfile).toMatchInlineSnapshot(` "{ debug auto_https disable_redirects - } :7601 { @@ -273,10 +262,6 @@ describe('caddy', () => { } - - - - test-3.abc.com:80 { reverse_proxy http://127.0.0.1:8080 { @@ -303,10 +288,6 @@ describe('caddy', () => { } } - - - - test-4.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { @@ -355,7 +336,6 @@ describe('caddy', () => { "{ debug auto_https disable_redirects - } :7601 { @@ -377,10 +357,6 @@ describe('caddy', () => { } - - - - test-2.abc.com { tls internal reverse_proxy http://127.0.0.1:8080 { From 815690ff0737d3c67acfb0dc7c0b2175c09c434d Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Fri, 5 Sep 2025 18:21:47 +0800 Subject: [PATCH 06/15] chore: deps --- package.json | 2 +- pnpm-lock.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 90d5748..563bf11 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,6 @@ } }, "dependencies": { - "alien-signals": "^2.0.7", "consola": "^3.4.2", "console-table-printer": "^2.12.1", "dotenv-flow": "^4.1.0", @@ -144,6 +143,7 @@ "unplugin": "^2.2.0" }, "devDependencies": { + "alien-signals": "^2.0.7", "@antfu/eslint-config": "^4.10.2", "@nuxt/kit": "^3.16.1", "@nuxt/schema": "^3.16.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1713b9..ff4d42e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - alien-signals: - specifier: ^2.0.7 - version: 2.0.7 consola: specifier: ^3.4.2 version: 3.4.2 @@ -81,6 +78,9 @@ importers: '@vue/cli': specifier: ^5.0.8 version: 5.0.8(encoding@0.1.13) + alien-signals: + specifier: ^2.0.7 + version: 2.0.7 arktype: specifier: ^2.1.20 version: 2.1.20 From 1f5c48805537496e3df7c25fb7ca55d97c21d8cd Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Fri, 5 Sep 2025 19:03:34 +0800 Subject: [PATCH 07/15] fix: prevent getting stuck during installation --- examples/nuxt/nuxt.config.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/nuxt/nuxt.config.ts b/examples/nuxt/nuxt.config.ts index 9173100..ca3cc3d 100644 --- a/examples/nuxt/nuxt.config.ts +++ b/examples/nuxt/nuxt.config.ts @@ -1,10 +1,11 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ devtools: { enabled: true }, - modules: [ - ['~/../../../src/nuxt', { - enabled: true, - target: 'test-nuxt.abc.com', - }], - ], + // uncomment to enable the plugin + // modules: [ + // ['~/../../../src/nuxt', { + // enabled: true, + // target: 'test-nuxt.abc.com', + // }], + // ], }) From 0f06cb290da755bfdd39b97b10294932fae522e3 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Fri, 5 Sep 2025 19:04:49 +0800 Subject: [PATCH 08/15] chore: lint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 563bf11..b75d2d5 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,6 @@ "unplugin": "^2.2.0" }, "devDependencies": { - "alien-signals": "^2.0.7", "@antfu/eslint-config": "^4.10.2", "@nuxt/kit": "^3.16.1", "@nuxt/schema": "^3.16.1", @@ -155,6 +154,7 @@ "@types/proper-lockfile": "^4.1.4", "@types/qrcode-terminal": "^0.12.2", "@vue/cli": "^5.0.8", + "alien-signals": "^2.0.7", "arktype": "^2.1.20", "bumpp": "^9.11.1", "chalk": "^5.4.1", From 63cfe153def8cc7daffc3528cf9f5bec155cff12 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Fri, 5 Sep 2025 19:18:37 +0800 Subject: [PATCH 09/15] chore: release v2.0.3-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b75d2d5..df17017 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unplugin-https-reverse-proxy", "type": "module", - "version": "2.0.2", + "version": "2.0.3-beta.1", "description": "", "license": "MIT", "homepage": "https://github.com/zcf0508/unplugin-https-reverse-proxy#readme", From 1ae67fcda7c188de0212a56a9c0d63f0b205a6f7 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 30 Sep 2025 16:25:56 +0800 Subject: [PATCH 10/15] feat: support disable health check --- src/caddy/caddyfile.ts | 9 +++------ src/caddy/index.ts | 3 +++ src/types.ts | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/caddy/caddyfile.ts b/src/caddy/caddyfile.ts index 8415455..49d272b 100644 --- a/src/caddy/caddyfile.ts +++ b/src/caddy/caddyfile.ts @@ -7,6 +7,7 @@ export const ProxyType = type({ tls: 'string', source: 'string', base: 'string', + health_check: 'boolean', }) const CaddyContext = type({ @@ -48,7 +49,7 @@ export const caddyTemplate = `{ {% for p in proxies %} {{ p.target }}{{ p.port_suffix }} { {{ p.tls }} - reverse_proxy http://{{ p.source }} { + reverse_proxy http://{{ p.source }} {% if p.health_check %} { health_uri {{ p.base }} health_interval 2s health_timeout 5s @@ -56,11 +57,6 @@ export const caddyTemplate = `{ unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -70,6 +66,7 @@ export const caddyTemplate = `{ } } } + {% endif %} } {% endfor %} ` diff --git a/src/caddy/index.ts b/src/caddy/index.ts index 6f74086..2b2b31a 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -102,6 +102,7 @@ interface RunOptions { base: string showCaddyLog: boolean https: boolean + health_check?: boolean } const { cleanUp, configJsonRef, writeCaddyConfig } = useCaddyConfig() @@ -177,6 +178,7 @@ export class CaddyInstant { base = '/', showCaddyLog = false, https = false, + health_check = true, } = options || {} this._source = source @@ -198,6 +200,7 @@ export class CaddyInstant { tls: https ? 'tls internal' : '', source, base, + health_check, }, ] diff --git a/src/types.ts b/src/types.ts index 3f8d2a4..e2a9c06 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,4 +5,5 @@ export interface Options { showCaddyLog?: boolean /** default `false` */ https?: boolean + health_check?: boolean } From b74cddcb1a9936fcbcf723b75c726188fc90476f Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 30 Sep 2025 16:37:56 +0800 Subject: [PATCH 11/15] chore: release v2.0.3-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df17017..2b15359 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unplugin-https-reverse-proxy", "type": "module", - "version": "2.0.3-beta.1", + "version": "2.0.3-beta.2", "description": "", "license": "MIT", "homepage": "https://github.com/zcf0508/unplugin-https-reverse-proxy#readme", From 6fec89902cd875e02a46a996bd19a0226c52d855 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 21 Oct 2025 17:50:34 +0800 Subject: [PATCH 12/15] fix: avoid getting stuck during nuxt prepare --- examples/nuxt/nuxt.config.ts | 13 ++++++------- src/caddy/index.ts | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/examples/nuxt/nuxt.config.ts b/examples/nuxt/nuxt.config.ts index ca3cc3d..9173100 100644 --- a/examples/nuxt/nuxt.config.ts +++ b/examples/nuxt/nuxt.config.ts @@ -1,11 +1,10 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ devtools: { enabled: true }, - // uncomment to enable the plugin - // modules: [ - // ['~/../../../src/nuxt', { - // enabled: true, - // target: 'test-nuxt.abc.com', - // }], - // ], + modules: [ + ['~/../../../src/nuxt', { + enabled: true, + target: 'test-nuxt.abc.com', + }], + ], }) diff --git a/src/caddy/index.ts b/src/caddy/index.ts index 2b2b31a..eb689d0 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -105,7 +105,14 @@ interface RunOptions { health_check?: boolean } -const { cleanUp, configJsonRef, writeCaddyConfig } = useCaddyConfig() +let caddyConfig: ReturnType | undefined + +function getCaddyConfig(): ReturnType { + if (!caddyConfig) { + caddyConfig = useCaddyConfig() + } + return caddyConfig +} export class CaddyInstant { private inited = false @@ -204,10 +211,12 @@ export class CaddyInstant { }, ] + const { writeCaddyConfig } = getCaddyConfig() writeCaddyConfig(proxies) this._stopEffects.push( effect(async () => { + const { configJsonRef } = getCaddyConfig() const proxies = Object.values(configJsonRef() || {}) if (!(this.caddyfile || '').includes(`${target.split(':')[0]}${https ? '' : ':80'}`)) { const ctx = validateTemplateContext({ @@ -368,7 +377,9 @@ export class CaddyInstant { await lockfile.unlock(caddyLockFilePath).catch(() => { }) await unlink(caddyFilePath).catch(() => { }) await unlink(caddyLockFilePath).catch(() => { }) - await cleanUp() + if (caddyConfig) { + await caddyConfig.cleanUp() + } } catch (e) { consola.error(e) From 005b80b3325338c797e31b22b8c49da162a54a35 Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 21 Oct 2025 18:00:29 +0800 Subject: [PATCH 13/15] refactor: rename `healthCheck` config key --- src/caddy/caddyfile.ts | 8 ++--- src/caddy/config.ts | 2 +- src/caddy/index.ts | 8 ++--- src/types.ts | 2 +- test/caddy/index.test.ts | 64 ++++++++-------------------------------- 5 files changed, 22 insertions(+), 62 deletions(-) diff --git a/src/caddy/caddyfile.ts b/src/caddy/caddyfile.ts index 49d272b..3b65072 100644 --- a/src/caddy/caddyfile.ts +++ b/src/caddy/caddyfile.ts @@ -3,11 +3,11 @@ import { Liquid } from 'liquidjs' export const ProxyType = type({ target: 'string', - port_suffix: 'string', + portSuffix: 'string', tls: 'string', source: 'string', base: 'string', - health_check: 'boolean', + healthCheck: 'boolean', }) const CaddyContext = type({ @@ -47,9 +47,9 @@ export const caddyTemplate = `{ } {% for p in proxies %} -{{ p.target }}{{ p.port_suffix }} { +{{ p.target }}{{ p.portSuffix }} { {{ p.tls }} - reverse_proxy http://{{ p.source }} {% if p.health_check %} { + reverse_proxy http://{{ p.source }} {% if p.healthCheck %} { health_uri {{ p.base }} health_interval 2s health_timeout 5s diff --git a/src/caddy/config.ts b/src/caddy/config.ts index fea35d6..7997c66 100644 --- a/src/caddy/config.ts +++ b/src/caddy/config.ts @@ -44,7 +44,7 @@ export function useCaddyConfig(): { const proxies = configJsonRef() || {} _proxies.forEach((p) => { - const index = `${p.target}${p.port_suffix}` + const index = `${p.target}${p.portSuffix}` proxies[index] = p }) diff --git a/src/caddy/index.ts b/src/caddy/index.ts index eb689d0..e13a3cf 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -102,7 +102,7 @@ interface RunOptions { base: string showCaddyLog: boolean https: boolean - health_check?: boolean + healthCheck?: boolean } let caddyConfig: ReturnType | undefined @@ -185,7 +185,7 @@ export class CaddyInstant { base = '/', showCaddyLog = false, https = false, - health_check = true, + healthCheck = true, } = options || {} this._source = source @@ -203,11 +203,11 @@ export class CaddyInstant { const proxies = [ { target: targetHost, - port_suffix: https ? '' : ':80', + portSuffix: https ? '' : ':80', tls: https ? 'tls internal' : '', source, base, - health_check, + healthCheck, }, ] diff --git a/src/types.ts b/src/types.ts index e2a9c06..9a464fc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,5 +5,5 @@ export interface Options { showCaddyLog?: boolean /** default `false` */ https?: boolean - health_check?: boolean + healthCheck?: boolean } diff --git a/test/caddy/index.test.ts b/test/caddy/index.test.ts index 3b6a6a0..218a3eb 100644 --- a/test/caddy/index.test.ts +++ b/test/caddy/index.test.ts @@ -69,7 +69,7 @@ describe('caddy', () => { test-1.abc.com:80 { - reverse_proxy http://127.0.0.1:8080 { + reverse_proxy http://127.0.0.1:8080 { health_uri / health_interval 2s health_timeout 5s @@ -77,11 +77,6 @@ describe('caddy', () => { unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -91,6 +86,7 @@ describe('caddy', () => { } } } + } " @@ -136,7 +132,7 @@ describe('caddy', () => { test-2.abc.com { tls internal - reverse_proxy http://127.0.0.1:8080 { + reverse_proxy http://127.0.0.1:8080 { health_uri / health_interval 2s health_timeout 5s @@ -144,11 +140,6 @@ describe('caddy', () => { unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -158,6 +149,7 @@ describe('caddy', () => { } } } + } " @@ -200,7 +192,7 @@ describe('caddy', () => { test-3.abc.com:80 { - reverse_proxy http://127.0.0.1:8080 { + reverse_proxy http://127.0.0.1:8080 { health_uri / health_interval 2s health_timeout 5s @@ -208,11 +200,6 @@ describe('caddy', () => { unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -222,6 +209,7 @@ describe('caddy', () => { } } } + } " @@ -231,6 +219,7 @@ describe('caddy', () => { expect(caddy_443.checkLock()).toBe(true) await caddy_443.run('127.0.0.1:8080', 'test-4.abc.com', { https: true, + healthCheck: false, }) expect(caddy_443.checkLock()).toBe(true) @@ -264,7 +253,7 @@ describe('caddy', () => { test-3.abc.com:80 { - reverse_proxy http://127.0.0.1:8080 { + reverse_proxy http://127.0.0.1:8080 { health_uri / health_interval 2s health_timeout 5s @@ -272,11 +261,6 @@ describe('caddy', () => { unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -286,32 +270,12 @@ describe('caddy', () => { } } } + } test-4.abc.com { tls internal - reverse_proxy http://127.0.0.1:8080 { - health_uri / - health_interval 2s - health_timeout 5s - fail_duration 2s - unhealthy_status 502 503 504 404 - lb_try_duration 3s - lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } - - @error status 502 503 504 404 - handle_response @error { - respond * 503 { - body "Service Unavailable" - close - } - } - } + reverse_proxy http://127.0.0.1:8080 } " @@ -359,7 +323,7 @@ describe('caddy', () => { test-2.abc.com { tls internal - reverse_proxy http://127.0.0.1:8080 { + reverse_proxy http://127.0.0.1:8080 { health_uri /test/ health_interval 2s health_timeout 5s @@ -367,11 +331,6 @@ describe('caddy', () => { unhealthy_status 502 503 504 404 lb_try_duration 3s lb_try_interval 300ms - health_headers { - Accept text/html - Accept-Encoding gzip, deflate, br - User-Agent Caddy-Health-Check - } @error status 502 503 504 404 handle_response @error { @@ -381,6 +340,7 @@ describe('caddy', () => { } } } + } " From 58de9b2c8bf9aaeb073c613a5c10fcf488739c6f Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 21 Oct 2025 18:01:15 +0800 Subject: [PATCH 14/15] chore: add typecheck script --- .github/workflows/ci.yml | 3 +++ package.json | 1 + tsconfig.json | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24a66a9..5fc84cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,9 @@ jobs: - name: Lint run: pnpm run lint + - name: Typecheck + run: pnpm run typecheck + # test: # runs-on: ${{ matrix.os }} diff --git a/package.json b/package.json index 2b15359..c039f1b 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "dev": "tsup --watch src", "build:fix": "tsx scripts/postbuild.ts", "lint": "eslint .", + "typecheck": "tsc --noEmit", "play": "npm -C examples/vite run dev", "play:rsbuild": "npm -C examples/rsbuild run dev", "play:nuxt": "npm -C examples/nuxt run dev", diff --git a/tsconfig.json b/tsconfig.json index ada762f..f3c58a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "resolveJsonModule": true, "strict": true, "strictNullChecks": true, - "esModuleInterop": true + "esModuleInterop": true, + "skipLibCheck": true }, - "exclude": ["node_modules", "eslint.config.js", "examples/vuecli4"] + "exclude": ["node_modules", "eslint.config.js", "examples"] } From f9fb54810e03b9b5e17c0f969ed88c0f7b81f6af Mon Sep 17 00:00:00 2001 From: zcf0508 Date: Tue, 21 Oct 2025 18:04:38 +0800 Subject: [PATCH 15/15] chore: release v3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c039f1b..16b09a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unplugin-https-reverse-proxy", "type": "module", - "version": "2.0.3-beta.2", + "version": "3.0.0", "description": "", "license": "MIT", "homepage": "https://github.com/zcf0508/unplugin-https-reverse-proxy#readme",