feat: refactor deploy command to enhance file upload process and user handling
- Updated the deploy command to include a new username retrieval mechanism, falling back to the organization if not specified. - Introduced uploadFilesV2 function to streamline file upload logic, including hash checking to prevent redundant uploads. - Modified queryAppVersion to accept a create parameter for better version management. - Added a new test file to validate the uploadFilesV2 functionality.
This commit is contained in:
@@ -7,9 +7,10 @@ import { getBaseURL, query, storage } from '@/module/query.ts';
|
||||
import { input, confirm } from '@inquirer/prompts';
|
||||
import chalk from 'chalk';
|
||||
import { upload } from '@/module/download/upload.ts';
|
||||
import { getHash } from '@/uitls/hash.ts';
|
||||
import { getBufferHash, getHash } from '@/uitls/hash.ts';
|
||||
import { queryAppVersion } from '@/query/app-manager/query-app.ts';
|
||||
import { logger } from '@/module/logger.ts';
|
||||
import { getUsername } from './login.ts';
|
||||
/**
|
||||
* 获取package.json 中的 basename, version, user, appKey
|
||||
* @returns
|
||||
@@ -44,13 +45,11 @@ const command = new Command('deploy')
|
||||
.option('-o, --org <org>', 'org')
|
||||
.option('-u, --update', 'load current app. set current version in product。 redis 缓存更新')
|
||||
.option('-s, --showBackend', 'show backend url, 部署的后端应用,显示执行的cli命令')
|
||||
.option('-c, --noCheck', '是否受app manager控制的模块。默认检测')
|
||||
.option('-d, --dot', '是否上传隐藏文件')
|
||||
.option('--dir, --directory <directory>', '上传的默认路径')
|
||||
.action(async (filePath, options) => {
|
||||
try {
|
||||
let { version, key, yes, update, org, showBackend } = options;
|
||||
const noCheck = !options.noCheck;
|
||||
const dot = !!options.dot;
|
||||
const pkgInfo = getPackageJson({ version, appKey: key });
|
||||
if (!version && pkgInfo?.version) {
|
||||
@@ -109,18 +108,30 @@ const command = new Command('deploy')
|
||||
return;
|
||||
}
|
||||
}
|
||||
let username = '';
|
||||
if (pkgInfo?.user) {
|
||||
username = pkgInfo.user;
|
||||
} else if (org) {
|
||||
username = org;
|
||||
} else {
|
||||
const me = await getUsername();
|
||||
if (me) {
|
||||
username = me;
|
||||
} else {
|
||||
logger.error('无法获取用户名,请使用先登录');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const uploadDirectory = isDirectory ? directory : path.dirname(directory);
|
||||
const res = await uploadFiles(_relativeFiles, uploadDirectory, { key, version, username: org, noCheckAppFiles: !noCheck, directory: options.directory });
|
||||
const res = await uploadFilesV2(_relativeFiles, uploadDirectory, { key, version, username: username, directory: options.directory });
|
||||
logger.debug('upload res', res);
|
||||
if (res?.code === 200) {
|
||||
res.data?.upload?.map?.((d) => {
|
||||
console.log(chalk.green('uploaded file', d?.name, d?.path));
|
||||
});
|
||||
const res2 = await queryAppVersion({
|
||||
key: key,
|
||||
version: version,
|
||||
create: true
|
||||
});
|
||||
logger.debug('queryAppVersion res', res2);
|
||||
logger.debug('queryAppVersion res', res2, key, version);
|
||||
if (res2.code !== 200) {
|
||||
console.error(chalk.red('查询应用版本失败'), res2.message, key);
|
||||
return;
|
||||
@@ -145,7 +156,6 @@ const command = new Command('deploy')
|
||||
} else {
|
||||
console.error('File upload failed', res?.message);
|
||||
}
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
@@ -154,88 +164,65 @@ const command = new Command('deploy')
|
||||
type UploadFileOptions = {
|
||||
key: string;
|
||||
version: string;
|
||||
username?: string;
|
||||
noCheckAppFiles?: boolean;
|
||||
username: string;
|
||||
directory?: string;
|
||||
};
|
||||
const uploadFiles = async (files: string[], directory: string, opts: UploadFileOptions): Promise<any> => {
|
||||
|
||||
export const uploadFilesV2 = async (files: string[], directory: string, opts: UploadFileOptions): Promise<any> => {
|
||||
const { key, version, username } = opts || {};
|
||||
const form = new FormData();
|
||||
const data: Record<string, any> = { files: [] };
|
||||
let description = '';
|
||||
for (const file of files) {
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const filePath = path.join(directory, file);
|
||||
const hash = getHash(filePath);
|
||||
if (!hash) {
|
||||
logger.error('文件', filePath, '不存在');
|
||||
logger.error('请检查文件是否存在');
|
||||
}
|
||||
data.files.push({ path: file, hash: hash });
|
||||
if (filePath.includes('readme.md')) {
|
||||
description = fs.readFileSync(filePath, 'utf-8');
|
||||
}
|
||||
}
|
||||
data.appKey = key;
|
||||
data.version = version;
|
||||
form.append('appKey', key);
|
||||
form.append('version', version);
|
||||
if (username) {
|
||||
form.append('username', username);
|
||||
data.username = username;
|
||||
}
|
||||
if (opts?.directory) {
|
||||
form.append('directory', opts.directory);
|
||||
data.directory = opts.directory;
|
||||
}
|
||||
const token = await storage.getItem('token');
|
||||
const checkUrl = new URL('/api/s1/resources/upload/check', getBaseURL());
|
||||
const res = await query.adapter({ url: checkUrl.toString(), method: 'POST', body: data, headers: { Authorization: 'Bearer ' + token } }).then((res) => {
|
||||
try {
|
||||
if (typeof res === 'string') {
|
||||
return JSON.parse(res);
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
} catch (error) {
|
||||
return typeof res === 'string' ? {} : res;
|
||||
}
|
||||
});
|
||||
const checkData: { path: string; isUpload: boolean }[] = res.data;
|
||||
if (res.code !== 200) {
|
||||
console.error('check failed', res);
|
||||
return res;
|
||||
}
|
||||
let needUpload = false;
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directory, file);
|
||||
const check = checkData.find((d) => d.path === file);
|
||||
if (check?.isUpload) {
|
||||
logger.debug('文件已经上传过了', file);
|
||||
continue;
|
||||
}
|
||||
logger.info('[上传进度]', `${i + 1}/${files.length}`, file);
|
||||
const form = new FormData();
|
||||
const filename = path.basename(filePath);
|
||||
logger.debug('upload file', file, filename);
|
||||
// 解决 busbox 文件名乱码: 将 UTF-8 编码的文件名转换为 binary 字符串
|
||||
const encodedFilename = Buffer.from(filename, 'utf-8').toString('binary');
|
||||
form.append('file', fs.createReadStream(filePath), {
|
||||
filename: encodedFilename,
|
||||
filepath: file,
|
||||
});
|
||||
needUpload = true;
|
||||
const _baseURL = getBaseURL();
|
||||
const url = new URL(`/${username}/resources/${key}/${version}/${file}`, _baseURL);
|
||||
// console.log('upload file', file, filePath);
|
||||
const token = await storage.getItem('token');
|
||||
const check = () => {
|
||||
const checkUrl = new URL(url.toString());
|
||||
checkUrl.searchParams.set('stat', '1');
|
||||
const res = query
|
||||
.adapter({ url: checkUrl.toString(), method: 'GET', headers: { Authorization: 'Bearer ' + token } })
|
||||
return res;
|
||||
}
|
||||
const checkRes = await check();
|
||||
let needUpload = false;
|
||||
let hash = '';
|
||||
if (checkRes?.code === 404) {
|
||||
needUpload = true;
|
||||
hash = getHash(filePath);
|
||||
} else if (checkRes?.code === 200) {
|
||||
const etag = checkRes?.data?.etag;
|
||||
hash = getHash(filePath);
|
||||
if (etag !== hash) {
|
||||
needUpload = true;
|
||||
}
|
||||
}
|
||||
if (needUpload) {
|
||||
url.searchParams.append('hash', hash);
|
||||
const res = await upload({ url: url, form: form, token: token });
|
||||
logger.debug('upload file', file, res);
|
||||
if (res.code !== 200) {
|
||||
logger.error('文件上传失败', file, res);
|
||||
return { code: 500, message: '文件上传失败', file, fileRes: res };
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.green('\t 文件已经上传过了', url.toString()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!needUpload) {
|
||||
logger.debug('所有文件都上传过了,不需要上传文件');
|
||||
return {
|
||||
code: 200,
|
||||
};
|
||||
}
|
||||
const _baseURL = getBaseURL();
|
||||
const url = new URL('/api/s1/resources/upload', _baseURL);
|
||||
if (opts.noCheckAppFiles) {
|
||||
url.searchParams.append('noCheckAppFiles', 'true');
|
||||
}
|
||||
return upload({ url: url, form: form, token: token });
|
||||
};
|
||||
return { code: 200 }
|
||||
|
||||
}
|
||||
app.addCommand(command);
|
||||
|
||||
const deployLoadFn = async (id: string, org?: string) => {
|
||||
|
||||
Reference in New Issue
Block a user