diff --git a/examples/vite/vite.config.ts b/examples/vite/vite.config.ts index f2a0cc3..0ec4b64 100644 --- a/examples/vite/vite.config.ts +++ b/examples/vite/vite.config.ts @@ -6,8 +6,10 @@ export default defineConfig({ plugins: [ Inspect(), Unplugin({ - target: 'test.abc.com', + target: 'test23.abc.com', showCaddyLog: true, + enable: true, + https: false, }), ], }) diff --git a/package.json b/package.json index 4f2da79..f5103ff 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "dependencies": { "consola": "^3.2.3", "dotenv-flow": "^4.1.0", + "easy-host-cli": "^1.0.10", "got-cjs": "^12.5.4", "hosts-so-easy": "^1.2.9", "http-proxy-agent": "^7.0.2", @@ -141,6 +142,7 @@ "kill-port": "^2.0.1", "ora": "5", "picocolors": "^1.0.0", + "sudo-prompt": "^9.2.1", "tsx": "^4.7.1", "unplugin": "^1.7.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdd53a4..59d2720 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: dotenv-flow: specifier: ^4.1.0 version: 4.1.0 + easy-host-cli: + specifier: ^1.0.10 + version: 1.0.10 esbuild: specifier: '*' version: 0.19.11 @@ -38,6 +41,9 @@ importers: picocolors: specifier: ^1.0.0 version: 1.0.0 + sudo-prompt: + specifier: ^9.2.1 + version: 9.2.1 tsx: specifier: ^4.7.1 version: 4.7.1 @@ -2842,7 +2848,7 @@ packages: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.5.4 + semver: 7.6.0 tar: 6.2.0 transitivePeerDependencies: - encoding @@ -2965,7 +2971,7 @@ packages: proc-log: 3.0.0 promise-inflight: 1.0.1(bluebird@3.7.2) promise-retry: 2.0.1 - semver: 7.5.4 + semver: 7.6.0 which: 4.0.0 transitivePeerDependencies: - bluebird @@ -3038,7 +3044,7 @@ packages: pkg-types: 1.0.3 prompts: 2.4.2 rc9: 2.1.1 - semver: 7.5.4 + semver: 7.6.0 dev: true /@nuxt/devtools@1.0.8(nuxt@3.10.2)(rollup@3.29.4)(vite@4.5.2): @@ -8949,6 +8955,11 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true + /easy-host-cli@1.0.10: + resolution: {integrity: sha512-wxTpsC1P+KN83A/33GvZSgzxXdjHu02HGzspE8o5qizBUulFaIQfRTXGo01cAemgpKHMihpeABPhzLGakmZQvg==} + hasBin: true + dev: false + /easy-stack@1.0.1: resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==} engines: {node: '>=6.0.0'} @@ -12685,7 +12696,7 @@ packages: resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==} engines: {node: 14 || >=16.14} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: true /lru-cache@4.1.5: @@ -13657,7 +13668,7 @@ packages: dependencies: hosted-git-info: 7.0.1 proc-log: 3.0.0 - semver: 7.5.4 + semver: 7.6.0 validate-npm-package-name: 5.0.0 dev: true @@ -13675,7 +13686,7 @@ packages: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 11.0.1 - semver: 7.5.4 + semver: 7.6.0 dev: true /npm-registry-fetch@16.1.0: @@ -17667,6 +17678,10 @@ packages: ts-interface-checker: 0.1.13 dev: true + /sudo-prompt@9.2.1: + resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -19007,7 +19022,7 @@ packages: fast-glob: 3.3.2 fs-extra: 11.2.0 npm-run-path: 4.0.1 - semver: 7.5.4 + semver: 7.6.0 strip-ansi: 6.0.1 tiny-invariant: 1.3.1 typescript: 5.3.3 @@ -19238,7 +19253,7 @@ packages: engines: {vscode: ^1.52.0} dependencies: minimatch: 3.1.2 - semver: 7.5.4 + semver: 7.6.0 vscode-languageserver-protocol: 3.16.0 dev: true diff --git a/src/caddy/index.ts b/src/caddy/index.ts index 8008dc3..34a4c28 100644 --- a/src/caddy/index.ts +++ b/src/caddy/index.ts @@ -6,8 +6,8 @@ import { got } from 'got-cjs' import { HttpProxyAgent } from 'http-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent' import kill from 'kill-port' -import { chmodRecursive, consola, once } from '../utils' -import { addHost, removeHost } from '../host' +import { consola, once, runAsAdmin } from '../utils' +import { addHost, removeHost } from '../host/cli' import { TEMP_DIR, caddyPath, supportList } from './constants' import { logProgress, logProgressOver, tryPort } from './utils' @@ -20,7 +20,7 @@ export async function download() { if (!existsSync(TEMP_DIR)) { mkdirSync(TEMP_DIR, { recursive: true }) - chmodSync(TEMP_DIR, 0o777) + chmodSync(TEMP_DIR, 0o744) } const file = createWriteStream(caddyPath) @@ -46,10 +46,9 @@ export async function download() { const httpAgent = httpProxy ? new HttpProxyAgent(httpProxy) : undefined const httpsAgent = httpsProxy ? new HttpsProxyAgent(httpsProxy) : undefined - const chmodCaddyOnce = once(() => { // chmod +x - chmodSync(caddyPath, 0o777) + chmodSync(caddyPath, 0o744) }) got.stream(dowmloadLink, { @@ -65,7 +64,7 @@ export async function download() { if (process.platform === 'win32') return resolve(caddyPath) // chmod +x - chmodSync(caddyPath, 0o777) + chmodSync(caddyPath, 0o744) resolve(caddyPath) }).on('error', (err) => { // consola.error(err) @@ -78,8 +77,8 @@ function testCaddy() { return new Promise((resolve, reject) => { if (!existsSync(caddyPath)) return resolve(false) - chmodSync(caddyPath, 0o777) - const child = process.platform === 'win32' ? spawn(caddyPath, []) : spawn('sudo', ['-E', caddyPath]) + chmodSync(caddyPath, 0o744) + const child = spawn(caddyPath, []) child.on('close', () => { return resolve(false) }) @@ -142,12 +141,37 @@ export class CaddyInstant { return new Promise((resolve, reject) => { // caddy reverse-proxy --from target --to source --internal-certs - const child = process.platform !== 'win32' - ? spawn('sudo', ['-E', caddyPath, 'reverse-proxy', '--from', `${target.split(':')[0]}${https ? '' : ':80'}`, '--to', `${source}`, '--internal-certs', '--insecure', '--disable-redirects']) - : spawn(caddyPath, ['reverse-proxy', '--from', `${target.split(':')[0]}`, '--to', `${source}`, '--internal-certs']) - - child.on('error', (err) => { - return reject(err) + runAsAdmin([caddyPath, + 'reverse-proxy', + '--from', + `${target.split(':')[0]}${https ? '' : ':80'}`, + '--to', + `${source}`, + '--internal-certs', + '--insecure', + '--disable-redirects', + ].join(' '), 'caddy', (error, stdout, stderr) => { + if (error) + return reject(error) + + // stderr.on('data', (data: any) => { + // const lines = (data.toString() as string).split('\n').map(line => line.trim()) + // for (const line of lines) { + // // caddy log + // // eslint-disable-next-line no-console + // showCaddyLog && line && console.info(line) + // if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) { + // consola.error(line) + // // child.kill() + // return reject(line) + // } + // } + // }) + + // stdout.on('data', (_data: any) => { + // consola.info(_data.toString()) + // resolve() + // }) }) process.on('SIGINT', async () => { @@ -181,18 +205,6 @@ export class CaddyInstant { if (!Number.isNaN(port) && await tryPort(port)) await kill(port, 'tcp') - // fix `Error: EPERM: operation not permitted` - const pwd = process.cwd() - const viteCacheDir = `${pwd}/node_modules/.vite` - if (existsSync(viteCacheDir)) - chmodRecursive(viteCacheDir, 0o777) - const nuxtCacheDir = `${pwd}/.nuxt` - if (existsSync(nuxtCacheDir)) - await chmodRecursive(nuxtCacheDir, 0o777) - const webpackCacheDir = `${pwd}/node_modules/.cache` - if (existsSync(webpackCacheDir)) - chmodRecursive(webpackCacheDir, 0o777) - if (!restore || this.stoped) return originalExit(code) @@ -215,24 +227,7 @@ export class CaddyInstant { } } - child.stderr.on('data', (data) => { - const lines = (data.toString() as string).split('\n').map(line => line.trim()) - for (const line of lines) { - // caddy log - // eslint-disable-next-line no-console - showCaddyLog && line && console.info(line) - if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) { - consola.error(line) - // child.kill() - return reject(line) - } - } - }) - - child.stdout.on('data', (_data) => { - consola.info(_data.toString()) - resolve() - }) + resolve() }) } } diff --git a/src/host/cli.ts b/src/host/cli.ts new file mode 100644 index 0000000..b3a8f10 --- /dev/null +++ b/src/host/cli.ts @@ -0,0 +1,9 @@ +import { runAsAdmin } from '../utils' + +export async function addHost(ip: string, host: string) { + return runAsAdmin(`npx easy-host-cli add --ip ${ip} --host ${host}`, 'addHost') +} + +export async function removeHost(ip: string, host: string) { + return runAsAdmin(`npx easy-host-cli rm --ip ${ip} --host ${host}`, 'removeHost') +} diff --git a/src/index.ts b/src/index.ts index 1ff57ff..7dfc3ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,10 +55,6 @@ export const unpluginFactory: UnpluginFactory = options => ({ if (!enable) return - if (!isAdmin()) { - consola.warn('please run as administrator') - return - } if (!target) { consola.fail('please provide target') return diff --git a/src/utils.ts b/src/utils.ts index 757eb2e..3b48c01 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,13 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import process from 'node:process' import path from 'node:path' import { chmod, readdir, stat } from 'node:fs/promises' import { execSync } from 'node:child_process' import { createConsola } from 'consola' +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const sudo = require('sudo-prompt') + export const consola = createConsola({ level: 3, }).withTag('reverse-proxy') @@ -41,3 +45,27 @@ export function isAdmin() { return process.getuid && process.getuid() === 0 } } + +export function runAsAdmin(command: string, name?: string, fn?: (error: any, stdout: any, stderr: any) => void) { + return new Promise((resolve, reject) => { + consola.log('exec command: ', command) + sudo.exec(command, { + name: `unpluginHttpsReverseProxy ${name}`, + }, (error: any, stdout: any, stderr: any) => { + try { + fn?.(error, stdout, stderr) + } + finally { + if (error) { + consola.error(error) + // eslint-disable-next-line prefer-promise-reject-errors + reject(false) + // eslint-disable-next-line no-unsafe-finally + return + } + consola.success(stdout) + resolve(true) + } + }) + }) +}