2025-04-30 01:45:53 +08:00

282 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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('<filePath>', 'Path to the file to be uploaded, filepath or directory') // 定义文件路径参数
.option('-v, --version <version>', 'verbose')
.option('-k, --key <key>', 'key')
.option('-y, --yes <yes>', 'yes')
.option('-o, --org <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 <key>');
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<any> => {
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>', 'id')
.option('-o, --org <org>', 'org')
.action(async (id, opts) => {
deployLoadFn(id, opts?.org);
});
app.addCommand(deployLoad);
const local = new Command('local')
.description('本地部署')
.argument('<key>', '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);