@xuanqikai/one-click-upload
Version:
A CLI tool for one-click file upload to cloud storage services (OSS, TOS)
305 lines • 13.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UploadCommand = void 0;
const chalk_1 = __importDefault(require("chalk"));
const commander_1 = require("commander");
const inquirer_1 = __importDefault(require("inquirer"));
const ora_1 = __importDefault(require("ora"));
const path = __importStar(require("path"));
const ConfigManager_1 = require("../config/ConfigManager");
const UploaderFactory_1 = require("../uploaders/UploaderFactory");
const fileUtils_1 = require("../utils/fileUtils");
class UploadCommand {
constructor() {
this.configManager = new ConfigManager_1.ConfigManager();
}
getCommand() {
const uploadCmd = new commander_1.Command('upload')
.description('Upload files or directories to cloud storage');
uploadCmd
.argument('<local-path>', 'Local file or directory path to upload')
.argument('[remote-path]', 'Remote path in the storage service')
.option('-s, --service <name>', 'Service name to use for upload')
.option('-o, --overwrite', 'Overwrite existing files', false)
.option('-r, --resume', 'Enable resume upload for large files', true)
.option('--max-retries <number>', 'Maximum retry attempts', '3')
.option('--chunk-size <size>', 'Chunk size for multipart upload (in MB)', '1')
.option('--contents-only', 'Upload folder contents only (without folder name)')
.option('--cache-control <value>', 'Custom cache control header (e.g., "no-cache")')
.action(async (localPath, remotePath, options) => {
await this.directUpload(localPath, remotePath, options);
});
return uploadCmd;
}
/**
* 交互式上传
*/
async interactiveUpload() {
try {
// 检查是否有配置的服务
const services = this.configManager.getAllServices();
if (services.length === 0) {
console.log(chalk_1.default.yellow('No services configured. Please add a service first:'));
console.log(chalk_1.default.cyan(' one-upload config add'));
return;
}
// 交互式输入上传参数
const answers = await inquirer_1.default.prompt([
{
type: 'input',
name: 'localPath',
message: 'Local file or directory path:',
validate: async (input) => {
if (!input.trim()) {
return 'Local path is required';
}
if (!(await (0, fileUtils_1.pathExists)(input.trim()))) {
return 'Local path does not exist';
}
return true;
}
},
{
type: 'input',
name: 'remotePath',
message: 'Remote path (optional):',
default: ''
},
{
type: 'list',
name: 'serviceName',
message: 'Select service:',
choices: services.map(service => ({
name: `${service.name}${service.isDefault ? ' (default)' : ''} - ${UploaderFactory_1.UploaderFactory.getTypeDisplayName(service.type)}`,
value: service.name
})),
default: this.configManager.getDefaultService()?.name
},
{
type: 'confirm',
name: 'overwrite',
message: 'Overwrite existing files?',
default: false
},
{
type: 'confirm',
name: 'enableResume',
message: 'Enable resume upload for large files?',
default: true
}
]);
const options = {
serviceName: answers.serviceName,
localPath: answers.localPath.trim(),
remotePath: answers.remotePath.trim() || path.basename(answers.localPath.trim()),
overwrite: !!answers.overwrite,
enableResume: answers.enableResume,
maxRetries: 3,
chunkSize: 1024 * 1024 // 1MB
};
await this.performUpload(options);
}
catch (error) {
console.error(chalk_1.default.red('Interactive upload failed:'), error.message || 'Unknown error');
}
}
/**
* 直接上传
*/
async directUpload(localPath, remotePath, cliOptions) {
try {
// 检查命令行参数中的 overwrite 选项
const hasOverwriteFlag = process.argv.includes('--overwrite') || process.argv.includes('-o');
// 检查本地路径是否存在
if (!(await (0, fileUtils_1.pathExists)(localPath))) {
console.error(chalk_1.default.red(`Local path does not exist: ${localPath}`));
return;
}
// 确定使用的服务
let serviceName = cliOptions?.service;
if (!serviceName) {
const defaultService = this.configManager.getDefaultService();
if (defaultService) {
serviceName = defaultService.name;
}
else {
console.error(chalk_1.default.red('No service specified and no default service configured'));
console.log(chalk_1.default.cyan('Use --service option or set a default service with: one-upload config default <name>'));
return;
}
}
// 检查服务是否存在
const service = this.configManager.getService(serviceName);
if (!service) {
console.error(chalk_1.default.red(`Service '${serviceName}' not found`));
return;
}
// 设置默认远程路径
if (!remotePath) {
remotePath = path.basename(localPath);
}
const options = {
serviceName,
localPath,
remotePath,
overwrite: hasOverwriteFlag,
enableResume: cliOptions?.resume !== false,
maxRetries: parseInt(cliOptions?.maxRetries) || 3,
chunkSize: (parseInt(cliOptions?.chunkSize) || 1) * 1024 * 1024, // Convert MB to bytes
customCacheControl: cliOptions?.cacheControl
};
await this.performUpload(options);
}
catch (error) {
console.error(chalk_1.default.red('Direct upload failed:'), error.message || 'Unknown error');
}
}
/**
* 执行上传操作
*/
async performUpload(options) {
const service = this.configManager.getService(options.serviceName);
if (!service) {
console.error(chalk_1.default.red(`Service '${options.serviceName}' not found`));
return;
}
console.log(chalk_1.default.blue(`\nUploading to ${UploaderFactory_1.UploaderFactory.getTypeDisplayName(service.type)} service: ${chalk_1.default.cyan(service.name)}`));
console.log(chalk_1.default.gray(`Local: ${options.localPath}`));
console.log(chalk_1.default.gray(`Remote: ${options.remotePath}`));
console.log('');
const spinner = (0, ora_1.default)('Initializing upload...').start();
try {
// 创建上传器
const uploader = UploaderFactory_1.UploaderFactory.createUploader(service);
// 设置进度回调
const onProgress = (progress) => {
const percentage = progress.percentage.toFixed(1);
const loaded = (0, fileUtils_1.formatFileSize)(progress.loaded);
const total = (0, fileUtils_1.formatFileSize)(progress.total);
spinner.text = `Uploading ${progress.fileName}: ${percentage}% (${loaded}/${total})`;
};
// 执行上传
const result = await uploader.upload(options.localPath, options.remotePath, {
overwrite: options.overwrite,
enableResume: options.enableResume,
maxRetries: options.maxRetries,
chunkSize: options.chunkSize,
onProgress
});
spinner.stop();
// 显示上传结果
this.displayUploadResult(result);
}
catch (error) {
spinner.fail('Upload failed');
console.error(chalk_1.default.red('Error:'), error.message || 'Unknown error');
}
}
/**
* 显示上传结果
*/
displayUploadResult(result) {
console.log('');
if ('results' in result) {
// 批量上传结果
this.displayBatchResult(result);
}
else {
// 单个文件上传结果
this.displaySingleResult(result);
}
}
/**
* 显示单个文件上传结果
*/
displaySingleResult(result) {
if (result.success) {
console.log(chalk_1.default.green('✓ Upload completed successfully'));
console.log(chalk_1.default.gray(`File: ${result.fileName}`));
if (result.size) {
console.log(chalk_1.default.gray(`Size: ${(0, fileUtils_1.formatFileSize)(result.size)}`));
}
if (result.url) {
console.log(chalk_1.default.gray(`URL: ${result.url}`));
}
}
else {
console.log(chalk_1.default.red('✗ Upload failed'));
console.log(chalk_1.default.gray(`File: ${result.fileName}`));
if (result.error) {
console.log(chalk_1.default.red(`Error: ${result.error}`));
}
}
}
/**
* 显示批量上传结果
*/
displayBatchResult(result) {
const { totalFiles, successCount, failedCount, totalSize, duration } = result;
console.log(chalk_1.default.bold('Upload Summary:'));
console.log('─'.repeat(40));
console.log(`Total files: ${totalFiles}`);
console.log(`Successful: ${chalk_1.default.green(successCount)}`);
console.log(`Failed: ${failedCount > 0 ? chalk_1.default.red(failedCount) : failedCount}`);
console.log(`Total size: ${(0, fileUtils_1.formatFileSize)(totalSize)}`);
console.log(`Duration: ${(0, fileUtils_1.formatDuration)(duration)}`);
if (failedCount > 0) {
console.log('\nFailed files:');
result.results
.filter(r => !r.success)
.forEach(r => {
console.log(chalk_1.default.red(` ✗ ${r.fileName}: ${r.error}`));
});
}
if (successCount > 0) {
console.log('\nSuccessful uploads:');
result.results
.filter(r => r.success)
.slice(0, 5) // 只显示前5个成功的文件
.forEach(r => {
console.log(chalk_1.default.green(` ✓ ${r.fileName}`));
});
if (successCount > 5) {
console.log(chalk_1.default.gray(` ... and ${successCount - 5} more files`));
}
}
}
}
exports.UploadCommand = UploadCommand;
//# sourceMappingURL=UploadCommand.js.map