diff --git a/.gitignore b/.gitignore index 854474e..e907041 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ coverage .DS_Store upload -app.config.json5 \ No newline at end of file +app.config.json5 + +release/* +!release/.gitkeep \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..449687c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# page proxy \ No newline at end of file diff --git a/app.config.json5.example b/app.config.json5.example new file mode 100644 index 0000000..b65cadf --- /dev/null +++ b/app.config.json5.example @@ -0,0 +1,10 @@ +{ + port: 3005, + api: { + host: 'localhost:3000', // 后台代理 + path: '/api/router', + }, + allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'], + domain: 'demo.kevisual.xiongxiao.me', + resources: 'localhost:9000/resources', +} diff --git a/package.json b/package.json index 016f13a..84ba9c9 100644 --- a/package.json +++ b/package.json @@ -20,29 +20,32 @@ "build": "rimraf dist && rollup -c", "deploy": "rsync -avz dist/ light:~/apps/var-proxy/backend", "reload": "ssh light pm2 restart proxy", - "pub": "npm run build && npm run deploy && npm run reload" + "pub": "npm run build && npm run deploy && npm run reload", + "demo": "rsync -avz dist/ on:~/docker/page-proxy/dist", + "start": "pm2 start dist/app.mjs --name page-proxy", + "release": "node ./scripts/release/index.mjs" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { - "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.3.0", - "@rollup/plugin-typescript": "^12.1.1", - "@types/http-proxy": "^1.17.15", - "@types/node": "^22.10.1", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-typescript": "^12.1.2", + "@types/http-proxy": "^1.17.16", + "@types/node": "^22.13.4", "cross-env": "^7.0.3", - "nodemon": "^3.1.7", - "rollup": "^4.28.1", + "nodemon": "^3.1.9", + "rollup": "^4.34.8", "tslib": "^2.8.1", - "typescript": "^5.7.2" + "typescript": "^5.7.3" }, "dependencies": { - "@kevisual/router": "0.0.6-alpha-2", + "@kevisual/router": "0.0.6-alpha-5", "@kevisual/use-config": "^1.0.7", - "ioredis": "^5.4.1", - "nanoid": "^5.0.9" + "ioredis": "^5.5.0", + "nanoid": "^5.1.0" }, "resolutions": { "picomatch": "^4.0.2" diff --git a/release/.gitkeep b/release/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/release/index.mjs b/scripts/release/index.mjs new file mode 100644 index 0000000..1db859b --- /dev/null +++ b/scripts/release/index.mjs @@ -0,0 +1,114 @@ +import fs from 'fs'; +import path from 'path'; +import archiver from 'archiver'; +import { exec } from 'child_process'; +import { nanoid } from 'nanoid'; + +const cwd = process.cwd(); +const pkgPath = path.join(cwd, 'package.json'); + +export const checkFileExistsSync = (filePath) => { + try { + // 使用 F_OK 检查文件或目录是否存在 + fs.accessSync(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } +}; + +const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + +const releasePath = path.join(cwd, 'release'); + +const distPath = path.join(cwd, 'dist'); + +const zip = archiver('zip', { + zlib: { level: 9 }, +}); +const zipName = `page-proxy-${pkg.version}.zip`; +const zipCache = path.join(releasePath, `page-proxy-${pkg.version}.zip`); + +const getZip = async () => { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(zipCache); + const startTime = (new Date().getTime() / 1000).toFixed(0); + // 监听事件 + output.on('close', async () => { + const bytes = zip.pointer(); + const size = bytes < 1024 ? `${bytes} bytes` : `${(bytes / 1024).toFixed(2)} KB`; + console.log(`Zip file has been created successfully. Total size: ${size} bytes.`); + let time = (new Date().getTime() / 1000).toFixed(0); + console.log('time', time - startTime); + resolve(); + }); + + output.on('end', () => { + console.log('Data has been drained.'); // 数据已被耗尽 + throw new CustomError('Data has been drained.'); + }); + + zip.on('warning', (err) => { + if (err.code === 'ENOENT') { + console.warn('File not found:', err); + } else { + throw err; + } + }); + + zip.on('error', (err) => { + throw err; + }); + + // 通过管道将 zip 数据流输出到指定文件 + zip.pipe(output); + + // 添加 sh 字符串作为文件到 zip 中 + const sh = `#!/bin/bash + npm i -g pnpm + pnpm install --prod + `; + zip.append(sh, { name: 'start.sh' }); + // 把dist目录下的文件添加到zip中 + zip.directory(distPath, 'dist'); + // 把README.md添加到zip中 + zip.file(path.join(cwd, 'README.md'), { name: 'README.md' }); + // 把package.json添加到zip中 + zip.file(pkgPath, { name: 'package.json' }); + const ecosystemContent = `module.exports = { + apps: [ + { + name: 'page-proxy', // 应用名称 + script: './dist/app.mjs', // 入口文件 + // cwd: '.', // 设置当前工作目录 + output: './logs/page-proxy.log', + error: './logs/page-proxy.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss', + // watch: true, // 自动监控文件变化 + watch: ['dist'], // 监控的文件夹 + ignore_watch: ['node_modules', 'logs'], // 忽略的文件夹 + } + ] +} +`; + zip.append(ecosystemContent, { name: 'ecosystem.config.cjs' }); + const json5Content = fs.readFileSync(path.join(cwd, 'app.config.json5.example'), 'utf8'); + // tokenSecret 是一个随机字符串,用于生成 token + const tokenSecret = 'XX' + nanoid(39); + json5Content.replace('', tokenSecret); + // tokenSecret + // 把app.config.json5.example添加到zip中 + // zip.file(path.join(cwd, 'app.config.json5.example'), { name: 'app.config.json5.example' }); + zip.append(json5Content, { name: 'app.config.json5.example' }); + + // 结束归档(必须调用,否则 zip 文件无法完成) + zip.finalize(); + }); +}; + +getZip().then(() => { + console.log('zip success'); + console.log(`envision switchOrg system && envision deploy ./release/${zipName} -v 1.0.0 -k page-proxy -y y -u`); + + console.log(`download zip: https://kevisual.xiongxiao.me/system/page-proxy/${zipName}`); +}); diff --git a/src/module/get-user-app.ts b/src/module/get-user-app.ts index b7011ba..73e1822 100644 --- a/src/module/get-user-app.ts +++ b/src/module/get-user-app.ts @@ -255,7 +255,10 @@ export const downloadUserAppFiles = async (user: string, app: string, data: type }; } if (data.type === 'oss') { - const serverPath = 'https://' + resources + '/'; + let serverPath = 'https://' + resources + '/'; + if(resources.includes('localhost')) { + serverPath = 'http://' + resources + '/'; + } // server download file for (let i = 0; i < files.length; i++) { const file = files[i]; diff --git a/src/module/index.ts b/src/module/index.ts index 4e9c34e..b68b2d9 100644 --- a/src/module/index.ts +++ b/src/module/index.ts @@ -24,13 +24,18 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR res.write('proxy no favicon.ico\n'); return; } - if (req.url.startsWith('/api/router')) { + if (req.url.startsWith('/api/proxy')) { + return; + } + if (req.url.startsWith('/api')) { // 代理到 http://codeflow.xiongxiao.me/api const _u = new URL(req.url, `http://${api.host}`); // 设置代理请求的目标 URL 和请求头 let header: any = {}; - if (req.headers?.['Authroization']) { - header.Authorization = req.headers?.['Authroization']; + if (req.headers?.['Authorization']) { + header.authorization = req.headers['Authorization']; + } else if(req.headers?.['authorization']) { + header.authorization = req.headers['authorization']; } if (req.headers?.['Content-Type']) { header['Content-Type'] = req.headers?.['Content-Type']; @@ -64,9 +69,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR req.pipe(proxyReq, { end: true }); return; } - if (req.url.startsWith('/api/proxy')) { - return; - } if (req.url.startsWith('/api')) { res.end('not catch api'); return;