bun 多文件上传会有问题

This commit is contained in:
熊潇 2025-05-11 21:57:54 +08:00
parent cdbebe92d0
commit 4aec2bc231
9 changed files with 510 additions and 691 deletions

View File

@ -25,7 +25,8 @@
"bun.config.mjs" "bun.config.mjs"
], ],
"scripts": { "scripts": {
"dev": "bun run src/run.ts ", "dev": "bun src/run.ts ",
"dev:tsx": "tsx src/run.ts ",
"build": "rimraf dist && bun run bun.config.mjs", "build": "rimraf dist && bun run bun.config.mjs",
"postbuild": "cd assistant && pnpm build", "postbuild": "cd assistant && pnpm build",
"dts": "dts-bundle-generator --external-inlines=@types/jsonwebtoken src/index.ts -o dist/index.d.ts ", "dts": "dts-bundle-generator --external-inlines=@types/jsonwebtoken src/index.ts -o dist/index.d.ts ",
@ -43,23 +44,23 @@
"@kevisual/load": "^0.0.6", "@kevisual/load": "^0.0.6",
"@kevisual/query": "0.0.17", "@kevisual/query": "0.0.17",
"@kevisual/query-login": "0.0.5", "@kevisual/query-login": "0.0.5",
"@types/bun": "^1.2.10", "@types/bun": "^1.2.13",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
"@types/node": "^22.14.1", "@types/node": "^22.15.17",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"commander": "^13.1.0", "commander": "^13.1.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"filesize": "^10.1.6", "filesize": "^10.1.6",
"form-data": "^4.0.2", "form-data": "^4.0.2",
"ignore": "^7.0.3", "ignore": "^7.0.4",
"inquirer": "^12.5.2", "inquirer": "^12.6.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"rollup": "^4.40.0", "rollup": "^4.40.2",
"rollup-plugin-dts": "^6.2.1", "rollup-plugin-dts": "^6.2.1",
"tar": "^7.4.3", "tar": "^7.4.3",
"zustand": "^5.0.3" "zustand": "^5.0.4"
}, },
"engines": { "engines": {
"node": ">=22.0.0" "node": ">=22.0.0"

962
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
import { chalk } from '@/module/chalk.ts'; import { chalk } from '@/module/chalk.ts';
import { program, Command } from '../../../program.ts'; import { program, Command } from '../../../program.ts';
import { queryApp } from '../../../query/app-manager/query-app.ts'; import { queryApp } from '../../../query/app-manager/query-app.ts';
import { checkAppDir, installApp, uninstallApp } from '@/module/download/install.ts'; import { checkAppDir, installApp, uninstallApp, fetchLink } from '@/module/download/install.ts';
import { fileIsExist } from '@/uitls/file.ts'; import { fileIsExist } from '@/uitls/file.ts';
import fs from 'fs'; import fs from 'fs';
import { getConfig } from '@/module/get-config.ts'; import { getConfig } from '@/module/get-config.ts';
@ -156,3 +156,20 @@ const uninstallAppCommand = new Command('uninstall')
appCommand.addCommand(downloadAppCommand); appCommand.addCommand(downloadAppCommand);
appCommand.addCommand(uninstallAppCommand); appCommand.addCommand(uninstallAppCommand);
const link = new Command('link')
.argument('url')
.option('-o --output <output>', '输出目录')
.action(async (url, options) => {
const output = options.output || '';
const { content, filename } = await fetchLink(url, { returnContent: true });
if (output) {
const checkOutput = fileIsExist(output);
if (!checkOutput) {
fs.mkdirSync(output, { recursive: true });
}
}
fs.writeFileSync(path.join(output, filename), content);
});
appCommand.addCommand(link);

View File

@ -9,7 +9,9 @@ import inquirer from 'inquirer';
import { packLib, unpackLib } from './publish.ts'; import { packLib, unpackLib } from './publish.ts';
import chalk from 'chalk'; import chalk from 'chalk';
import { installDeps } from '@/uitls/npm.ts'; import { installDeps } from '@/uitls/npm.ts';
import { MD5 } from 'crypto-js'; import cryptojs from 'crypto-js';
import { upload } from '@/module/download/upload.ts';
const MD5 = cryptojs.MD5;
/** /**
* package.json basename, version, user, appKey * package.json basename, version, user, appKey
* @returns * @returns
@ -209,8 +211,10 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO
console.log('文件已经上传过了', file); console.log('文件已经上传过了', file);
continue; continue;
} }
const filename = path.basename(filePath);
console.log('upload file', file, filename);
form.append('file', fs.createReadStream(filePath), { form.append('file', fs.createReadStream(filePath), {
filename: file, filename: filename,
filepath: file, filepath: file,
}); });
needUpload = true; needUpload = true;
@ -221,50 +225,12 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO
code: 200, code: 200,
}; };
} }
const _baseURL = getBaseURL();
return new Promise(async (resolve) => { const url = new URL('/api/s1/resources/upload', _baseURL);
const _baseURL = getBaseURL(); if (opts.noCheckAppFiles) {
const url = new URL('/api/s1/resources/upload', _baseURL); url.searchParams.append('noCheckAppFiles', 'true');
const searchParams = url.searchParams; }
if (opts.noCheckAppFiles) { return upload({ url: url, form: form, token: token });
searchParams.append('noCheckAppFiles', 'true');
}
console.log('upload url', url.hostname, url.protocol, url.port);
const pathname = url.href.toString().replace(_baseURL.toString(), '');
form.submit(
{
path: pathname,
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); app.addCommand(command);

View File

@ -1,8 +1,11 @@
import { program as app, Command } from '@/program.ts'; import { program as app, Command } from '@/program.ts';
const command = new Command('sync').description('同步项目').action(() => { const command = new Command('sync')
console.log('同步项目'); .option('-d --dir <dir>')
}); .description('同步项目')
.action(() => {
console.log('同步项目');
});
const syncUpload = new Command('upload').description('上传项目').action(() => { const syncUpload = new Command('upload').description('上传项目').action(() => {
console.log('上传项目'); console.log('上传项目');
}); });

View File

@ -1,6 +1,6 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { storage } from '../query.ts'; import { storage, baseURL } from '../query.ts';
import { chalk } from '../chalk.ts'; import { chalk } from '../chalk.ts';
type DownloadTask = { type DownloadTask = {
@ -20,6 +20,40 @@ export type Package = {
key?: string; key?: string;
[key: string]: any; [key: string]: any;
}; };
type Options = {
check?: boolean;
returnContent?: boolean;
[key: string]: any;
};
export const fetchLink = async (url: string, opts?: Options) => {
const token = process.env.KEVISUAL_TOKEN || storage.getItem('token');
const fetchURL = new URL(url);
const check = opts?.check ?? false;
if (check) {
if (!url.startsWith(baseURL)) {
throw new Error('url must start with ' + baseURL);
}
}
if (token) {
fetchURL.searchParams.set('token', token);
}
fetchURL.searchParams.set('download', 'true');
const res = await fetch(fetchURL.toString());
const blob = await res.blob();
const type = blob.type;
let content: Buffer | undefined;
if (opts?.returnContent) {
content = Buffer.from(await blob.arrayBuffer());
}
const pathname = fetchURL.pathname;
const filename = pathname.split('/').pop();
return {
filename,
blob,
type,
content,
};
};
type InstallAppOpts = { type InstallAppOpts = {
appDir?: string; appDir?: string;
kevisualUrl?: string; kevisualUrl?: string;
@ -65,20 +99,12 @@ export const installApp = async (app: Package, opts: InstallAppOpts = {}) => {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
console.log('downloadUrwl', downloadUrl); console.log('downloadUrwl', downloadUrl);
const token = process.env.KEVISUAL_TOKEN || storage.getItem('token'); const { blob, type } = await fetchLink(downloadUrl);
const fetchURL = new URL(downloadUrl);
if (token) {
fetchURL.searchParams.set('token', token);
}
fetchURL.searchParams.set('download', 'true');
const res = await fetch(fetchURL.toString());
const blob = await res.blob();
const type = blob.type;
if (type.includes('text/html')) { if (type.includes('text/html')) {
const html = await blob.text(); const html = await blob.text();
if (html === 'fetchRes is error') { if (html === 'fetchRes is error') {
console.log(chalk.red('fetchRes is error')); console.log(chalk.red('fetchRes is error'), '下载失败', downloadUrl);
break; throw new Error('fetchRes is error');
} }
} }
fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer()));

View File

@ -0,0 +1,63 @@
import FormData from 'form-data';
export const handleResponse = async (err: any, res: any) => {
return new Promise((resolve) => {
if (err) {
console.error('Upload failed:', err);
resolve({ code: 500, message: err });
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 });
}
});
});
};
export const getFormParams = (opts: UploadOptions, headers: any): FormData.SubmitOptions => {
const url = new URL(opts.url);
if (opts.token) {
// url.searchParams.append('token', opts.token);
}
const value: FormData.SubmitOptions = {
path: url.pathname + url.search,
host: url.hostname,
method: 'POST',
protocol: url.protocol === 'https:' ? 'https:' : 'http:',
port: url.port || (url.protocol === 'https:' ? 443 : 80),
headers: {
Authorization: 'Bearer ' + opts.token,
...headers,
},
};
return value;
};
type UploadOptions = {
url: string | URL;
file?: string | Buffer | File;
token?: string;
form?: FormData;
};
export const upload = (opts: UploadOptions): Promise<{ code?: number; message?: string; [key: string]: any }> => {
const form = opts?.form || new FormData();
if (!opts.form) {
if (typeof opts.file === 'string') {
form.append('file', Buffer.from(opts.file));
} else {
form.append('file', opts.file);
}
}
const headers = form.getHeaders();
return new Promise((resolve) => {
form.submit(getFormParams(opts, headers), (err, res) => {
handleResponse(err, res).then(resolve);
});
});
};

1
src/scripts/summary.md Normal file
View File

@ -0,0 +1 @@
# 汇总 23333

24
src/scripts/upload.ts Normal file
View File

@ -0,0 +1,24 @@
import { fetchLink } from '@/module/download/install.ts';
import { baseURL, storage } from '@/module/query.ts';
import { upload } from '@/module/download/upload.ts';
import fs from 'node:fs';
import path from 'node:path';
const scriptPath = path.join(process.cwd(), 'src', 'scripts');
const sum = 'https://kevisual.xiongxiao.me/root/ai/kevisual/01-summary.md';
const download = async () => {
const { content } = await fetchLink(sum, { returnContent: true });
console.log(content.toString());
};
// download();
const uploadTest = async () => {
const file = fs.readFileSync(path.join(scriptPath, './summary.md'));
const token = storage.getItem('token');
// const res = await upload({ url: sum, file: '# 汇总 123', token });
const res = await upload({ url: sum, file: file, token });
console.log(res);
};
uploadTest();