@kevisual/cnb 0.0.77 → 0.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/routes/repo/download.ts +23 -0
- package/agent/routes/repo/index.ts +2 -1
- package/dist/cli.js +22 -4
- package/dist/npc.js +22 -4
- package/dist/opencode.js +22 -4
- package/dist/routes.js +22 -4
- package/package.json +1 -1
- package/src/git/index.ts +79 -4
- package/src/repo/download.ts +114 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { downloadByCNBApi } from "@/repo/download.ts";
|
|
2
|
+
|
|
3
|
+
import { app } from '../../app.ts';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
// pnpm cli cnb download -- url=https://
|
|
7
|
+
app.route({
|
|
8
|
+
path: 'cnb',
|
|
9
|
+
key: 'download',
|
|
10
|
+
description: '下载文件',
|
|
11
|
+
middleware: ['auth'],
|
|
12
|
+
metadata: {
|
|
13
|
+
args: {
|
|
14
|
+
url: z.string().describe('文件下载链接'),
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
}).define(async (ctx) => {
|
|
18
|
+
const url = ctx.query?.url;
|
|
19
|
+
if (!url) {
|
|
20
|
+
ctx.throw(400, '缺少 url 参数');
|
|
21
|
+
}
|
|
22
|
+
//
|
|
23
|
+
}).addTo(app);
|
package/dist/cli.js
CHANGED
|
@@ -63216,6 +63216,24 @@ app.route({
|
|
|
63216
63216
|
ctx.forward(res);
|
|
63217
63217
|
}).addTo(app);
|
|
63218
63218
|
|
|
63219
|
+
// agent/routes/repo/download.ts
|
|
63220
|
+
app.route({
|
|
63221
|
+
path: "cnb",
|
|
63222
|
+
key: "download",
|
|
63223
|
+
description: "下载文件",
|
|
63224
|
+
middleware: ["auth"],
|
|
63225
|
+
metadata: {
|
|
63226
|
+
args: {
|
|
63227
|
+
url: exports_external2.string().describe("文件下载链接")
|
|
63228
|
+
}
|
|
63229
|
+
}
|
|
63230
|
+
}).define(async (ctx) => {
|
|
63231
|
+
const url4 = ctx.query?.url;
|
|
63232
|
+
if (!url4) {
|
|
63233
|
+
ctx.throw(400, "缺少 url 参数");
|
|
63234
|
+
}
|
|
63235
|
+
}).addTo(app);
|
|
63236
|
+
|
|
63219
63237
|
// agent/routes/workspace/skills.ts
|
|
63220
63238
|
app.route({
|
|
63221
63239
|
path: "cnb",
|
|
@@ -65701,7 +65719,7 @@ app.route({
|
|
|
65701
65719
|
};
|
|
65702
65720
|
}).addTo(app);
|
|
65703
65721
|
|
|
65704
|
-
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.
|
|
65722
|
+
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.96_zod@4.3.6/node_modules/@ai-sdk/gateway/dist/index.mjs
|
|
65705
65723
|
var import_oidc = __toESM(require_dist(), 1);
|
|
65706
65724
|
var import_oidc2 = __toESM(require_dist(), 1);
|
|
65707
65725
|
var marker17 = "vercel.ai.gateway.error";
|
|
@@ -66997,7 +67015,7 @@ async function getVercelRequestId() {
|
|
|
66997
67015
|
var _a92;
|
|
66998
67016
|
return (_a92 = import_oidc.getContext().headers) == null ? undefined : _a92["x-vercel-id"];
|
|
66999
67017
|
}
|
|
67000
|
-
var VERSION3 = "3.0.
|
|
67018
|
+
var VERSION3 = "3.0.96";
|
|
67001
67019
|
var AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
|
|
67002
67020
|
function createGatewayProvider(options = {}) {
|
|
67003
67021
|
var _a92, _b92;
|
|
@@ -67184,7 +67202,7 @@ async function getGatewayAuthToken(options) {
|
|
|
67184
67202
|
};
|
|
67185
67203
|
}
|
|
67186
67204
|
|
|
67187
|
-
// ../../node_modules/.pnpm/ai@6.0.
|
|
67205
|
+
// ../../node_modules/.pnpm/ai@6.0.159_zod@4.3.6/node_modules/ai/dist/index.mjs
|
|
67188
67206
|
var import_api = __toESM(require_src(), 1);
|
|
67189
67207
|
var import_api2 = __toESM(require_src(), 1);
|
|
67190
67208
|
var __defProp4 = Object.defineProperty;
|
|
@@ -67755,7 +67773,7 @@ function detectMediaType({
|
|
|
67755
67773
|
}
|
|
67756
67774
|
return;
|
|
67757
67775
|
}
|
|
67758
|
-
var VERSION4 = "6.0.
|
|
67776
|
+
var VERSION4 = "6.0.159";
|
|
67759
67777
|
var download = async ({
|
|
67760
67778
|
url: url4,
|
|
67761
67779
|
maxBytes,
|
package/dist/npc.js
CHANGED
|
@@ -61157,6 +61157,24 @@ app.route({
|
|
|
61157
61157
|
ctx.forward(res);
|
|
61158
61158
|
}).addTo(app);
|
|
61159
61159
|
|
|
61160
|
+
// agent/routes/repo/download.ts
|
|
61161
|
+
app.route({
|
|
61162
|
+
path: "cnb",
|
|
61163
|
+
key: "download",
|
|
61164
|
+
description: "下载文件",
|
|
61165
|
+
middleware: ["auth"],
|
|
61166
|
+
metadata: {
|
|
61167
|
+
args: {
|
|
61168
|
+
url: exports_external2.string().describe("文件下载链接")
|
|
61169
|
+
}
|
|
61170
|
+
}
|
|
61171
|
+
}).define(async (ctx) => {
|
|
61172
|
+
const url4 = ctx.query?.url;
|
|
61173
|
+
if (!url4) {
|
|
61174
|
+
ctx.throw(400, "缺少 url 参数");
|
|
61175
|
+
}
|
|
61176
|
+
}).addTo(app);
|
|
61177
|
+
|
|
61160
61178
|
// agent/routes/workspace/skills.ts
|
|
61161
61179
|
app.route({
|
|
61162
61180
|
path: "cnb",
|
|
@@ -63642,7 +63660,7 @@ app.route({
|
|
|
63642
63660
|
};
|
|
63643
63661
|
}).addTo(app);
|
|
63644
63662
|
|
|
63645
|
-
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.
|
|
63663
|
+
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.96_zod@4.3.6/node_modules/@ai-sdk/gateway/dist/index.mjs
|
|
63646
63664
|
var import_oidc = __toESM(require_dist(), 1);
|
|
63647
63665
|
var import_oidc2 = __toESM(require_dist(), 1);
|
|
63648
63666
|
var marker17 = "vercel.ai.gateway.error";
|
|
@@ -64938,7 +64956,7 @@ async function getVercelRequestId() {
|
|
|
64938
64956
|
var _a92;
|
|
64939
64957
|
return (_a92 = import_oidc.getContext().headers) == null ? undefined : _a92["x-vercel-id"];
|
|
64940
64958
|
}
|
|
64941
|
-
var VERSION3 = "3.0.
|
|
64959
|
+
var VERSION3 = "3.0.96";
|
|
64942
64960
|
var AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
|
|
64943
64961
|
function createGatewayProvider(options = {}) {
|
|
64944
64962
|
var _a92, _b92;
|
|
@@ -65125,7 +65143,7 @@ async function getGatewayAuthToken(options) {
|
|
|
65125
65143
|
};
|
|
65126
65144
|
}
|
|
65127
65145
|
|
|
65128
|
-
// ../../node_modules/.pnpm/ai@6.0.
|
|
65146
|
+
// ../../node_modules/.pnpm/ai@6.0.159_zod@4.3.6/node_modules/ai/dist/index.mjs
|
|
65129
65147
|
var import_api = __toESM(require_src(), 1);
|
|
65130
65148
|
var import_api2 = __toESM(require_src(), 1);
|
|
65131
65149
|
var __defProp4 = Object.defineProperty;
|
|
@@ -65696,7 +65714,7 @@ function detectMediaType({
|
|
|
65696
65714
|
}
|
|
65697
65715
|
return;
|
|
65698
65716
|
}
|
|
65699
|
-
var VERSION4 = "6.0.
|
|
65717
|
+
var VERSION4 = "6.0.159";
|
|
65700
65718
|
var download = async ({
|
|
65701
65719
|
url: url4,
|
|
65702
65720
|
maxBytes,
|
package/dist/opencode.js
CHANGED
|
@@ -61123,6 +61123,24 @@ app.route({
|
|
|
61123
61123
|
ctx.forward(res);
|
|
61124
61124
|
}).addTo(app);
|
|
61125
61125
|
|
|
61126
|
+
// agent/routes/repo/download.ts
|
|
61127
|
+
app.route({
|
|
61128
|
+
path: "cnb",
|
|
61129
|
+
key: "download",
|
|
61130
|
+
description: "下载文件",
|
|
61131
|
+
middleware: ["auth"],
|
|
61132
|
+
metadata: {
|
|
61133
|
+
args: {
|
|
61134
|
+
url: exports_external2.string().describe("文件下载链接")
|
|
61135
|
+
}
|
|
61136
|
+
}
|
|
61137
|
+
}).define(async (ctx) => {
|
|
61138
|
+
const url4 = ctx.query?.url;
|
|
61139
|
+
if (!url4) {
|
|
61140
|
+
ctx.throw(400, "缺少 url 参数");
|
|
61141
|
+
}
|
|
61142
|
+
}).addTo(app);
|
|
61143
|
+
|
|
61126
61144
|
// agent/routes/workspace/skills.ts
|
|
61127
61145
|
app.route({
|
|
61128
61146
|
path: "cnb",
|
|
@@ -63608,7 +63626,7 @@ app.route({
|
|
|
63608
63626
|
};
|
|
63609
63627
|
}).addTo(app);
|
|
63610
63628
|
|
|
63611
|
-
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.
|
|
63629
|
+
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.96_zod@4.3.6/node_modules/@ai-sdk/gateway/dist/index.mjs
|
|
63612
63630
|
var import_oidc = __toESM(require_dist(), 1);
|
|
63613
63631
|
var import_oidc2 = __toESM(require_dist(), 1);
|
|
63614
63632
|
var marker17 = "vercel.ai.gateway.error";
|
|
@@ -64904,7 +64922,7 @@ async function getVercelRequestId() {
|
|
|
64904
64922
|
var _a92;
|
|
64905
64923
|
return (_a92 = import_oidc.getContext().headers) == null ? undefined : _a92["x-vercel-id"];
|
|
64906
64924
|
}
|
|
64907
|
-
var VERSION3 = "3.0.
|
|
64925
|
+
var VERSION3 = "3.0.96";
|
|
64908
64926
|
var AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
|
|
64909
64927
|
function createGatewayProvider(options = {}) {
|
|
64910
64928
|
var _a92, _b92;
|
|
@@ -65091,7 +65109,7 @@ async function getGatewayAuthToken(options) {
|
|
|
65091
65109
|
};
|
|
65092
65110
|
}
|
|
65093
65111
|
|
|
65094
|
-
// ../../node_modules/.pnpm/ai@6.0.
|
|
65112
|
+
// ../../node_modules/.pnpm/ai@6.0.159_zod@4.3.6/node_modules/ai/dist/index.mjs
|
|
65095
65113
|
var import_api = __toESM(require_src(), 1);
|
|
65096
65114
|
var import_api2 = __toESM(require_src(), 1);
|
|
65097
65115
|
var __defProp4 = Object.defineProperty;
|
|
@@ -65662,7 +65680,7 @@ function detectMediaType({
|
|
|
65662
65680
|
}
|
|
65663
65681
|
return;
|
|
65664
65682
|
}
|
|
65665
|
-
var VERSION4 = "6.0.
|
|
65683
|
+
var VERSION4 = "6.0.159";
|
|
65666
65684
|
var download = async ({
|
|
65667
65685
|
url: url4,
|
|
65668
65686
|
maxBytes,
|
package/dist/routes.js
CHANGED
|
@@ -61123,6 +61123,24 @@ app.route({
|
|
|
61123
61123
|
ctx.forward(res);
|
|
61124
61124
|
}).addTo(app);
|
|
61125
61125
|
|
|
61126
|
+
// agent/routes/repo/download.ts
|
|
61127
|
+
app.route({
|
|
61128
|
+
path: "cnb",
|
|
61129
|
+
key: "download",
|
|
61130
|
+
description: "下载文件",
|
|
61131
|
+
middleware: ["auth"],
|
|
61132
|
+
metadata: {
|
|
61133
|
+
args: {
|
|
61134
|
+
url: exports_external2.string().describe("文件下载链接")
|
|
61135
|
+
}
|
|
61136
|
+
}
|
|
61137
|
+
}).define(async (ctx) => {
|
|
61138
|
+
const url4 = ctx.query?.url;
|
|
61139
|
+
if (!url4) {
|
|
61140
|
+
ctx.throw(400, "缺少 url 参数");
|
|
61141
|
+
}
|
|
61142
|
+
}).addTo(app);
|
|
61143
|
+
|
|
61126
61144
|
// agent/routes/workspace/skills.ts
|
|
61127
61145
|
app.route({
|
|
61128
61146
|
path: "cnb",
|
|
@@ -63608,7 +63626,7 @@ app.route({
|
|
|
63608
63626
|
};
|
|
63609
63627
|
}).addTo(app);
|
|
63610
63628
|
|
|
63611
|
-
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.
|
|
63629
|
+
// ../../node_modules/.pnpm/@ai-sdk+gateway@3.0.96_zod@4.3.6/node_modules/@ai-sdk/gateway/dist/index.mjs
|
|
63612
63630
|
var import_oidc = __toESM(require_dist(), 1);
|
|
63613
63631
|
var import_oidc2 = __toESM(require_dist(), 1);
|
|
63614
63632
|
var marker17 = "vercel.ai.gateway.error";
|
|
@@ -64904,7 +64922,7 @@ async function getVercelRequestId() {
|
|
|
64904
64922
|
var _a92;
|
|
64905
64923
|
return (_a92 = import_oidc.getContext().headers) == null ? undefined : _a92["x-vercel-id"];
|
|
64906
64924
|
}
|
|
64907
|
-
var VERSION3 = "3.0.
|
|
64925
|
+
var VERSION3 = "3.0.96";
|
|
64908
64926
|
var AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
|
|
64909
64927
|
function createGatewayProvider(options = {}) {
|
|
64910
64928
|
var _a92, _b92;
|
|
@@ -65091,7 +65109,7 @@ async function getGatewayAuthToken(options) {
|
|
|
65091
65109
|
};
|
|
65092
65110
|
}
|
|
65093
65111
|
|
|
65094
|
-
// ../../node_modules/.pnpm/ai@6.0.
|
|
65112
|
+
// ../../node_modules/.pnpm/ai@6.0.159_zod@4.3.6/node_modules/ai/dist/index.mjs
|
|
65095
65113
|
var import_api = __toESM(require_src(), 1);
|
|
65096
65114
|
var import_api2 = __toESM(require_src(), 1);
|
|
65097
65115
|
var __defProp4 = Object.defineProperty;
|
|
@@ -65662,7 +65680,7 @@ function detectMediaType({
|
|
|
65662
65680
|
}
|
|
65663
65681
|
return;
|
|
65664
65682
|
}
|
|
65665
|
-
var VERSION4 = "6.0.
|
|
65683
|
+
var VERSION4 = "6.0.159";
|
|
65666
65684
|
var download = async ({
|
|
65667
65685
|
url: url4,
|
|
65668
65686
|
maxBytes,
|
package/package.json
CHANGED
package/src/git/index.ts
CHANGED
|
@@ -69,11 +69,12 @@ export type Content = {
|
|
|
69
69
|
html_url: string;
|
|
70
70
|
git_url: string;
|
|
71
71
|
download_url: string;
|
|
72
|
-
type:
|
|
72
|
+
type: 'lfs' | 'blob';
|
|
73
73
|
content?: string;
|
|
74
|
-
encoding?:
|
|
74
|
+
encoding?: 'base64' | 'utf-8';
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
|
|
77
78
|
/**
|
|
78
79
|
* Git Blob 对象
|
|
79
80
|
*/
|
|
@@ -307,7 +308,7 @@ export class Git extends CNBCore {
|
|
|
307
308
|
* @param params 查询参数
|
|
308
309
|
* @returns 文件或目录内容
|
|
309
310
|
*/
|
|
310
|
-
async getContent(repo: string, params?: GetContentParams): Promise<Result<
|
|
311
|
+
async getContent(repo: string, params?: GetContentParams): Promise<Result<FileListContent | FileContent>> {
|
|
311
312
|
const url = `/${repo}/-/git/contents`;
|
|
312
313
|
return this.get({ url, params });
|
|
313
314
|
}
|
|
@@ -319,11 +320,46 @@ export class Git extends CNBCore {
|
|
|
319
320
|
* @param params 查询参数
|
|
320
321
|
* @returns 文件内容
|
|
321
322
|
*/
|
|
322
|
-
async getContentWithPath(repo: string, filePath: string, params?: GetContentWithPathParams): Promise<Result<
|
|
323
|
+
async getContentWithPath(repo: string, filePath: string, params?: GetContentWithPathParams): Promise<Result<FileListContent | FileContent>> {
|
|
323
324
|
const url = `/${repo}/-/git/contents/${filePath}`;
|
|
324
325
|
return this.get({ url, params });
|
|
325
326
|
}
|
|
326
327
|
|
|
328
|
+
async getAllContentWithPath(repo: string, filePath: string = '', opts: { recursive?: boolean } & GetContentParams): Promise<FileListContent['entries']> {
|
|
329
|
+
try {
|
|
330
|
+
const { recursive, ...rest } = opts;
|
|
331
|
+
const res = await this.getContentWithPath(repo, filePath, rest);
|
|
332
|
+
if (res.code !== 200) {
|
|
333
|
+
console.log('获取文件列表失败', repo, filePath, res);
|
|
334
|
+
return [];
|
|
335
|
+
} else if (!res.data) {
|
|
336
|
+
console.log('列表为空', res);
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
const fileList = res.data as unknown as FileListContent;
|
|
340
|
+
const entries = fileList.entries || [];
|
|
341
|
+
console.log('文件列表', fileList);
|
|
342
|
+
const treeList = entries.filter(item => item.type === 'tree');
|
|
343
|
+
const fileListResult = entries.filter(item => item.type === 'blob');
|
|
344
|
+
if (recursive) {
|
|
345
|
+
for (const tree of treeList) {
|
|
346
|
+
let treePath = tree.path;
|
|
347
|
+
if (tree.path.includes('/')) {
|
|
348
|
+
treePath = tree.name;
|
|
349
|
+
}
|
|
350
|
+
let newPath = filePath ? `${filePath}/${treePath}` : treePath;
|
|
351
|
+
const subFileList = await this.getAllContentWithPath(`${repo}`, newPath, { recursive });
|
|
352
|
+
fileListResult.push(...subFileList);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return fileListResult;
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
console.error('获取仓库文件列表失败', error);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
327
363
|
/**
|
|
328
364
|
* 获取原始文件内容
|
|
329
365
|
* @param repo 仓库名称,格式:组织名称/仓库名称
|
|
@@ -674,3 +710,42 @@ type PutTagAnnotationsData = {
|
|
|
674
710
|
/** 注释键值对 */
|
|
675
711
|
annotations: Record<string, string>;
|
|
676
712
|
};
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
export type FileListContent = {
|
|
716
|
+
type: 'blob' | 'tree';
|
|
717
|
+
size: number;
|
|
718
|
+
path: string;
|
|
719
|
+
name: string;
|
|
720
|
+
sha: string;
|
|
721
|
+
url: string;
|
|
722
|
+
entries?: FIleEntry[];
|
|
723
|
+
}
|
|
724
|
+
type FIleEntry = {
|
|
725
|
+
type: 'blob' | 'tree';
|
|
726
|
+
sha: string;
|
|
727
|
+
path: string;
|
|
728
|
+
name: string;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
type FileContent = FileContentBase64 | FileContentLFS;
|
|
732
|
+
type FileContentBase64 = {
|
|
733
|
+
type: 'blob';
|
|
734
|
+
size: number;
|
|
735
|
+
path: string;
|
|
736
|
+
name: string;
|
|
737
|
+
sha: string;
|
|
738
|
+
encoding: 'base64';
|
|
739
|
+
content: string;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
type FileContentLFS = {
|
|
743
|
+
type: 'lfs';
|
|
744
|
+
size: number;
|
|
745
|
+
path: string;
|
|
746
|
+
name: string;
|
|
747
|
+
sha: string;
|
|
748
|
+
lfs_oid: string;
|
|
749
|
+
lfs_size: number;
|
|
750
|
+
lfs_download_url: string;
|
|
751
|
+
}
|
package/src/repo/download.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
type DownloadFn = (url: string, opts?: { headers?: any, hash?: string, token?: string }) => Promise<{
|
|
1
|
+
type DownloadFn = (url: string, opts?: { headers?: any, hash?: string, token?: string, stream?: boolean }) => Promise<{
|
|
2
|
+
code: number;
|
|
3
|
+
buffer: Buffer | null;
|
|
4
|
+
stream?: ReadableStream<Uint8Array> | null;
|
|
5
|
+
}>;
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* 下载文件内容,使用CNB API,支持文件未修改时返回304状态码
|
|
@@ -17,6 +21,7 @@ export const downloadByCNBApi: DownloadFn = async (url, opts) => {
|
|
|
17
21
|
const res = await fetch(url, {
|
|
18
22
|
headers: headers
|
|
19
23
|
});
|
|
24
|
+
const stream = opts?.stream || false;
|
|
20
25
|
type CNBResponse = {
|
|
21
26
|
type?: 'blob' | 'file' | 'dir' | 'lfs';
|
|
22
27
|
size?: number;
|
|
@@ -48,6 +53,15 @@ export const downloadByCNBApi: DownloadFn = async (url, opts) => {
|
|
|
48
53
|
return { code: 304, buffer: null };
|
|
49
54
|
}
|
|
50
55
|
const buffer = Buffer.from(resJson.content, 'base64');
|
|
56
|
+
if (stream) {
|
|
57
|
+
const readableStream = new ReadableStream<Uint8Array>({
|
|
58
|
+
start(controller) {
|
|
59
|
+
controller.enqueue(buffer);
|
|
60
|
+
controller.close();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return { code: 200, buffer: null, stream: readableStream };
|
|
64
|
+
}
|
|
51
65
|
return { code: 200, buffer };
|
|
52
66
|
}
|
|
53
67
|
}
|
|
@@ -63,6 +77,10 @@ export const downloadByCNBApi: DownloadFn = async (url, opts) => {
|
|
|
63
77
|
// 文件未修改,跳过下载
|
|
64
78
|
return { code: 304, buffer: null };
|
|
65
79
|
}
|
|
80
|
+
if (stream) {
|
|
81
|
+
const readableStream = lfsRes.body;
|
|
82
|
+
return { code: 200, buffer: null, stream: readableStream };
|
|
83
|
+
}
|
|
66
84
|
const arrayBuffer = await lfsRes.arrayBuffer();
|
|
67
85
|
return { code: 200, buffer: Buffer.from(arrayBuffer) };
|
|
68
86
|
}
|
|
@@ -75,4 +93,99 @@ export const downloadByCNBApi: DownloadFn = async (url, opts) => {
|
|
|
75
93
|
console.error(`下载失败 ${url}: ${res.statusText}`, resJson);
|
|
76
94
|
return { code: res.status, buffer: null };
|
|
77
95
|
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param url
|
|
100
|
+
* @param opts
|
|
101
|
+
* @returns
|
|
102
|
+
*/
|
|
103
|
+
export const downloadByLFSUrl: DownloadFn = async (url, opts) => {
|
|
104
|
+
const headers = {
|
|
105
|
+
Accept: "application/vnd.cnb.api+json",
|
|
106
|
+
// Authorization: `${CNB_API_KEY}`
|
|
107
|
+
...(opts?.token ? { Authorization: `${opts.token}` } : {}),
|
|
108
|
+
...opts?.headers
|
|
109
|
+
}
|
|
110
|
+
const res = await fetch(url, { headers });
|
|
111
|
+
const stream = opts?.stream || false;
|
|
112
|
+
if (res.ok) {
|
|
113
|
+
if (stream) {
|
|
114
|
+
const readableStream = res.body;
|
|
115
|
+
return { code: 200, buffer: null, stream: readableStream };
|
|
116
|
+
}
|
|
117
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
118
|
+
return { code: 200, buffer: Buffer.from(arrayBuffer) };
|
|
119
|
+
}
|
|
120
|
+
console.error(`下载失败 ${url}: ${res.statusText}`);
|
|
121
|
+
return { code: res.status, buffer: null };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
export const downloadResource: DownloadFn = async (url, opts) => {
|
|
126
|
+
const headers = opts?.headers || {};
|
|
127
|
+
const res = await fetch(url, { headers });
|
|
128
|
+
const stream = opts?.stream || false;
|
|
129
|
+
const etag = res.headers.get('ETag');
|
|
130
|
+
if (opts?.hash && etag === opts.hash) {
|
|
131
|
+
// 文件未修改,跳过下载
|
|
132
|
+
return { code: 304, buffer: null };
|
|
133
|
+
}
|
|
134
|
+
if (res.ok) {
|
|
135
|
+
if (stream) {
|
|
136
|
+
const readableStream = res.body;
|
|
137
|
+
return { code: 200, buffer: null, stream: readableStream };
|
|
138
|
+
}
|
|
139
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
140
|
+
return { code: 200, buffer: Buffer.from(arrayBuffer) };
|
|
141
|
+
}
|
|
142
|
+
console.error(`下载失败 ${url}: ${res.statusText}`);
|
|
143
|
+
return { code: res.status, buffer: null };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const isCNBOpenApi = (url: string) => { return url.startsWith('https://api.cnb.cool') }
|
|
147
|
+
|
|
148
|
+
export const download: DownloadFn = async (url, opts) => {
|
|
149
|
+
const isCNB = isCNBOpenApi(url);
|
|
150
|
+
if (isCNB) {
|
|
151
|
+
return await downloadByCNBApi(url, opts);
|
|
152
|
+
} else {
|
|
153
|
+
return await downloadResource(url, opts);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const getLfsDownloadUrl = async (url: string, opts?: { headers?: any, token?: string }) => {
|
|
158
|
+
const headers = {
|
|
159
|
+
Accept: "application/vnd.cnb.api+json",
|
|
160
|
+
...(opts?.token ? { Authorization: `${opts.token}` } : {}),
|
|
161
|
+
...opts?.headers
|
|
162
|
+
}
|
|
163
|
+
const res = await fetch(url, {
|
|
164
|
+
headers: headers
|
|
165
|
+
});
|
|
166
|
+
type CNBResponse = {
|
|
167
|
+
type?: 'blob' | 'file' | 'dir' | 'lfs';
|
|
168
|
+
size?: number;
|
|
169
|
+
path: string;
|
|
170
|
+
name: string;
|
|
171
|
+
sha?: string;
|
|
172
|
+
encoding?: string;
|
|
173
|
+
content?: string;
|
|
174
|
+
|
|
175
|
+
lfs_oid?: string;
|
|
176
|
+
lfs_size?: number;
|
|
177
|
+
lfs_download_url?: string;
|
|
178
|
+
}
|
|
179
|
+
let resJson: CNBResponse;
|
|
180
|
+
try {
|
|
181
|
+
resJson = await res.json() as CNBResponse;
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error(`获取LFS下载链接失败 ${url}: ${res.statusText}`, e);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (res.ok && resJson.type === 'lfs' && resJson.lfs_download_url) {
|
|
187
|
+
return resJson.lfs_download_url;
|
|
188
|
+
}
|
|
189
|
+
console.error(`获取LFS下载链接失败 ${url}: ${res.statusText}`, resJson);
|
|
190
|
+
return null;
|
|
78
191
|
}
|