/** * download 命令模块 * 用于从 Kevisual 平台下载项目文件 */ import path from 'path'; import fs from 'fs'; import { queryLogin } from '@/module/query.ts'; import { program, Command } from '@/program.ts'; import { fetchLink } from '@/module/download/install.ts'; import { chalk } from '@/module/chalk.ts'; /** * 文件项类型定义 * 代表从 Kevisual 平台获取的文件元信息 */ export type FileItem = { /** 文件名称,包含完整路径 */ name: string; /** 文件大小(字节) */ size: number; /** 最后修改时间 */ lastModified: string; /** 文件 ETag 值,用于缓存验证 */ etag: string; /** 相对路径 */ path: string; /** 完整路径名 */ pathname: string; /** 文件下载 URL */ url: string; }; const downloadCmd = new Command('download') .description('下载项目') .option('-l, --link ', '下载链接') .option('-d, --directory ', '下载目录', process.cwd()) .action(async (opts) => { let link = opts.link || ''; if (!link) { console.log('请提供下载链接'); return; } let url = new URL(link); if (!url.pathname.endsWith('/')) { url.pathname += '/'; } url.searchParams.set('recursive', 'true'); const directory = opts.directory || process.cwd(); const token = await queryLogin.getToken(); const res = await queryLogin.query.fetchText({ url: url.toString(), method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (res.code === 200 && res.data) { const files = res.data as FileItem[]; console.log(`获取到 ${files.length} 个文件`); await downloadFiles(files, { directory }); } else { console.log(chalk.red('获取文件列表失败:'), res.message || '未知错误'); } }); program.addCommand(downloadCmd); /** * 下载文件列表 * @param files 文件列表 * @param opts 下载选项 * @param opts.directory 下载目录,默认为当前目录 */ export const downloadFiles = async (files: FileItem[], opts?: { directory?: string }) => { const directory = opts?.directory || process.cwd(); let successCount = 0; let failCount = 0; for (const file of files) { try { const downloadPath = path.join(directory, file.path); const dir = path.dirname(downloadPath); // 创建目录 if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // 下载文件 console.log(`下载中: ${file.name}`); const { blob, type } = await fetchLink(file.url); // 检查是否为错误响应 if (type.includes('text/html')) { const text = await blob.text(); if (text === 'fetchRes is error') { console.log(chalk.red('下载失败:'), file.name); failCount++; continue; } } // 写入文件 fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); successCount++; console.log(chalk.green('下载成功:'), file.name); } catch (error) { failCount++; console.log(chalk.red('下载失败:'), file.name, error); } } console.log(chalk.blue('下载完成')); console.log(chalk.green(`成功: ${successCount}`)); console.log(chalk.red(`失败: ${failCount}`)); return { successCount, failCount, }; };