@catchmexz/fedin-vibe-mcp-server 0.1.14 → 1.0.0
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/dist/operations/codeup/branches.js +2 -2
- package/dist/operations/codeup/files.js +72 -59
- package/dist/operations/codeup/repositories.js +6 -6
- package/dist/operations/codeup/types.js +0 -5
- package/dist/operations/deploy/deploy.js +141 -561
- package/dist/tool-handlers/code-management.js +7 -7
- package/dist/tool-handlers/index.js +0 -2
- package/dist/tool-registry/index.js +0 -2
- package/package.json +7 -3
|
@@ -7,7 +7,7 @@ import { CodeupBranchSchema } from "./types.js";
|
|
|
7
7
|
* @param branch
|
|
8
8
|
* @param ref
|
|
9
9
|
*/
|
|
10
|
-
export async function createBranchFunc(
|
|
10
|
+
export async function createBranchFunc(repositoryId, branch, ref = "master") {
|
|
11
11
|
// Automatically handle unencoded slashes in repositoryId
|
|
12
12
|
if (repositoryId.includes("/")) {
|
|
13
13
|
// Found unencoded slash, automatically URL encode it
|
|
@@ -19,7 +19,7 @@ export async function createBranchFunc(organizationId, repositoryId, branch, ref
|
|
|
19
19
|
repositoryId = `${parts[0]}%2F${formattedEncodedName}`;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
const baseUrl = `/oapi/v1/codeup/organizations
|
|
22
|
+
const baseUrl = `/oapi/v1/codeup/organizations/5fc86883f9b3ccf7cd2cbd89/repositories/${repositoryId}/branches`;
|
|
23
23
|
// Build query parameters
|
|
24
24
|
const queryParams = {
|
|
25
25
|
branch: branch,
|
|
@@ -251,7 +251,7 @@ export async function createFileFunc(organizationId, repositoryId, filePath, con
|
|
|
251
251
|
});
|
|
252
252
|
return CreateFileResponseSchema.parse(response);
|
|
253
253
|
}
|
|
254
|
-
export async function pushFilesFunc(
|
|
254
|
+
export async function pushFilesFunc(projectId, currntProjectPath, commitMessage) {
|
|
255
255
|
let repositoryId = "";
|
|
256
256
|
// 读取 .gitignore 模式
|
|
257
257
|
// const ignorePatterns = readGitignore(projectPath);
|
|
@@ -268,30 +268,35 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
268
268
|
".svelte-kit",
|
|
269
269
|
".DS_Store"
|
|
270
270
|
];
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
271
|
+
if (isPureNumber(projectId)) {
|
|
272
|
+
repositoryId = projectId;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const selectProjectQuery = `SELECT id FROM chat_history WHERE id = '${projectId}'`;
|
|
276
|
+
const projectResult = await fedinQueryRequest("api/supabase/query", selectProjectQuery);
|
|
277
|
+
if (!projectResult.length) {
|
|
278
|
+
throw new Error("当前项目不存在");
|
|
279
|
+
}
|
|
280
|
+
const query = `SELECT ch.id
|
|
281
|
+
FROM chat_history ch
|
|
282
|
+
JOIN codeup_tokens ct ON ch.user_id = ct.user_id
|
|
283
|
+
WHERE ct.token = '${process.env.FEDIN_ACCESS_TOKEN}';`;
|
|
284
|
+
const projects = await fedinQueryRequest("api/supabase/query", query);
|
|
285
|
+
if (!projects.length) {
|
|
286
|
+
throw new Error("token密钥未找到对应项目,请确保密钥的可用性");
|
|
287
|
+
}
|
|
288
|
+
// 判断查到的projects是否存在projectId
|
|
289
|
+
const project = projects.find((project) => project.id === projectId);
|
|
290
|
+
if (!project) {
|
|
291
|
+
throw new Error("无权限访问当前项目仓库");
|
|
292
|
+
}
|
|
293
|
+
const queryCodeUp = `SELECT "repositoryId" FROM codeup WHERE chat_id = '${projectId}'`;
|
|
294
|
+
const codeupDetail = await fedinQueryRequest("api/supabase/query", queryCodeUp);
|
|
295
|
+
if (!codeupDetail.length) {
|
|
296
|
+
throw new Error("当前项目仓库不存在,请先创建仓库");
|
|
297
|
+
}
|
|
298
|
+
repositoryId = codeupDetail[0].repositoryId;
|
|
299
|
+
}
|
|
295
300
|
if (!repositoryId) {
|
|
296
301
|
throw new Error("当前项目仓库不存在,请先到飞钉AI推送项目代码");
|
|
297
302
|
}
|
|
@@ -310,7 +315,7 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
310
315
|
else if (hasViteConfig) {
|
|
311
316
|
projectType = "oss";
|
|
312
317
|
}
|
|
313
|
-
const remoteFileList = await listFilesFunc(
|
|
318
|
+
const remoteFileList = await listFilesFunc("5fc86883f9b3ccf7cd2cbd89", repositoryId, undefined, "master", "RECURSIVE");
|
|
314
319
|
const remoteFilePathArr = remoteFileList.map((i) => i.path);
|
|
315
320
|
const shouldCreateFiles = files.filter((file) => !remoteFilePathArr.includes(file.path));
|
|
316
321
|
const shouldUpdateFiles = files.filter((file) => remoteFilePathArr.includes(file.path));
|
|
@@ -324,7 +329,7 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
324
329
|
for (let i of shouldCreateFiles) {
|
|
325
330
|
console.error("[创建文件]:", i.path);
|
|
326
331
|
try {
|
|
327
|
-
const result = await createFileFunc(
|
|
332
|
+
const result = await createFileFunc("5fc86883f9b3ccf7cd2cbd89", repositoryId, i.path, i.content, commitMessage, "master", i.encoding);
|
|
328
333
|
createFilesSuccess.push(result.filePath);
|
|
329
334
|
}
|
|
330
335
|
catch (error) {
|
|
@@ -334,7 +339,7 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
334
339
|
for (let i of shouldUpdateFiles) {
|
|
335
340
|
console.error("[更新文件]:", i.path);
|
|
336
341
|
try {
|
|
337
|
-
const result = await updateFileFunc(
|
|
342
|
+
const result = await updateFileFunc(repositoryId, i.path, i.content, commitMessage, "master", i.encoding);
|
|
338
343
|
updateFilesSuccess.push(result.filePath);
|
|
339
344
|
}
|
|
340
345
|
catch (error) {
|
|
@@ -344,7 +349,7 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
344
349
|
for (let i of shouldDeleteFiles) {
|
|
345
350
|
console.error("[删除文件]:", i.path);
|
|
346
351
|
try {
|
|
347
|
-
const result = await deleteFileFunc(
|
|
352
|
+
const result = await deleteFileFunc("5fc86883f9b3ccf7cd2cbd89", repositoryId, i.path, commitMessage, "master");
|
|
348
353
|
deleteFilesSuccess.push(result.filePath);
|
|
349
354
|
}
|
|
350
355
|
catch (error) {
|
|
@@ -356,7 +361,7 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
356
361
|
projectId: projectId,
|
|
357
362
|
repoUrl: `https://codeup.aliyun.com/ctrod/fedin-ai-project/${projectId}.git`,
|
|
358
363
|
projectType,
|
|
359
|
-
organizationId:
|
|
364
|
+
organizationId: "5fc86883f9b3ccf7cd2cbd89",
|
|
360
365
|
token: "pt-LYBdmK5wiw5vIG9DnhaMzRED_b0f4124a-5c6b-4a1a-86a5-652022be25f8"
|
|
361
366
|
};
|
|
362
367
|
const pipelineResponse = await fetch("https://ai.fedin.cn/api/alicloud/create-pipeline-run", {
|
|
@@ -384,48 +389,56 @@ export async function pushFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
384
389
|
onlineUrl: onlineUrl || ""
|
|
385
390
|
};
|
|
386
391
|
}
|
|
392
|
+
function isPureNumber(str) {
|
|
393
|
+
return /^\d+$/.test(str);
|
|
394
|
+
}
|
|
387
395
|
/**
|
|
388
396
|
* 拉取整个代码库文件
|
|
389
397
|
* @param organizationId
|
|
390
398
|
* @param repositoryId
|
|
391
399
|
* @param projectPath
|
|
392
400
|
*/
|
|
393
|
-
export async function pullFilesFunc(
|
|
401
|
+
export async function pullFilesFunc(projectId, currntProjectPath) {
|
|
394
402
|
let repositoryId = "";
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
403
|
+
if (isPureNumber(projectId)) {
|
|
404
|
+
repositoryId = projectId;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
const selectProjectQuery = `SELECT id FROM chat_history WHERE id = '${projectId}'`;
|
|
408
|
+
const projectResult = await fedinQueryRequest("api/supabase/query", selectProjectQuery);
|
|
409
|
+
if (!projectResult.length) {
|
|
410
|
+
throw new Error("当前项目不存在");
|
|
411
|
+
}
|
|
412
|
+
const query = `SELECT ch.id
|
|
413
|
+
FROM chat_history ch
|
|
414
|
+
JOIN codeup_tokens ct ON ch.user_id = ct.user_id
|
|
415
|
+
WHERE ct.token = '${process.env.FEDIN_ACCESS_TOKEN}';`;
|
|
416
|
+
const projects = await fedinQueryRequest("api/supabase/query", query);
|
|
417
|
+
if (!projects.length) {
|
|
418
|
+
throw new Error("token密钥未找到对应项目,请确保密钥的可用性");
|
|
419
|
+
}
|
|
420
|
+
// 判断查到的projects是否存在projectId
|
|
421
|
+
const project = projects.find((project) => project.id === projectId);
|
|
422
|
+
if (!project) {
|
|
423
|
+
throw new Error("无权限访问当前项目仓库");
|
|
424
|
+
}
|
|
425
|
+
const queryCodeUp = `SELECT "repositoryId" FROM codeup WHERE chat_id = '${projectId}'`;
|
|
426
|
+
const codeupDetail = await fedinQueryRequest("api/supabase/query", queryCodeUp);
|
|
427
|
+
if (!codeupDetail.length) {
|
|
428
|
+
throw new Error("当前项目仓库不存在,请先创建仓库");
|
|
429
|
+
}
|
|
430
|
+
repositoryId = codeupDetail[0].repositoryId;
|
|
431
|
+
}
|
|
419
432
|
if (!repositoryId) {
|
|
420
433
|
throw new Error("当前项目仓库不存在,请先到飞钉AI推送项目代码");
|
|
421
434
|
}
|
|
422
435
|
// 拉取之前 需要将当前projectPath路径下的所有代码文件清空
|
|
423
436
|
await clearProjectPath(currntProjectPath);
|
|
424
|
-
const allRemoteFilesPath = await listFilesFunc(
|
|
437
|
+
const allRemoteFilesPath = await listFilesFunc("5fc86883f9b3ccf7cd2cbd89", repositoryId, undefined, "master", "RECURSIVE");
|
|
425
438
|
const allFiles = [];
|
|
426
439
|
for (let i of allRemoteFilesPath) {
|
|
427
440
|
try {
|
|
428
|
-
const fileBlob = await getFileBlobsFunc(
|
|
441
|
+
const fileBlob = await getFileBlobsFunc("5fc86883f9b3ccf7cd2cbd89", repositoryId, i.path, "master");
|
|
429
442
|
allFiles.push(fileBlob);
|
|
430
443
|
// 写入文件到本地目录
|
|
431
444
|
const fullPath = path.join(currntProjectPath, i.path);
|
|
@@ -457,7 +470,7 @@ export async function pullFilesFunc(organizationId, projectId, currntProjectPath
|
|
|
457
470
|
* @param branch
|
|
458
471
|
* @param encoding
|
|
459
472
|
*/
|
|
460
|
-
export async function updateFileFunc(
|
|
473
|
+
export async function updateFileFunc(repositoryId, filePath, content, commitMessage, branch, encoding) {
|
|
461
474
|
//const { encodedRepoId, encodedFilePath } = handlePathEncoding(repositoryId, filePath);
|
|
462
475
|
let encodedRepoId = repositoryId;
|
|
463
476
|
let encodedFilePath = filePath;
|
|
@@ -479,7 +492,7 @@ export async function updateFileFunc(organizationId, repositoryId, filePath, con
|
|
|
479
492
|
: filePath;
|
|
480
493
|
encodedFilePath = encodeURIComponent(pathToEncode);
|
|
481
494
|
}
|
|
482
|
-
const url = `/oapi/v1/codeup/organizations
|
|
495
|
+
const url = `/oapi/v1/codeup/organizations/5fc86883f9b3ccf7cd2cbd89/repositories/${encodedRepoId}/files/${encodedFilePath}`;
|
|
483
496
|
const body = {
|
|
484
497
|
branch: branch,
|
|
485
498
|
commitMessage: commitMessage,
|
|
@@ -7,8 +7,8 @@ import { fedinQueryRequest } from "../../common/utils.js";
|
|
|
7
7
|
* @param name
|
|
8
8
|
* @param description
|
|
9
9
|
*/
|
|
10
|
-
export async function createRepositoryFunc(
|
|
11
|
-
const url = `/oapi/v1/codeup/organizations
|
|
10
|
+
export async function createRepositoryFunc(name, description) {
|
|
11
|
+
const url = `/oapi/v1/codeup/organizations/5fc86883f9b3ccf7cd2cbd89/repositories?createParentPath=true`;
|
|
12
12
|
const response = await yunxiaoRequest(url, {
|
|
13
13
|
method: "POST",
|
|
14
14
|
body: {
|
|
@@ -25,9 +25,9 @@ export async function createRepositoryFunc(organizationId, name, description) {
|
|
|
25
25
|
* @param organizationId
|
|
26
26
|
* @param repositoryId
|
|
27
27
|
*/
|
|
28
|
-
export async function getRepositoryFunc(
|
|
28
|
+
export async function getRepositoryFunc(repositoryId) {
|
|
29
29
|
const encodedRepoId = handleRepositoryIdEncoding(repositoryId);
|
|
30
|
-
const url = `/oapi/v1/codeup/organizations
|
|
30
|
+
const url = `/oapi/v1/codeup/organizations/5fc86883f9b3ccf7cd2cbd89/repositories/${encodedRepoId}`;
|
|
31
31
|
const response = await yunxiaoRequest(url, {
|
|
32
32
|
method: "GET"
|
|
33
33
|
});
|
|
@@ -43,8 +43,8 @@ export async function getRepositoryFunc(organizationId, repositoryId) {
|
|
|
43
43
|
* @param search
|
|
44
44
|
* @param archived
|
|
45
45
|
*/
|
|
46
|
-
export async function listRepositoriesFunc(
|
|
47
|
-
const baseUrl = `/oapi/v1/codeup/organizations
|
|
46
|
+
export async function listRepositoriesFunc(page, perPage, orderBy, sort, search, archived) {
|
|
47
|
+
const baseUrl = `/oapi/v1/codeup/organizations/5fc86883f9b3ccf7cd2cbd89/repositories`;
|
|
48
48
|
const queryParams = {};
|
|
49
49
|
if (page !== undefined) {
|
|
50
50
|
queryParams.page = page;
|
|
@@ -231,16 +231,13 @@ export const ListBranchesSchema = z.object({
|
|
|
231
231
|
});
|
|
232
232
|
// Codeup repositories related Schema definitions
|
|
233
233
|
export const CreateRepositorySchema = z.object({
|
|
234
|
-
organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"),
|
|
235
234
|
name: z.string().describe("Repository name"),
|
|
236
235
|
description: z.string().describe("Repository description")
|
|
237
236
|
});
|
|
238
237
|
export const GetRepositorySchema = z.object({
|
|
239
|
-
organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"),
|
|
240
238
|
repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"),
|
|
241
239
|
});
|
|
242
240
|
export const ListRepositoriesSchema = z.object({
|
|
243
|
-
organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"),
|
|
244
241
|
page: z.number().int().default(1).optional().describe("Page number, default starts from 1, generally should not exceed 150 pages"),
|
|
245
242
|
perPage: z.number().int().default(20).optional().describe("Items per page, default 20, value range [1, 100]"),
|
|
246
243
|
orderBy: z.string().default("created_at").optional().describe("Sort field, options include {created_at, name, path, last_activity_at}, default is created_at"),
|
|
@@ -265,13 +262,11 @@ export const CreateFileSchema = z.object({
|
|
|
265
262
|
encoding: z.string().optional().describe("Encoding rule, options {text, base64}, default is text"),
|
|
266
263
|
});
|
|
267
264
|
export const PushFilesSchema = z.object({
|
|
268
|
-
organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"),
|
|
269
265
|
currntProjectPath: z.string().describe("The project path, and it must be an absolute path."),
|
|
270
266
|
projectId: z.string().describe("Project ID. Note that this is not the repository ID, but the project ID. The project ID is obtained from the user’s message. If the user’s message does not mention it, then it is retrieved from the projectName field in the package.json file."),
|
|
271
267
|
commitMessage: z.string().describe("Commit message, not empty, no more than 102400 characters")
|
|
272
268
|
});
|
|
273
269
|
export const PullFilesSchema = z.object({
|
|
274
|
-
organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"),
|
|
275
270
|
currntProjectPath: z.string().describe("The absolute local path of the current project, and it must be an absolute path."),
|
|
276
271
|
projectId: z.string().describe("Project ID, the ID of the project corresponding to the repository to be fetched.")
|
|
277
272
|
});
|
|
@@ -1,52 +1,21 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { unlink } from "fs/promises";
|
|
2
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { unlink, readdir } from "fs/promises";
|
|
5
4
|
import archiver from "archiver";
|
|
6
|
-
import
|
|
7
|
-
import { join, resolve, relative } from "path";
|
|
5
|
+
import { join, resolve } from "path";
|
|
8
6
|
import axios from "axios";
|
|
9
|
-
import
|
|
10
|
-
import mime from "mime-types";
|
|
7
|
+
import FormData from "form-data";
|
|
11
8
|
const CONFIG = {
|
|
12
9
|
projectPath: "",
|
|
13
10
|
projectType: "",
|
|
14
|
-
buildCommand: "npm run build",
|
|
15
|
-
buildOutput: "dist",
|
|
16
|
-
apiUrl: "https://your-api.com/upload",
|
|
17
|
-
apiHeaders: {
|
|
18
|
-
Authorization: "Bearer your-token"
|
|
19
|
-
},
|
|
20
11
|
projectName: "",
|
|
21
|
-
token: ""
|
|
22
|
-
ossConfig: {},
|
|
23
|
-
domain: "",
|
|
24
|
-
domain_certificate: ""
|
|
25
|
-
};
|
|
26
|
-
const deployConfig = {
|
|
27
|
-
distDir: "",
|
|
28
|
-
ossPrefix: "",
|
|
29
|
-
indexPage: "index.html",
|
|
30
|
-
errorPage: "404.html",
|
|
31
|
-
deleteRemote: true,
|
|
32
|
-
cacheControl: {
|
|
33
|
-
html: "no-cache",
|
|
34
|
-
css: "max-age=31536000",
|
|
35
|
-
js: "max-age=31536000",
|
|
36
|
-
images: "max-age=31536000",
|
|
37
|
-
fonts: "max-age=31536000",
|
|
38
|
-
default: "max-age=86400"
|
|
39
|
-
}
|
|
12
|
+
token: ""
|
|
40
13
|
};
|
|
41
14
|
function resetConfig(projectPath, token) {
|
|
42
15
|
CONFIG.projectPath = resolve(projectPath);
|
|
43
16
|
CONFIG.token = token;
|
|
44
17
|
CONFIG.projectType = "";
|
|
45
18
|
CONFIG.projectName = "";
|
|
46
|
-
CONFIG.domain = "";
|
|
47
|
-
CONFIG.domain_certificate = "";
|
|
48
|
-
deployConfig.distDir = join(CONFIG.projectPath, "dist");
|
|
49
|
-
deployConfig.ossPrefix = "";
|
|
50
19
|
}
|
|
51
20
|
function getProjectName(projectPath) {
|
|
52
21
|
function generateRandomString() {
|
|
@@ -100,496 +69,143 @@ function detectProjectType(projectPath) {
|
|
|
100
69
|
}
|
|
101
70
|
return "vite";
|
|
102
71
|
}
|
|
103
|
-
function
|
|
104
|
-
const nextConfigFiles = [
|
|
105
|
-
"next.config.js",
|
|
106
|
-
"next.config.ts",
|
|
107
|
-
"next.config.mjs",
|
|
108
|
-
"next.config.mts"
|
|
109
|
-
];
|
|
110
|
-
for (const file of nextConfigFiles) {
|
|
111
|
-
if (existsSync(join(projectPath, file))) {
|
|
112
|
-
return file;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return "next.config.js";
|
|
116
|
-
}
|
|
117
|
-
class OSSDeployer {
|
|
118
|
-
client;
|
|
119
|
-
errors = [];
|
|
120
|
-
constructor(config) {
|
|
121
|
-
this.client = new OSS(config);
|
|
122
|
-
}
|
|
123
|
-
getAllFiles(dir, baseDir = dir) {
|
|
124
|
-
const files = [];
|
|
125
|
-
const items = readdirSync(dir);
|
|
126
|
-
for (const item of items) {
|
|
127
|
-
const fullPath = join(dir, item);
|
|
128
|
-
const relativePath = relative(baseDir, fullPath);
|
|
129
|
-
if (statSync(fullPath).isDirectory()) {
|
|
130
|
-
files.push(...this.getAllFiles(fullPath, baseDir));
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
files.push({
|
|
134
|
-
localPath: fullPath,
|
|
135
|
-
relativePath: relativePath.replace(/\\/g, "/"),
|
|
136
|
-
size: statSync(fullPath).size
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return files;
|
|
141
|
-
}
|
|
142
|
-
getContentType(filePath) {
|
|
143
|
-
return mime.lookup(filePath) || "application/octet-stream";
|
|
144
|
-
}
|
|
145
|
-
async uploadFile(file) {
|
|
146
|
-
try {
|
|
147
|
-
const ossKey = deployConfig.ossPrefix
|
|
148
|
-
? `${deployConfig.ossPrefix}/${file.relativePath}`
|
|
149
|
-
: file.relativePath;
|
|
150
|
-
const headers = {
|
|
151
|
-
"Content-Type": this.getContentType(file.localPath)
|
|
152
|
-
};
|
|
153
|
-
if (headers["Content-Type"].includes("text/html")) {
|
|
154
|
-
headers["Content-Type"] = "text/html; charset=utf-8";
|
|
155
|
-
}
|
|
156
|
-
const result = await this.client.put(ossKey, file.localPath, { headers });
|
|
157
|
-
return { success: true, file, ossKey, url: result.url };
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
this.errors.push({ file, error: error.message });
|
|
161
|
-
return { success: false, file, error: error.message };
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
async getRemoteFiles() {
|
|
165
|
-
try {
|
|
166
|
-
const remoteFiles = [];
|
|
167
|
-
let continuationToken = null;
|
|
168
|
-
do {
|
|
169
|
-
const listParams = {
|
|
170
|
-
prefix: deployConfig.ossPrefix,
|
|
171
|
-
"max-keys": 1000
|
|
172
|
-
};
|
|
173
|
-
if (continuationToken) {
|
|
174
|
-
listParams["continuation-token"] = continuationToken;
|
|
175
|
-
}
|
|
176
|
-
const result = await this.client.listV2(listParams);
|
|
177
|
-
if (result.objects) {
|
|
178
|
-
remoteFiles.push(...result.objects.map((obj) => obj.name));
|
|
179
|
-
}
|
|
180
|
-
continuationToken = result.nextContinuationToken;
|
|
181
|
-
} while (continuationToken);
|
|
182
|
-
return remoteFiles;
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
return [];
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
async cleanupRemoteFiles(localFiles, remoteFiles) {
|
|
189
|
-
if (!deployConfig.deleteRemote)
|
|
190
|
-
return;
|
|
191
|
-
const localKeys = localFiles.map((file) => deployConfig.ossPrefix
|
|
192
|
-
? `${deployConfig.ossPrefix}/${file.relativePath}`
|
|
193
|
-
: file.relativePath);
|
|
194
|
-
const filesToDelete = remoteFiles.filter((remoteFile) => {
|
|
195
|
-
if (deployConfig.ossPrefix &&
|
|
196
|
-
!remoteFile.startsWith(deployConfig.ossPrefix)) {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
return !localKeys.includes(remoteFile);
|
|
200
|
-
});
|
|
201
|
-
for (const fileToDelete of filesToDelete) {
|
|
202
|
-
try {
|
|
203
|
-
await this.client.delete(fileToDelete);
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
console.error(`删除失败 ${fileToDelete}: ${error.message}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async configureStaticWebsite() {
|
|
211
|
-
try {
|
|
212
|
-
const { bucket, region } = CONFIG.ossConfig;
|
|
213
|
-
if (!bucket || !region)
|
|
214
|
-
return;
|
|
215
|
-
await this.client.putBucketWebsite(bucket, {
|
|
216
|
-
index: deployConfig.indexPage,
|
|
217
|
-
error: deployConfig.errorPage
|
|
218
|
-
});
|
|
219
|
-
const domain = `${bucket}.${region}.aliyuncs.com`;
|
|
220
|
-
console.error(`网站访问地址: http://${domain}`);
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
console.error(`静态网站配置失败: ${error.message}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
async uploadFilesWithConcurrency(files, concurrency = 5) {
|
|
227
|
-
const results = [];
|
|
228
|
-
const chunks = [];
|
|
229
|
-
for (let i = 0; i < files.length; i += concurrency) {
|
|
230
|
-
chunks.push(files.slice(i, i + concurrency));
|
|
231
|
-
}
|
|
232
|
-
for (const chunk of chunks) {
|
|
233
|
-
const chunkResults = await Promise.all(chunk.map((file) => this.uploadFile(file)));
|
|
234
|
-
results.push(...chunkResults);
|
|
235
|
-
}
|
|
236
|
-
return results;
|
|
237
|
-
}
|
|
238
|
-
async deploy() {
|
|
239
|
-
const distPath = join(CONFIG.projectPath, "dist");
|
|
240
|
-
if (!existsSync(distPath)) {
|
|
241
|
-
throw new Error(`dist目录不存在: ${distPath}`);
|
|
242
|
-
}
|
|
243
|
-
const localFiles = this.getAllFiles(distPath);
|
|
244
|
-
if (localFiles.length === 0) {
|
|
245
|
-
throw new Error("dist目录为空");
|
|
246
|
-
}
|
|
247
|
-
const uploadResults = await this.uploadFilesWithConcurrency(localFiles, 5);
|
|
248
|
-
const successCount = uploadResults.filter((r) => r.success).length;
|
|
249
|
-
const failCount = uploadResults.filter((r) => !r.success).length;
|
|
250
|
-
if (this.errors.length > 0) {
|
|
251
|
-
this.errors.forEach((err) => {
|
|
252
|
-
console.error(`${err.file.relativePath}: ${err.error}`);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
success: failCount === 0,
|
|
257
|
-
uploaded: successCount,
|
|
258
|
-
failed: failCount
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
async function deployProjectForNext(formData, currentChatId) {
|
|
263
|
-
formData.append("functionName", `fedin-${currentChatId}`);
|
|
264
|
-
try {
|
|
265
|
-
const createFunctionResponse = await axios.post("https://ai.fedin.cn/api/alicloud/create-function", formData, {
|
|
266
|
-
headers: { ...formData.getHeaders(), token: CONFIG.token },
|
|
267
|
-
maxContentLength: Infinity,
|
|
268
|
-
maxBodyLength: Infinity
|
|
269
|
-
});
|
|
270
|
-
const createFunctionResult = createFunctionResponse.data;
|
|
271
|
-
if (!createFunctionResult.success) {
|
|
272
|
-
return {
|
|
273
|
-
success: false,
|
|
274
|
-
error: createFunctionResult.error || "Deployment failed"
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
const functionName = createFunctionResult.functionName;
|
|
278
|
-
const createTriggerResponse = await axios(`https://ai.fedin.cn/api/alicloud/create-trigger?functionName=${functionName}`, { method: "POST", headers: { token: CONFIG.token } });
|
|
279
|
-
const createTriggerResult = createTriggerResponse.data;
|
|
280
|
-
if (!createTriggerResult.success) {
|
|
281
|
-
return {
|
|
282
|
-
success: false,
|
|
283
|
-
error: createTriggerResult.error || "Deployment failed"
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const domainResponse = await fetch(`https://ai.fedin.cn/api/alicloud/config-domain?projectId=${currentChatId}`, {
|
|
287
|
-
method: "POST",
|
|
288
|
-
headers: { token: "fedin_ac_9x7k2m8p4w6q1z5n3v7b9c2e8r4t6" }
|
|
289
|
-
});
|
|
290
|
-
const customDomainResult = await domainResponse.json();
|
|
291
|
-
if (!domainResponse.ok || !customDomainResult.success) {
|
|
292
|
-
return {
|
|
293
|
-
success: false,
|
|
294
|
-
error: customDomainResult.error ||
|
|
295
|
-
customDomainResult.message ||
|
|
296
|
-
"Custom domain binding failed"
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
const url = customDomainResult.data;
|
|
300
|
-
return { success: true, url, functionName };
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
const errorMessage = error.response?.data?.error;
|
|
304
|
-
return { success: false, error: errorMessage || "Deployment failed" };
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async function updateProjectForNext(formData, currentChatId) {
|
|
308
|
-
formData.append("functionName", "fedin-" + currentChatId);
|
|
309
|
-
try {
|
|
310
|
-
const updateFunctionResponse = await axios.post("https://ai.fedin.cn/api/alicloud/update-function", formData, {
|
|
311
|
-
headers: { ...formData.getHeaders(), token: CONFIG.token },
|
|
312
|
-
maxContentLength: Infinity,
|
|
313
|
-
maxBodyLength: Infinity
|
|
314
|
-
});
|
|
315
|
-
const createFunctionResult = updateFunctionResponse.data;
|
|
316
|
-
if (!createFunctionResult.success) {
|
|
317
|
-
return {
|
|
318
|
-
success: false,
|
|
319
|
-
error: createFunctionResult.error || "Deployment failed"
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
const functionName = createFunctionResult.functionName;
|
|
323
|
-
const getTriggerResponse = await axios(`https://ai.fedin.cn/api/alicloud/get-trigger?functionName=${functionName}`, { method: "GET", headers: { token: CONFIG.token } });
|
|
324
|
-
const getTriggerResult = getTriggerResponse.data;
|
|
325
|
-
if (!getTriggerResult.success) {
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: getTriggerResult.error || "Deployment failed"
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
const domainResponse = await fetch(`https://ai.fedin.cn/api/alicloud/config-domain?projectId=${currentChatId}`, {
|
|
332
|
-
method: "POST",
|
|
333
|
-
headers: { token: "fedin_ac_9x7k2m8p4w6q1z5n3v7b9c2e8r4t6" }
|
|
334
|
-
});
|
|
335
|
-
const customDomainResult = await domainResponse.json();
|
|
336
|
-
if (!domainResponse.ok || !customDomainResult.success) {
|
|
337
|
-
return {
|
|
338
|
-
success: false,
|
|
339
|
-
error: customDomainResult.error ||
|
|
340
|
-
customDomainResult.message ||
|
|
341
|
-
"Custom domain binding failed"
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
const url = customDomainResult.data;
|
|
345
|
-
return { success: true, url, functionName };
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
const errorMessage = error.response?.data?.error;
|
|
349
|
-
return { success: false, error: errorMessage || "Deployment failed" };
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
async function postProcessStandalone() {
|
|
353
|
-
const buildPath = join(CONFIG.projectPath, "build");
|
|
354
|
-
const standalonePath = join(buildPath, "standalone");
|
|
355
|
-
const staticPath = join(buildPath, "static");
|
|
356
|
-
const standaloneStaticPath = join(standalonePath, "build", "static");
|
|
357
|
-
const envPath = join(CONFIG.projectPath, ".env.local");
|
|
358
|
-
const standaloneEnvPath = join(standalonePath, ".env");
|
|
359
|
-
const runShPath = join(standalonePath, "run.sh");
|
|
360
|
-
if (!existsSync(standalonePath)) {
|
|
361
|
-
throw new Error(`Standalone 目录不存在: ${standalonePath}`);
|
|
362
|
-
}
|
|
363
|
-
if (existsSync(staticPath)) {
|
|
364
|
-
mkdirSync(join(standalonePath, "build"), { recursive: true });
|
|
365
|
-
cpSync(staticPath, standaloneStaticPath, { recursive: true });
|
|
366
|
-
}
|
|
367
|
-
if (existsSync(envPath)) {
|
|
368
|
-
cpSync(envPath, standaloneEnvPath);
|
|
369
|
-
}
|
|
370
|
-
const shScriptContent = `#!/bin/bash
|
|
371
|
-
export PORT=\${_BYTEFAAS_RUNTIME_PORT:-3000}
|
|
372
|
-
export HOSTNAME='0.0.0.0'
|
|
373
|
-
exec node server.js`;
|
|
374
|
-
writeFileSync(runShPath, shScriptContent, "utf8");
|
|
375
|
-
if (process.platform !== "win32") {
|
|
376
|
-
try {
|
|
377
|
-
const { execSync } = await import("child_process");
|
|
378
|
-
execSync(`chmod +x "${runShPath}"`);
|
|
379
|
-
}
|
|
380
|
-
catch (e) {
|
|
381
|
-
console.error("设置脚本执行权限失败:", e.message);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
async function runBuild() {
|
|
72
|
+
async function createProjectZipFile() {
|
|
386
73
|
return new Promise(async (resolve, reject) => {
|
|
387
74
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (existsSync(nextConfigPath)) {
|
|
417
|
-
originalNextConfig = readFileSync(nextConfigPath, "utf8");
|
|
418
|
-
}
|
|
419
|
-
writeFileSync(nextConfigPath, nextConfigContent, "utf8");
|
|
420
|
-
const child = spawn("npm", ["run", "build"], {
|
|
421
|
-
cwd: CONFIG.projectPath,
|
|
422
|
-
shell: true,
|
|
423
|
-
stdio: "pipe"
|
|
424
|
-
});
|
|
425
|
-
let output = "";
|
|
426
|
-
let error = "";
|
|
427
|
-
child.stdout?.on("data", (data) => {
|
|
428
|
-
output += data.toString();
|
|
429
|
-
});
|
|
430
|
-
child.stderr?.on("data", (data) => {
|
|
431
|
-
error += data.toString();
|
|
432
|
-
});
|
|
433
|
-
child.on("close", async (code) => {
|
|
75
|
+
const timestamp = Date.now();
|
|
76
|
+
const zipFileName = join(CONFIG.projectPath, `project-${timestamp}.zip`);
|
|
77
|
+
const excludePatterns = [
|
|
78
|
+
"node_modules",
|
|
79
|
+
".next",
|
|
80
|
+
".git",
|
|
81
|
+
"dist",
|
|
82
|
+
"build",
|
|
83
|
+
".cache",
|
|
84
|
+
".turbo",
|
|
85
|
+
".vercel",
|
|
86
|
+
".env.local",
|
|
87
|
+
".env.*.local",
|
|
88
|
+
".DS_Store",
|
|
89
|
+
"coverage",
|
|
90
|
+
".nyc_output",
|
|
91
|
+
".idea",
|
|
92
|
+
".vscode",
|
|
93
|
+
"*.zip"
|
|
94
|
+
];
|
|
95
|
+
const output = createWriteStream(zipFileName);
|
|
96
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
97
|
+
// 统计信息
|
|
98
|
+
let fileCount = 0;
|
|
99
|
+
let dirCount = 0;
|
|
100
|
+
let skipCount = 0;
|
|
101
|
+
// 递归添加文件到压缩包
|
|
102
|
+
const addFilesToArchive = async (dirPath, baseName = "") => {
|
|
434
103
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
104
|
+
const items = await readdir(dirPath, { withFileTypes: true });
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
const itemPath = join(dirPath, item.name);
|
|
107
|
+
const archivePath = baseName
|
|
108
|
+
? `${baseName}/${item.name}`
|
|
109
|
+
: item.name;
|
|
110
|
+
let matchedPattern;
|
|
111
|
+
const shouldExclude = excludePatterns.some((pattern) => {
|
|
112
|
+
if (pattern.includes("*")) {
|
|
113
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
114
|
+
if (regex.test(item.name) || regex.test(archivePath)) {
|
|
115
|
+
matchedPattern = pattern;
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const pathParts = archivePath.split("/");
|
|
121
|
+
const matches = item.name === pattern ||
|
|
122
|
+
archivePath === pattern ||
|
|
123
|
+
archivePath.startsWith(pattern + "/") ||
|
|
124
|
+
archivePath.startsWith(pattern + "\\") ||
|
|
125
|
+
pathParts.some((part) => part === pattern);
|
|
126
|
+
if (matches) {
|
|
127
|
+
matchedPattern = pattern;
|
|
128
|
+
}
|
|
129
|
+
return matches;
|
|
130
|
+
});
|
|
131
|
+
if (shouldExclude) {
|
|
132
|
+
skipCount++;
|
|
133
|
+
continue;
|
|
442
134
|
}
|
|
443
|
-
|
|
444
|
-
|
|
135
|
+
if (item.isDirectory()) {
|
|
136
|
+
dirCount++;
|
|
137
|
+
await addFilesToArchive(itemPath, archivePath);
|
|
445
138
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
catch (restoreError) {
|
|
452
|
-
if (code === 0) {
|
|
453
|
-
try {
|
|
454
|
-
await postProcessStandalone();
|
|
455
|
-
resolve(output);
|
|
139
|
+
else {
|
|
140
|
+
fileCount++;
|
|
141
|
+
archive.file(itemPath, { name: archivePath });
|
|
456
142
|
}
|
|
457
|
-
catch (postProcessError) {
|
|
458
|
-
reject(postProcessError);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
reject(new Error(`构建失败: ${error}`));
|
|
463
143
|
}
|
|
464
144
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
catch (error) {
|
|
468
|
-
reject(error);
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
async function runBuildVite() {
|
|
473
|
-
return new Promise((resolve, reject) => {
|
|
474
|
-
try {
|
|
475
|
-
const buildArgs = ["run", "build", "--", "--base=./"];
|
|
476
|
-
const child = spawn("npm", buildArgs, {
|
|
477
|
-
cwd: CONFIG.projectPath,
|
|
478
|
-
shell: true,
|
|
479
|
-
stdio: "pipe"
|
|
480
|
-
});
|
|
481
|
-
let output = "";
|
|
482
|
-
let error = "";
|
|
483
|
-
child.stdout?.on("data", (data) => {
|
|
484
|
-
output += data.toString();
|
|
485
|
-
});
|
|
486
|
-
child.stderr?.on("data", (data) => {
|
|
487
|
-
error += data.toString();
|
|
488
|
-
});
|
|
489
|
-
child.on("close", (code) => {
|
|
490
|
-
if (code === 0) {
|
|
491
|
-
resolve(output);
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
reject(new Error(`构建失败: ${error}`));
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error(`[打包] 处理目录 ${dirPath} 时出错:`, error);
|
|
495
147
|
}
|
|
148
|
+
};
|
|
149
|
+
output.on("close", () => {
|
|
150
|
+
console.error(JSON.stringify({
|
|
151
|
+
Zip文件: zipFileName,
|
|
152
|
+
文件大小: `${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`
|
|
153
|
+
}, null, 2));
|
|
154
|
+
resolve(zipFileName);
|
|
155
|
+
});
|
|
156
|
+
archive.on("error", (err) => {
|
|
157
|
+
reject(err);
|
|
496
158
|
});
|
|
159
|
+
archive.pipe(output);
|
|
160
|
+
await addFilesToArchive(CONFIG.projectPath, "");
|
|
161
|
+
archive.finalize();
|
|
497
162
|
}
|
|
498
163
|
catch (error) {
|
|
499
164
|
reject(error);
|
|
500
165
|
}
|
|
501
166
|
});
|
|
502
167
|
}
|
|
503
|
-
async function
|
|
504
|
-
|
|
505
|
-
const timestamp = Date.now();
|
|
506
|
-
const zipFileName = join(CONFIG.projectPath, `build-${timestamp}.zip`);
|
|
507
|
-
const standaloneOutputPath = join(CONFIG.projectPath, "build", "standalone");
|
|
508
|
-
const output = createWriteStream(zipFileName);
|
|
509
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
510
|
-
if (!existsSync(standaloneOutputPath)) {
|
|
511
|
-
reject(new Error(`Standalone 目录不存在: ${standaloneOutputPath}`));
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
output.on("close", () => resolve(zipFileName));
|
|
515
|
-
archive.on("error", (err) => reject(err));
|
|
516
|
-
archive.pipe(output);
|
|
517
|
-
archive.directory(standaloneOutputPath, false);
|
|
518
|
-
archive.finalize();
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
async function uploadToApi(zipFileName) {
|
|
522
|
-
const filename = zipFileName.split("/").pop();
|
|
523
|
-
if (!filename)
|
|
524
|
-
throw new Error("无法识别压缩包名称");
|
|
168
|
+
async function deployViaLocalApi(opts) {
|
|
169
|
+
const { projectName, projectType = "fullstack", zipFilePath } = opts;
|
|
525
170
|
const form = new FormData();
|
|
526
|
-
form.append("
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (existFunctionResultJson.error?.includes("FunctionNotFound")) {
|
|
537
|
-
hasFunction = false;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
throw new Error(existFunctionResultJson.error || "Invalid deployment response");
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
const deployResult = hasFunction
|
|
544
|
-
? await updateProjectForNext(form, CONFIG.projectName)
|
|
545
|
-
: await deployProjectForNext(form, CONFIG.projectName);
|
|
546
|
-
if (!deployResult.success) {
|
|
547
|
-
throw new Error(deployResult.error || "Invalid deployment response");
|
|
548
|
-
}
|
|
549
|
-
return deployResult.url;
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
throw error;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
async function getOssConfig() {
|
|
556
|
-
const configResponse = await axios.get("https://ai.fedin.cn/api/alicloud/oss-config", {
|
|
557
|
-
headers: { token: CONFIG.token }
|
|
171
|
+
form.append("projectName", projectName);
|
|
172
|
+
form.append("projectType", projectType);
|
|
173
|
+
form.append("zipFile", createReadStream(zipFilePath));
|
|
174
|
+
const res = await axios.post("https://ai.fedin.cn/api/edgeone-pages/deploy", form, {
|
|
175
|
+
headers: {
|
|
176
|
+
...form.getHeaders(),
|
|
177
|
+
token: CONFIG.token
|
|
178
|
+
},
|
|
179
|
+
maxBodyLength: Infinity,
|
|
180
|
+
maxContentLength: Infinity
|
|
558
181
|
});
|
|
559
|
-
|
|
560
|
-
throw new Error(configResponse.data?.message);
|
|
561
|
-
}
|
|
562
|
-
return configResponse.data.data;
|
|
182
|
+
return res.data;
|
|
563
183
|
}
|
|
564
184
|
async function deployVite() {
|
|
565
|
-
let zipFileName = null;
|
|
566
185
|
try {
|
|
567
|
-
await
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const result = await deployer.deploy();
|
|
578
|
-
if (!result.success) {
|
|
579
|
-
throw new Error("部署失败");
|
|
186
|
+
const zipFileName = await createProjectZipFile();
|
|
187
|
+
try {
|
|
188
|
+
const data = await deployViaLocalApi({
|
|
189
|
+
projectName: CONFIG.projectName,
|
|
190
|
+
projectType: "static",
|
|
191
|
+
zipFilePath: zipFileName
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
195
|
+
};
|
|
580
196
|
}
|
|
581
|
-
|
|
582
|
-
return { content: [{ type: "text", text: `部署完成: 地址 ${url}` }] };
|
|
583
|
-
}
|
|
584
|
-
catch (error) {
|
|
585
|
-
if (zipFileName) {
|
|
197
|
+
finally {
|
|
586
198
|
try {
|
|
587
199
|
await unlink(zipFileName);
|
|
588
200
|
}
|
|
589
|
-
catch {
|
|
590
|
-
// ignore
|
|
591
|
-
}
|
|
201
|
+
catch { }
|
|
592
202
|
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(JSON.stringify({
|
|
206
|
+
错误信息: error.message,
|
|
207
|
+
错误堆栈: error.stack
|
|
208
|
+
}, null, 2));
|
|
593
209
|
return {
|
|
594
210
|
content: [{ type: "text", text: `❌ Vite 部署失败: ${error.message}` }],
|
|
595
211
|
isError: true
|
|
@@ -597,92 +213,56 @@ async function deployVite() {
|
|
|
597
213
|
}
|
|
598
214
|
}
|
|
599
215
|
async function deployNext() {
|
|
600
|
-
let zipFileName = null;
|
|
601
216
|
try {
|
|
602
|
-
await
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
217
|
+
const zipFileName = await createProjectZipFile();
|
|
218
|
+
try {
|
|
219
|
+
const data = await deployViaLocalApi({
|
|
220
|
+
projectName: CONFIG.projectName,
|
|
221
|
+
projectType: "fullstack",
|
|
222
|
+
zipFilePath: zipFileName
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
226
|
+
};
|
|
607
227
|
}
|
|
608
|
-
|
|
609
|
-
content: [
|
|
610
|
-
{ type: "text", text: `🎉 部署成功!\n\n部署地址为: ${result}` }
|
|
611
|
-
]
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
if (zipFileName) {
|
|
228
|
+
finally {
|
|
616
229
|
try {
|
|
617
230
|
await unlink(zipFileName);
|
|
618
231
|
}
|
|
619
|
-
catch {
|
|
620
|
-
// ignore
|
|
621
|
-
}
|
|
232
|
+
catch { }
|
|
622
233
|
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const errorMessage = error?.message || "未知错误";
|
|
623
237
|
return {
|
|
624
|
-
content: [
|
|
238
|
+
content: [
|
|
239
|
+
{ type: "text", text: `❌ Next.js 部署失败\n\n${errorMessage}` }
|
|
240
|
+
],
|
|
625
241
|
isError: true
|
|
626
242
|
};
|
|
627
243
|
}
|
|
628
244
|
}
|
|
629
|
-
async function getRepositoryIdWithPat(projectId) {
|
|
630
|
-
const query = `
|
|
631
|
-
select
|
|
632
|
-
d.*
|
|
633
|
-
from
|
|
634
|
-
public.chat_history c
|
|
635
|
-
left join public.user_domains d
|
|
636
|
-
on c.domain_id = d.id
|
|
637
|
-
where
|
|
638
|
-
c.id = '${projectId}';
|
|
639
|
-
`;
|
|
640
|
-
try {
|
|
641
|
-
const response = await fetch("https://ai.fedin.cn/api/supabase/query", {
|
|
642
|
-
method: "post",
|
|
643
|
-
body: JSON.stringify({ query }),
|
|
644
|
-
headers: { "Content-Type": "application/json" }
|
|
645
|
-
});
|
|
646
|
-
const data = await response.json();
|
|
647
|
-
if (data.length > 0) {
|
|
648
|
-
return {
|
|
649
|
-
custom_domain: data[0].domain,
|
|
650
|
-
domain_certificate: data[0].certificate
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
return {};
|
|
654
|
-
}
|
|
655
|
-
catch (error) {
|
|
656
|
-
console.error("获取域名信息失败", error);
|
|
657
|
-
return {};
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
245
|
export async function runDeployTool(args) {
|
|
661
|
-
|
|
662
|
-
if (!token) {
|
|
663
|
-
throw new Error("请先设置deploy token");
|
|
664
|
-
}
|
|
665
|
-
resetConfig(args.path, token);
|
|
246
|
+
resetConfig(args.path, process.env.DEPLOY_ACCESS_TOKEN || "");
|
|
666
247
|
const verifyTokenResponse = await axios.post("https://ai.fedin.cn/api/verify-token", {}, {
|
|
667
248
|
headers: {
|
|
668
249
|
token: CONFIG.token
|
|
669
250
|
}
|
|
670
251
|
});
|
|
671
252
|
if (!verifyTokenResponse.data.success) {
|
|
253
|
+
console.error("[验证] Token 验证失败");
|
|
254
|
+
console.error(JSON.stringify({
|
|
255
|
+
错误信息: verifyTokenResponse.data.message
|
|
256
|
+
}, null, 2));
|
|
672
257
|
throw new Error(`token校验不通过 ${verifyTokenResponse.data.message}`);
|
|
673
258
|
}
|
|
259
|
+
console.log("[验证] Token 验证通过");
|
|
674
260
|
const projectName = getProjectName(CONFIG.projectPath);
|
|
675
261
|
const projectType = detectProjectType(CONFIG.projectPath);
|
|
676
262
|
CONFIG.projectName = projectName;
|
|
677
263
|
CONFIG.projectType = projectType;
|
|
678
|
-
|
|
679
|
-
deployConfig.distDir = join(CONFIG.projectPath, "dist");
|
|
264
|
+
// 根据项目类型部署
|
|
680
265
|
if (CONFIG.projectType === "next") {
|
|
681
|
-
const projectInfo = await getRepositoryIdWithPat(projectName);
|
|
682
|
-
if (projectInfo.custom_domain) {
|
|
683
|
-
CONFIG.domain = projectInfo.custom_domain;
|
|
684
|
-
CONFIG.domain_certificate = projectInfo.domain_certificate;
|
|
685
|
-
}
|
|
686
266
|
return deployNext();
|
|
687
267
|
}
|
|
688
268
|
if (CONFIG.projectType === "vite") {
|
|
@@ -10,7 +10,7 @@ export const handleCodeManagementTools = async (request) => {
|
|
|
10
10
|
// Branch Operations
|
|
11
11
|
case "create_branch": {
|
|
12
12
|
const args = types.CreateBranchSchema.parse(request.params.arguments);
|
|
13
|
-
const branch = await branches.createBranchFunc(args.
|
|
13
|
+
const branch = await branches.createBranchFunc(args.repositoryId, args.branch, args.ref);
|
|
14
14
|
return {
|
|
15
15
|
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }]
|
|
16
16
|
};
|
|
@@ -53,7 +53,7 @@ export const handleCodeManagementTools = async (request) => {
|
|
|
53
53
|
}
|
|
54
54
|
case "push_files": {
|
|
55
55
|
const args = types.PushFilesSchema.parse(request.params.arguments);
|
|
56
|
-
const result = await files.pushFilesFunc(args.
|
|
56
|
+
const result = await files.pushFilesFunc(args.projectId, args.currntProjectPath, args.commitMessage);
|
|
57
57
|
const returnText = `项目部署地址:${result.onlineUrl}\n文件推送结果:${JSON.stringify(result.pushResult)}
|
|
58
58
|
`;
|
|
59
59
|
return {
|
|
@@ -62,14 +62,14 @@ export const handleCodeManagementTools = async (request) => {
|
|
|
62
62
|
}
|
|
63
63
|
case "pull_files": {
|
|
64
64
|
const args = types.PullFilesSchema.parse(request.params.arguments);
|
|
65
|
-
const result = await files.pullFilesFunc(args.
|
|
65
|
+
const result = await files.pullFilesFunc(args.projectId, args.currntProjectPath);
|
|
66
66
|
return {
|
|
67
67
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
case "update_file": {
|
|
71
71
|
const args = types.UpdateFileSchema.parse(request.params.arguments);
|
|
72
|
-
const result = await files.updateFileFunc(args.
|
|
72
|
+
const result = await files.updateFileFunc(args.repositoryId, args.filePath, args.content, args.commitMessage, args.branch, args.encoding);
|
|
73
73
|
return {
|
|
74
74
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
75
75
|
};
|
|
@@ -100,21 +100,21 @@ export const handleCodeManagementTools = async (request) => {
|
|
|
100
100
|
// Repository Operations
|
|
101
101
|
case "create_repository": {
|
|
102
102
|
const args = types.CreateRepositorySchema.parse(request.params.arguments);
|
|
103
|
-
const repository = await repositories.createRepositoryFunc(args.
|
|
103
|
+
const repository = await repositories.createRepositoryFunc(args.name, args.description);
|
|
104
104
|
return {
|
|
105
105
|
content: [{ type: "text", text: JSON.stringify(repository, null, 2) }]
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
case "get_repository": {
|
|
109
109
|
const args = types.GetRepositorySchema.parse(request.params.arguments);
|
|
110
|
-
const repository = await repositories.getRepositoryFunc(args.
|
|
110
|
+
const repository = await repositories.getRepositoryFunc(args.repositoryId);
|
|
111
111
|
return {
|
|
112
112
|
content: [{ type: "text", text: JSON.stringify(repository, null, 2) }]
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
case "list_repositories": {
|
|
116
116
|
const args = types.ListRepositoriesSchema.parse(request.params.arguments);
|
|
117
|
-
const repositoryList = await repositories.listRepositoriesFunc(args.
|
|
117
|
+
const repositoryList = await repositories.listRepositoriesFunc(args.page, args.perPage, args.orderBy, args.sort, args.search ?? undefined, args.archived);
|
|
118
118
|
return {
|
|
119
119
|
content: [
|
|
120
120
|
{ type: "text", text: JSON.stringify(repositoryList, null, 2) }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { handleCodeManagementTools } from "./code-management.js";
|
|
2
|
-
import { handleOrganizationTools } from "./organization.js";
|
|
3
2
|
import { handleMiniprogramTools } from "./miniprogram.js";
|
|
4
3
|
import { handleDeployTools } from "./deploy.js";
|
|
5
4
|
import { handleSupabaseTools } from "./supabase.js";
|
|
@@ -7,7 +6,6 @@ export const handleToolRequest = async (request) => {
|
|
|
7
6
|
// Try each handler in sequence until one returns a result
|
|
8
7
|
const handlers = [
|
|
9
8
|
handleCodeManagementTools,
|
|
10
|
-
handleOrganizationTools,
|
|
11
9
|
handleDeployTools,
|
|
12
10
|
handleSupabaseTools,
|
|
13
11
|
handleMiniprogramTools
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { getCodeManagementTools } from "./code-management.js";
|
|
2
|
-
import { getOrganizationTools } from "./organization.js";
|
|
3
2
|
import { getMiniprogramTools } from "./miniprogram.js";
|
|
4
3
|
import { getDeployTools } from "./deploy.js";
|
|
5
4
|
import { getSupabaseTools } from "./supabase.js";
|
|
6
5
|
export const getAllTools = () => [
|
|
7
6
|
...getCodeManagementTools(),
|
|
8
|
-
...getOrganizationTools(),
|
|
9
7
|
...getDeployTools(),
|
|
10
8
|
...getMiniprogramTools(),
|
|
11
9
|
...getSupabaseTools(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catchmexz/fedin-vibe-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"build": "tsc && shx chmod +x dist/*.js",
|
|
16
16
|
"watch": "tsc --watch",
|
|
17
17
|
"start": "node dist/index.js",
|
|
18
|
-
"start:sse": "node dist/index.js --sse"
|
|
18
|
+
"start:sse": "node dist/index.js --sse",
|
|
19
|
+
"dev": "tsx index.ts",
|
|
20
|
+
"dev:sse": "tsx index.ts --sse"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
"archiver": "^7.0.1",
|
|
24
26
|
"axios": "^1.7.7",
|
|
25
27
|
"common-tags": "^1.8.2",
|
|
28
|
+
"cos-nodejs-sdk-v5": "^2.14.7",
|
|
26
29
|
"dotenv": "^16.4.7",
|
|
27
30
|
"express": "^4.18.2",
|
|
28
31
|
"form-data": "^4.0.2",
|
|
@@ -34,12 +37,13 @@
|
|
|
34
37
|
"zod-to-json-schema": "^3.22.3"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
40
|
+
"@types/common-tags": "^1.8.4",
|
|
37
41
|
"@types/express": "^5.0.3",
|
|
38
42
|
"@types/fs-extra": "^11.0.4",
|
|
39
43
|
"@types/minimatch": "^5.1.2",
|
|
40
|
-
"@types/common-tags": "^1.8.4",
|
|
41
44
|
"@types/node": "^20.10.5",
|
|
42
45
|
"shx": "^0.3.4",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
43
47
|
"typescript": "^5.8.2"
|
|
44
48
|
}
|
|
45
49
|
}
|