import { program as app, Command } from '@/program.ts'; import glob from 'fast-glob'; import path from 'path'; import fs from 'fs'; import FormData from 'form-data'; import { getBaseURL, query, storage } from '@/module/query.ts'; import { getConfig } from '@/module/index.ts'; import inquirer from 'inquirer'; import { packLib, unpackLib } from './publish.ts'; import chalk from 'chalk'; import { installDeps } from '@/uitls/npm.ts'; /** * 获取package.json 中的 basename, version, user, appKey * @returns */ export const getPackageJson = () => { const filePath = path.join(process.cwd(), 'package.json'); if (!fs.existsSync(filePath)) { return null; } try { const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf-8')); const basename = packageJson.basename || ''; const version = packageJson.version || ''; const app = packageJson.app as { key: string }; const userAppArry = basename.split('/'); if (userAppArry.length <= 2) { console.error(chalk.red('basename is error, 请输入正确的路径, packages.json中basename例如 /root/appKey')); return null; } const [user, appKey] = userAppArry; return { basename, version, pkg: packageJson, user, appKey, app }; } catch (error) { return null; } }; const command = new Command('deploy') .description('把前端文件传到服务器') .argument('', 'Path to the file to be uploaded, filepath or directory') // 定义文件路径参数 .option('-v, --version ', 'verbose') .option('-k, --key ', 'key') .option('-y, --yes ', 'yes') .option('-o, --org ', 'org') .option('-u, --update', 'load current app. set current version in product。 redis 缓存更新') .option('-s, --showBackend', 'show backend url, 部署的后端应用,显示执行的cli命令') .action(async (filePath, options) => { try { let { version, key, yes, update, org, showBackend } = options; // 获取当前目录,是否存在package.json, 如果有,从package.json 获取 version 和basename const pkgInfo = getPackageJson(); if (!version && pkgInfo?.version) { version = pkgInfo?.version || ''; } if (!key && pkgInfo?.appKey) { key = pkgInfo?.appKey || ''; } console.log('start deploy') if (!version || !key) { const answers = await inquirer.prompt([ { type: 'input', name: 'version', message: 'Enter your version:', when: () => !version, }, { type: 'input', name: 'key', message: 'Enter your key:', when: () => !key, }, ]); version = answers.version || version; key = answers.key || key; } const pwd = process.cwd(); const directory = path.join(pwd, filePath); // 获取directory,如果是文件夹,获取文件夹下所有文件,如果是文件,获取文件 const stat = fs.statSync(directory); let _relativeFiles = []; let isDirectory = false; if (stat.isDirectory()) { isDirectory = true; const gPath = path.join(directory, '**/*'); const files = await glob(gPath, { cwd: pwd, ignore: ['node_modules/**/*'], onlyFiles: true }); _relativeFiles = files.map((file) => path.relative(directory, file)); } else if (stat.isFile()) { const filename = path.basename(directory); _relativeFiles = [filename]; } console.log('upload Files', _relativeFiles); console.log('upload Files Key', key, version); if (!yes) { // 确认是否上传 const confirm = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Do you want to upload these files?', }, ]); if (!confirm.confirm) { return; } } const uploadDirectory = isDirectory ? directory : path.dirname(directory); const res = await uploadFiles(_relativeFiles, uploadDirectory, { key, version, username: org }); if (res?.code === 200) { console.log('File uploaded successfully!'); res.data?.data?.files?.map?.((d) => { console.log('uploaded file', d?.name, d?.path); }); const { id, data, ...rest } = res.data || {}; if (id && !update) { console.log(chalk.green('id: '), id); if (!org) { console.log(chalk.green(`更新为最新版本: envision deploy-load ${id}`)); } else { console.log(chalk.green(`更新为最新版本: envision deploy-load ${id} -o ${org}`)); } } else if (id && update) { deployLoadFn(id); } else { console.log('rest error', JSON.stringify(rest, null, 2)); } if (id && showBackend) { console.log('\n'); // 获取当前应用的key const pkKey = pkgInfo?.app?.key || pkgInfo?.appKey; console.log(chalk.blue('服务端应用部署: '), 'envision pack-deploy', id, '-k '); if (pkKey) { console.log('\n'); console.log(chalk.blue('命令推荐: '), 'envision pack-deploy', id, `-k ${pkKey} -f`); } console.log('\n'); } } else { console.error('File upload failed', res?.message); } return res; } catch (error) { console.error('error', error); } }); const uploadFiles = async ( files: string[], directory: string, { key, version, username }: { key: string; version: string; username: string }, ): Promise => { const config = await getConfig(); const form = new FormData(); for (const file of files) { const filePath = path.join(directory, file); form.append('file', fs.createReadStream(filePath), { filename: file, filepath: file, }); } form.append('appKey', key); form.append('version', version); if (username) { form.append('username', username); } return new Promise(async (resolve) => { const _baseURL = getBaseURL(); const url = new URL(_baseURL); console.log('upload url', url.hostname, url.protocol, url.port); const token = await storage.getItem('token'); form.submit( { path: '/api/app/upload', host: url.hostname, protocol: url.protocol as any, port: url.port, method: 'POST', headers: { Authorization: 'Bearer ' + token, ...form.getHeaders(), }, }, (err, res) => { if (err) { console.error('Error uploading file:', err.message); return; } // 处理服务器响应 let body = ''; res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { try { const res = JSON.parse(body); resolve(res); } catch (e) { resolve({ code: 500, message: body }); } }); }, ); }); }; app.addCommand(command); const deployLoadFn = async (id: string, org?: string) => { if (!id) { console.error(chalk.red('id is required')); return; } const res = await query.post({ path: 'app', key: 'publish', data: { id: id, username: org, }, }); if (res.code === 200) { console.log(chalk.green('deploy-load success. current version:', res.data?.version)); // /:username/:appName try { const { user, key } = res.data; const baseURL = getBaseURL(); const deployURL = new URL(`/${user}/${key}/`, baseURL); console.log(chalk.blue('deployURL', deployURL.href)); } catch (error) {} } else { console.error('deploy-load failed', res.message); } }; const deployLoad = new Command('deploy-load') .description('部署加载') .argument('', 'id') .option('-o, --org ', 'org') .action(async (id, opts) => { deployLoadFn(id, opts?.org); }); app.addCommand(deployLoad); const local = new Command('local') .description('本地部署') .argument('', 'key') .option('-i, --ignore', '使用 .npmignore 文件模式去忽略文件进行打包, 不需要package.json中的files字段') .option('-u, --update', 'query查询 127.0.0.1:11015/api/router?path=local-apps&key=detect') .action(async (key, opts) => { console.log('local deploy'); const { outputFilePath } = await packLib(opts?.ignore); const mainAppPath = getConfig().mainAppPath; const appsPath = getConfig().appsPath || path.join(mainAppPath, 'apps'); if (!key) { console.error(chalk.red('key is required')); return; } const appPath = path.join(appsPath, key); if (!fs.existsSync(appPath)) { fs.mkdirSync(appPath, { recursive: true }); } // 复制应用到apps目录下 if (outputFilePath) { await unpackLib(outputFilePath, appPath); fs.unlinkSync(outputFilePath); installDeps({ appPath }); if (opts?.update) { const res = await query.post( { path: 'local-apps', key: 'detect' }, { url: `http://127.0.0.1:11015/api/router?path=local-apps&key=detect`, }, ); if (res.code === 200) { console.log('local deploy success'); } else { console.error('local deploy failed', res.message); } } } }); app.addCommand(local);