xinyu-pro 0.22.5 → 0.23.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/app/api/chat/route.ts +2 -2
- package/app/api/generate-svg/route.ts +1 -1
- package/app/api/generate-theme/route.ts +112 -135
- package/app/api/plugins/bindings/route.ts +4 -4
- package/app/api/plugins/export/route.ts +9 -3
- package/app/api/plugins/export-xye/route.ts +12 -7
- package/app/api/plugins/files/route.ts +9 -9
- package/app/api/plugins/files-list/route.ts +7 -3
- package/app/api/plugins/files-upload/route.ts +3 -3
- package/app/api/plugins/files-write/all/route.ts +250 -0
- package/app/api/plugins/files-write/route.ts +13 -15
- package/app/api/plugins/import/route.ts +5 -5
- package/app/api/plugins/import-package/route.ts +9 -9
- package/app/api/plugins/resources/route.ts +6 -2
- package/app/api/plugins/route.ts +215 -119
- package/app/api/plugins/scan/route.ts +1 -1
- package/app/api/plugins/storage/route.ts +3 -3
- package/app/api/plugins/toggle/route.ts +21 -0
- package/app/api/sessions/route.ts +8 -3
- package/app/api/settings/route.ts +2 -2
- package/app/api/suggest-fields/route.ts +1 -1
- package/app/api/templates/route.ts +2 -2
- package/app/api/themes/route.ts +116 -0
- package/app/editor/page.tsx +27 -7
- package/app/extensions/edit/[id]/page.tsx +3576 -2342
- package/app/extensions/page.tsx +1967 -1572
- package/app/extensions/tutorial/code-examples.ts +833 -0
- package/app/extensions/tutorial/nav-link.tsx +27 -0
- package/app/extensions/tutorial/page.tsx +47 -3616
- package/app/extensions/tutorial/sections/AiInterceptionSection.tsx +87 -0
- package/app/extensions/tutorial/sections/ArchitectureSection.tsx +39 -0
- package/app/extensions/tutorial/sections/BestPracticesSection.tsx +78 -0
- package/app/extensions/tutorial/sections/ChatApiSection.tsx +212 -0
- package/app/extensions/tutorial/sections/CrossPluginSection.tsx +90 -0
- package/app/extensions/tutorial/sections/CssVariablesSection.tsx +189 -0
- package/app/extensions/tutorial/sections/DebugSection.tsx +48 -0
- package/app/extensions/tutorial/sections/DependencySection.tsx +134 -0
- package/app/extensions/tutorial/sections/DomSandboxSection.tsx +229 -0
- package/app/extensions/tutorial/sections/ExamplesSection.tsx +29 -0
- package/app/extensions/tutorial/sections/FileApiSection.tsx +44 -0
- package/app/extensions/tutorial/sections/GlobalApiSection.tsx +50 -0
- package/app/extensions/tutorial/sections/InputApiSection.tsx +213 -0
- package/app/extensions/tutorial/sections/LifecycleSection.tsx +105 -0
- package/app/extensions/tutorial/sections/OverviewSection.tsx +54 -0
- package/app/extensions/tutorial/sections/PermissionsSection.tsx +239 -0
- package/app/extensions/tutorial/sections/PersistentStorageSection.tsx +49 -0
- package/app/extensions/tutorial/sections/PluginConfigSection.tsx +81 -0
- package/app/extensions/tutorial/sections/PluginImportExportSection.tsx +68 -0
- package/app/extensions/tutorial/sections/PluginStorageSection.tsx +94 -0
- package/app/extensions/tutorial/sections/ResourceApiSection.tsx +88 -0
- package/app/extensions/tutorial/sections/SecuritySection.tsx +106 -0
- package/app/extensions/tutorial/sections/SlotsSection.tsx +130 -0
- package/app/extensions/tutorial/sections/UiApiSection.tsx +147 -0
- package/app/extensions/tutorial/sections/UtilsApiSection.tsx +96 -0
- package/app/extensions/tutorial/sections-data.tsx +325 -0
- package/app/extensions/tutorial/styles.ts +59 -0
- package/app/extensions/tutorial/types.ts +14 -0
- package/app/game/[id]/page.tsx +336 -148
- package/app/globals.css +41 -0
- package/app/page.tsx +18 -11
- package/app/settings/page.tsx +218 -236
- package/bin/cli.js +219 -211
- package/components/ChatInput.tsx +20 -6
- package/components/ChatWindow.tsx +1 -1
- package/components/MessageBubble.tsx +5 -6
- package/components/ui/ConfirmDialog.tsx +28 -6
- package/components/ui/DeprecatedBadge.tsx +163 -0
- package/components/ui/PageHeader.tsx +1 -1
- package/components/ui/PermissionConflictDialog.tsx +1 -1
- package/components/ui/PluginConfigForm.tsx +2 -3
- package/components/ui/PluginFloatingLayer.tsx +2 -2
- package/components/ui/PluginModalRenderer.tsx +84 -38
- package/components/ui/PluginProvider.tsx +32 -13
- package/components/ui/PluginSlotRenderer.tsx +2 -2
- package/components/ui/ThemeCustomizer.tsx +4 -7
- package/components/ui/ThemeProvider.tsx +214 -125
- package/components/ui/ThemeSwitcher.tsx +119 -157
- package/components/ui/ToastProvider.tsx +47 -17
- package/lib/{parseWorldCard.ts → client/parseWorldCard.ts} +1 -1
- package/lib/{plugin-dom-sandbox.ts → client/plugin-dom-sandbox.ts} +2 -2
- package/lib/{plugin-runtime.ts → client/plugin-runtime.ts} +166 -67
- package/lib/client/plugin-utils.ts +105 -0
- package/lib/{router-history.ts → client/router-history.ts} +1 -1
- package/lib/{storage.ts → client/storage.ts} +66 -3
- package/lib/{themes.ts → client/themes.ts} +21 -1
- package/lib/{builtin-plugins.ts → server/builtin-plugins.ts} +1 -1
- package/lib/{db-init.ts → server/db-init.ts} +2 -2
- package/lib/{db.ts → server/db.ts} +49 -9
- package/lib/{get-ai-config.ts → server/get-ai-config.ts} +2 -2
- package/lib/{plugin-files.ts → server/plugin-files.ts} +191 -186
- package/lib/server/plugin-utils.ts +88 -0
- package/lib/{manifest-parser.ts → shared/manifest-parser.ts} +4 -4
- package/lib/{plugin-types.ts → shared/plugin-types.ts} +11 -6
- package/lib/{version.ts → shared/version.ts} +1 -1
- package/next.config.mjs +3 -2
- package/package.json +5 -3
- package/styles/themes.css +7 -2
- package/version.json +2 -2
- package/app/extensions/create/page.tsx +0 -1422
- /package/lib/{plugin-events.ts → client/plugin-events.ts} +0 -0
- /package/lib/{plugin-resource-tracker.ts → client/plugin-resource-tracker.ts} +0 -0
- /package/lib/{plugin-html-sanitizer.ts → shared/plugin-html-sanitizer.ts} +0 -0
- /package/lib/{plugin-security.ts → shared/plugin-security.ts} +0 -0
- /package/lib/{prompt-builder.ts → shared/prompt-builder.ts} +0 -0
- /package/lib/{types.ts → shared/types.ts} +0 -0
package/app/api/chat/route.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// app/api/chat/route.ts - AI 对话 API 接口
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { buildSystemPrompt } from '@/lib/prompt-builder';
|
|
5
|
-
import { getAiConfig } from '@/lib/get-ai-config';
|
|
4
|
+
import { buildSystemPrompt } from '@/lib/shared/prompt-builder';
|
|
5
|
+
import { getAiConfig } from '@/lib/server/get-ai-config';
|
|
6
6
|
|
|
7
7
|
export async function POST(request: NextRequest) {
|
|
8
8
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// app/api/generate-svg/route.ts - AI 生成 SVG 世界卡片
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { getAiConfig } from '@/lib/get-ai-config';
|
|
4
|
+
import { getAiConfig } from '@/lib/server/get-ai-config';
|
|
5
5
|
|
|
6
6
|
interface GenerateSvgRequest {
|
|
7
7
|
worldData: Record<string, unknown>;
|
|
@@ -1,135 +1,112 @@
|
|
|
1
|
-
// app/api/generate-theme/route.ts - AI
|
|
2
|
-
|
|
3
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { getAiConfig } from '@/lib/get-ai-config';
|
|
5
|
-
|
|
6
|
-
interface GenerateThemeRequest {
|
|
7
|
-
prompt: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"--color-
|
|
37
|
-
"--color-
|
|
38
|
-
"--color-
|
|
39
|
-
"--color-text
|
|
40
|
-
"--color-text
|
|
41
|
-
"--
|
|
42
|
-
"--
|
|
43
|
-
"--
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (format === 'svg') {
|
|
115
|
-
// 提取 SVG
|
|
116
|
-
const start = content.indexOf('<svg');
|
|
117
|
-
const end = content.lastIndexOf('</svg>');
|
|
118
|
-
if (start !== -1 && end !== -1) content = content.slice(start, end + 6);
|
|
119
|
-
if (!content.includes('xmlns')) content = content.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
120
|
-
// 移除 script 标签
|
|
121
|
-
content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '').trim();
|
|
122
|
-
return NextResponse.json({ type: 'svg', content });
|
|
123
|
-
} else {
|
|
124
|
-
// 提取 JSON
|
|
125
|
-
const start = content.indexOf('{');
|
|
126
|
-
const end = content.lastIndexOf('}');
|
|
127
|
-
if (start !== -1 && end !== -1) content = content.slice(start, end + 1);
|
|
128
|
-
const parsed = JSON.parse(content);
|
|
129
|
-
return NextResponse.json({ type: 'json', content: parsed });
|
|
130
|
-
}
|
|
131
|
-
} catch (error) {
|
|
132
|
-
console.error('Generate theme error:', error);
|
|
133
|
-
return NextResponse.json({ error: '生成失败' }, { status: 500 });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
1
|
+
// app/api/generate-theme/route.ts - AI 生成配色方案(仅 JSON 格式)
|
|
2
|
+
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
import { getAiConfig } from '@/lib/server/get-ai-config';
|
|
5
|
+
|
|
6
|
+
interface GenerateThemeRequest {
|
|
7
|
+
prompt: string;
|
|
8
|
+
apiConfig?: {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
apiBase: string;
|
|
11
|
+
model: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const THEME_SYSTEM_PROMPT = `你是一位专业的 UI 配色方案设计师。你的任务是生成视觉精美的配色方案。
|
|
16
|
+
|
|
17
|
+
## 输出格式
|
|
18
|
+
输出一个 JSON 对象,包含以下结构:
|
|
19
|
+
{
|
|
20
|
+
"name": "主题名称",
|
|
21
|
+
"description": "主题描述",
|
|
22
|
+
"isDark": true/false,
|
|
23
|
+
"variables": {
|
|
24
|
+
"--color-bg-primary": "主背景色",
|
|
25
|
+
"--color-bg-secondary": "次背景色",
|
|
26
|
+
"--color-bg-tertiary": "三级背景色",
|
|
27
|
+
"--color-bg-hover": "悬浮背景色",
|
|
28
|
+
"--color-text-primary": "主文字色",
|
|
29
|
+
"--color-text-secondary": "次文字色",
|
|
30
|
+
"--color-text-muted": "弱化文字色",
|
|
31
|
+
"--color-text-hover": "悬浮文字色",
|
|
32
|
+
"--color-accent": "强调色",
|
|
33
|
+
"--color-accent-hover": "强调色悬停",
|
|
34
|
+
"--color-border": "边框色",
|
|
35
|
+
"--color-border-hover": "悬浮边框色",
|
|
36
|
+
"--color-shadow": "rgba阴影色",
|
|
37
|
+
"--color-user-bubble": "用户气泡背景色",
|
|
38
|
+
"--color-ai-bubble": "AI气泡背景色",
|
|
39
|
+
"--color-user-text": "用户气泡文本色",
|
|
40
|
+
"--color-ai-text": "AI气泡文本色",
|
|
41
|
+
"--font-body": "字体",
|
|
42
|
+
"--font-heading": "标题字体",
|
|
43
|
+
"--border-radius": "圆角大小"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
## 配色原则
|
|
48
|
+
- 确保文字在背景上有足够对比度(WCAG AA 标准)
|
|
49
|
+
- 强调色要醒目但不刺眼
|
|
50
|
+
- 背景色层次分明
|
|
51
|
+
- 整体配色和谐统一`;
|
|
52
|
+
|
|
53
|
+
export async function POST(request: NextRequest) {
|
|
54
|
+
try {
|
|
55
|
+
const body: GenerateThemeRequest = await request.json();
|
|
56
|
+
const { prompt, apiConfig } = body;
|
|
57
|
+
|
|
58
|
+
if (!prompt) {
|
|
59
|
+
return NextResponse.json({ error: '请描述你想要的配色风格' }, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const aiConfig = await getAiConfig();
|
|
63
|
+
const apiKey = apiConfig?.apiKey || aiConfig.apiKey;
|
|
64
|
+
const apiBase = apiConfig?.apiBase || aiConfig.apiBase;
|
|
65
|
+
const model = apiConfig?.model || aiConfig.model;
|
|
66
|
+
|
|
67
|
+
if (!apiKey) {
|
|
68
|
+
return NextResponse.json({ error: 'AI API Key 未配置' }, { status: 500 });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await fetch(`${apiBase}/chat/completions`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
Authorization: `Bearer ${apiKey}`,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
model,
|
|
79
|
+
messages: [
|
|
80
|
+
{ role: 'system', content: THEME_SYSTEM_PROMPT },
|
|
81
|
+
{ role: 'user', content: `请设计一个配色方案:${prompt}\n\n请以 JSON 格式输出配色方案。只输出 JSON 代码。` },
|
|
82
|
+
],
|
|
83
|
+
temperature: 0.9,
|
|
84
|
+
max_tokens: 8192,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
return NextResponse.json({ error: `AI 调用失败 (${response.status})` }, { status: 502 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
let content: string = data.choices?.[0]?.message?.content || '';
|
|
94
|
+
|
|
95
|
+
content = content.trim();
|
|
96
|
+
// 清理 markdown 代码块
|
|
97
|
+
if (content.startsWith('```json')) content = content.slice(7);
|
|
98
|
+
else if (content.startsWith('```')) content = content.slice(3);
|
|
99
|
+
if (content.endsWith('```')) content = content.slice(0, -3);
|
|
100
|
+
content = content.trim();
|
|
101
|
+
|
|
102
|
+
// 提取 JSON
|
|
103
|
+
const start = content.indexOf('{');
|
|
104
|
+
const end = content.lastIndexOf('}');
|
|
105
|
+
if (start !== -1 && end !== -1) content = content.slice(start, end + 1);
|
|
106
|
+
const parsed = JSON.parse(content);
|
|
107
|
+
return NextResponse.json({ type: 'json', content: parsed });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Generate theme error:', error);
|
|
110
|
+
return NextResponse.json({ error: '生成失败' }, { status: 500 });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// app/api/plugins/bindings/route.ts - 插件绑定 API
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { execute } from '@/lib/db';
|
|
5
|
-
import { ensureDb } from '@/lib/db-init';
|
|
4
|
+
import { execute } from '@/lib/server/db';
|
|
5
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
6
6
|
|
|
7
7
|
/** 解析 JSON 字段 */
|
|
8
8
|
function parseJsonField<T>(value: unknown): T {
|
|
@@ -47,14 +47,14 @@ export async function GET(request: NextRequest) {
|
|
|
47
47
|
return NextResponse.json(rowToBinding((rows as Record<string, unknown>[])[0]));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
let query = 'SELECT * FROM extension_bindings WHERE
|
|
50
|
+
let query = 'SELECT * FROM extension_bindings WHERE TRUE';
|
|
51
51
|
const params: (string | null)[] = [];
|
|
52
52
|
|
|
53
53
|
if (scope) {
|
|
54
54
|
query += ' AND scope = ?';
|
|
55
55
|
params.push(scope);
|
|
56
56
|
}
|
|
57
|
-
if (worldId) {
|
|
57
|
+
if (scope === 'world' && worldId) {
|
|
58
58
|
query += ' AND world_id = ?';
|
|
59
59
|
params.push(worldId);
|
|
60
60
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// app/api/plugins/export/route.ts - 插件导出 API
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
import { execute } from '@/lib/server/db';
|
|
8
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
9
|
+
import { readPluginCode } from '@/lib/server/plugin-files';
|
|
7
10
|
|
|
8
11
|
/** 解析 JSON 字段 */
|
|
9
12
|
function parseJsonField<T>(value: unknown): T {
|
|
@@ -81,10 +84,12 @@ export async function GET(request: NextRequest) {
|
|
|
81
84
|
[pluginId]
|
|
82
85
|
);
|
|
83
86
|
|
|
87
|
+
// @ts-ignore
|
|
84
88
|
if (rows.length === 0) {
|
|
85
89
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
// @ts-ignore
|
|
88
93
|
const plugin = await fillPluginCode(rowToPlugin(rows[0]));
|
|
89
94
|
|
|
90
95
|
return new NextResponse(JSON.stringify(plugin, null, 2), {
|
|
@@ -100,6 +105,7 @@ export async function GET(request: NextRequest) {
|
|
|
100
105
|
'SELECT * FROM extensions ORDER BY updated_at DESC'
|
|
101
106
|
);
|
|
102
107
|
|
|
108
|
+
// @ts-ignore
|
|
103
109
|
const plugins = await Promise.all(rows.map(r => fillPluginCode(rowToPlugin(r))));
|
|
104
110
|
|
|
105
111
|
const exportData = {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// app/api/plugins/export-xye/route.ts - 导出插件为 .xye 包(ZIP 格式)
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
import { execute } from '@/lib/server/db';
|
|
8
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
9
|
+
import { getPluginDir, readPluginCode } from '@/lib/server/plugin-files';
|
|
10
|
+
import { listPluginFiles } from '@/lib/server/plugin-files';
|
|
8
11
|
import AdmZip from 'adm-zip';
|
|
9
12
|
import path from 'path';
|
|
10
13
|
import { promises as fs } from 'fs';
|
|
@@ -54,10 +57,12 @@ export async function GET(request: NextRequest) {
|
|
|
54
57
|
[pluginId]
|
|
55
58
|
);
|
|
56
59
|
|
|
60
|
+
// @ts-ignore
|
|
57
61
|
if (rows.length === 0) {
|
|
58
62
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
// @ts-ignore
|
|
61
66
|
const row = rows[0];
|
|
62
67
|
const perms = parsePermissions(row.ui_permissions);
|
|
63
68
|
const pluginDir = getPluginDir(pluginId);
|
|
@@ -92,9 +97,9 @@ export async function GET(request: NextRequest) {
|
|
|
92
97
|
code = await readPluginCode(pluginId);
|
|
93
98
|
} catch { /* empty */ }
|
|
94
99
|
|
|
95
|
-
// 从
|
|
96
|
-
const
|
|
97
|
-
const entryName =
|
|
100
|
+
// 从 file_entry 提取入口文件名
|
|
101
|
+
const fileEntry = row.file_entry as string || '';
|
|
102
|
+
const entryName = fileEntry ? fileEntry.split('/').pop() || 'plugin.js' : 'plugin.js';
|
|
98
103
|
|
|
99
104
|
// 生成 manifest.json
|
|
100
105
|
const manifest: Record<string, unknown> = {
|
|
@@ -13,19 +13,19 @@ function isValidPluginId(id: string): boolean {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** 验证文件名格式(防止路径遍历) */
|
|
16
|
-
function isValidFileName(name: string): boolean {
|
|
16
|
+
function isValidFileName(name: string | any): boolean {
|
|
17
17
|
// 禁止路径分隔符、.. 和空名称
|
|
18
|
-
return typeof name === 'string' &&
|
|
19
|
-
name.
|
|
20
|
-
name.length <= 255 &&
|
|
21
|
-
!name.includes('..') &&
|
|
22
|
-
!name.includes('/') &&
|
|
23
|
-
!name.includes('\\') &&
|
|
24
|
-
!name.includes('\0');
|
|
18
|
+
return typeof name === 'string' && name.length > 0 && name.length <= 255 && !name.includes('..') &&
|
|
19
|
+
!name.includes('/') && !name.includes('\\') && !name.includes('\0');
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
/**
|
|
22
|
+
/** 获取插件的根目录路径 */
|
|
28
23
|
function getPluginDir(pluginId: string): string {
|
|
24
|
+
return path.join(PLUGIN_FILES_ROOT, pluginId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 获取插件资源文件的根目录路径 */
|
|
28
|
+
function getPluginResourceDir(pluginId: string): string {
|
|
29
29
|
return path.join(PLUGIN_FILES_ROOT, pluginId, 'resources');
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// app/api/plugins/files-list/route.ts - 列出插件目录文件 + 读取文件内容
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
8
|
+
import { execute } from '@/lib/server/db';
|
|
9
|
+
import { listPluginFiles, readPluginResourceText } from '@/lib/server/plugin-files';
|
|
7
10
|
import path from 'path';
|
|
8
11
|
|
|
9
12
|
function isValidPluginId(id: string): boolean {
|
|
@@ -61,6 +64,7 @@ export async function GET(request: NextRequest) {
|
|
|
61
64
|
[pluginId]
|
|
62
65
|
);
|
|
63
66
|
|
|
67
|
+
// @ts-ignore
|
|
64
68
|
if (rows.length === 0) {
|
|
65
69
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
66
70
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// app/api/plugins/files-upload/route.ts - 插件资源文件上传
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { ensureDb } from '@/lib/db-init';
|
|
5
|
-
import { execute } from '@/lib/db';
|
|
6
|
-
import { getPluginDir } from '@/lib/plugin-files';
|
|
4
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
5
|
+
import { execute } from '@/lib/server/db';
|
|
6
|
+
import { getPluginDir } from '@/lib/server/plugin-files';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { promises as fs } from 'fs';
|
|
9
9
|
|