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
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// app/api/plugins/files-write/all/route.ts
|
|
2
|
+
|
|
3
|
+
import {NextRequest, NextResponse} from 'next/server';
|
|
4
|
+
import {ensureDb} from '@/lib/server/db-init';
|
|
5
|
+
import {execute} from '@/lib/server/db';
|
|
6
|
+
import {getPluginDir, writePluginResource} from '@/lib/server/plugin-files';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import {promises as fs} from 'fs';
|
|
9
|
+
|
|
10
|
+
function isValidPluginId(id: string): boolean {
|
|
11
|
+
return /^[a-zA-Z0-9._-]+$/.test(id) && id.length <= 200 && !id.includes('..');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isSafePath(filePath: string): boolean {
|
|
15
|
+
const normalized = path.normalize(filePath);
|
|
16
|
+
return !normalized.includes('..') && !path.isAbsolute(normalized) && normalized !== '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 验证插件存在 */
|
|
20
|
+
async function verifyPlugin(pluginId: string) {
|
|
21
|
+
await ensureDb();
|
|
22
|
+
const [rows] = await execute(
|
|
23
|
+
'SELECT id, file_entry FROM extensions WHERE id = ?',
|
|
24
|
+
[pluginId]
|
|
25
|
+
);
|
|
26
|
+
const list = rows as Record<string, unknown>[];
|
|
27
|
+
return list.length > 0 ? list[0] : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 验证插件存在 */
|
|
31
|
+
async function deleteOrphanedFiles(pluginId: string, existingFiles: Set<string>, currentFiles: Set<string>) {
|
|
32
|
+
const pluginDir = getPluginDir(pluginId);
|
|
33
|
+
|
|
34
|
+
// 使用 Array.from 或 for...of 配合 downlevelIteration 的替代方案
|
|
35
|
+
const existingFilesArray = Array.from(existingFiles);
|
|
36
|
+
|
|
37
|
+
for (const filePath of existingFilesArray) {
|
|
38
|
+
if (!currentFiles.has(filePath)) {
|
|
39
|
+
const fullPath = path.join(pluginDir, filePath);
|
|
40
|
+
try {
|
|
41
|
+
const stat = await fs.stat(fullPath);
|
|
42
|
+
if (stat.isFile()) {
|
|
43
|
+
await fs.unlink(fullPath);
|
|
44
|
+
console.log(`删除孤儿文件: ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(`删除文件失败 ${filePath}:`, e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 获取插件当前所有文件列表 */
|
|
54
|
+
async function getExistingFiles(pluginId: string): Promise<Set<string>> {
|
|
55
|
+
const pluginDir = getPluginDir(pluginId);
|
|
56
|
+
const files = new Set<string>();
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(pluginDir);
|
|
60
|
+
} catch {
|
|
61
|
+
return files; // 目录不存在,返回空集合
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const walk = async (dir: string, basePath: string) => {
|
|
65
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const fullPath = path.join(dir, entry.name);
|
|
68
|
+
const relativePath = path.join(basePath, entry.name).replace(/\\/g, '/');
|
|
69
|
+
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
await walk(fullPath, relativePath);
|
|
72
|
+
} else {
|
|
73
|
+
files.add(relativePath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
await walk(pluginDir, '');
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ==================== PUT: 批量保存所有文件 ====================
|
|
83
|
+
|
|
84
|
+
export async function PUT(request: NextRequest) {
|
|
85
|
+
try {
|
|
86
|
+
const { searchParams } = new URL(request.url);
|
|
87
|
+
const pluginId = searchParams.get('pluginId');
|
|
88
|
+
const deleteOrphans = searchParams.get('deleteOrphans') !== 'false'; // 默认删除孤儿文件
|
|
89
|
+
|
|
90
|
+
if (!pluginId) {
|
|
91
|
+
return NextResponse.json({ error: '缺少 pluginId 参数' }, { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
if (!isValidPluginId(pluginId)) {
|
|
94
|
+
return NextResponse.json({ error: '非法插件 ID' }, { status: 400 });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const plugin = await verifyPlugin(pluginId);
|
|
98
|
+
if (!plugin) {
|
|
99
|
+
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const body = await request.json();
|
|
103
|
+
const { files, manifest, currentFile } = body;
|
|
104
|
+
|
|
105
|
+
if (!files || typeof files !== 'object') {
|
|
106
|
+
return NextResponse.json({ error: '缺少 files 参数或格式错误' }, { status: 400 });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const errors: Array<{ path: string; error: string }> = [];
|
|
110
|
+
const savedFiles: string[] = [];
|
|
111
|
+
|
|
112
|
+
// 获取现有文件列表(用于删除孤儿文件)
|
|
113
|
+
let existingFiles: Set<string> = new Set();
|
|
114
|
+
if (deleteOrphans) {
|
|
115
|
+
existingFiles = await getExistingFiles(pluginId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 保存所有文件
|
|
119
|
+
const fileEntries = Object.entries(files);
|
|
120
|
+
for (const [filePath, content] of fileEntries) {
|
|
121
|
+
if (typeof content !== 'string') {
|
|
122
|
+
errors.push({ path: filePath, error: '内容必须是字符串' });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!isSafePath(filePath)) {
|
|
127
|
+
errors.push({ path: filePath, error: '非法文件路径' });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 禁止写入关键目录检查
|
|
132
|
+
if (filePath.includes('..') || filePath.startsWith('.')) {
|
|
133
|
+
errors.push({ path: filePath, error: '非法文件路径' });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await writePluginResource(pluginId, filePath, content);
|
|
139
|
+
savedFiles.push(filePath);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(`保存文件失败 ${filePath}:`, e);
|
|
142
|
+
errors.push({ path: filePath, error: '写入失败' });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 处理 manifest 单独传入的情况
|
|
147
|
+
if (manifest && !files['manifest.json']) {
|
|
148
|
+
try {
|
|
149
|
+
const manifestContent = JSON.stringify(manifest, null, 2);
|
|
150
|
+
await writePluginResource(pluginId, 'manifest.json', manifestContent);
|
|
151
|
+
savedFiles.push('manifest.json');
|
|
152
|
+
} catch (e) {
|
|
153
|
+
errors.push({ path: 'manifest.json', error: '保存失败' });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 删除孤儿文件
|
|
158
|
+
let deletedCount = 0;
|
|
159
|
+
if (deleteOrphans && existingFiles.size > 0) {
|
|
160
|
+
const currentFilesSet = new Set(Object.keys(files));
|
|
161
|
+
await deleteOrphanedFiles(pluginId, existingFiles, currentFilesSet);
|
|
162
|
+
// 计算删除数量
|
|
163
|
+
const existingArray = Array.from(existingFiles);
|
|
164
|
+
const currentArray = Array.from(currentFilesSet);
|
|
165
|
+
deletedCount = existingArray.filter(f => !currentArray.includes(f)).length;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 返回结果
|
|
169
|
+
if (errors.length > 0 && savedFiles.length === 0) {
|
|
170
|
+
return NextResponse.json({
|
|
171
|
+
error: '所有文件保存失败',
|
|
172
|
+
errors,
|
|
173
|
+
savedFiles: [],
|
|
174
|
+
deletedCount
|
|
175
|
+
}, { status: 500 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return NextResponse.json({
|
|
179
|
+
success: true,
|
|
180
|
+
savedFiles,
|
|
181
|
+
savedCount: savedFiles.length,
|
|
182
|
+
deletedCount,
|
|
183
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
184
|
+
currentFile: currentFile || null
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.error('批量保存插件文件失败:', e);
|
|
189
|
+
return NextResponse.json({
|
|
190
|
+
error: '批量保存失败',
|
|
191
|
+
message: e instanceof Error ? e.message : String(e)
|
|
192
|
+
}, { status: 500 });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ==================== POST: 获取所有文件内容 ====================
|
|
197
|
+
|
|
198
|
+
export async function POST(request: NextRequest) {
|
|
199
|
+
try {
|
|
200
|
+
const { searchParams } = new URL(request.url);
|
|
201
|
+
const pluginId = searchParams.get('pluginId');
|
|
202
|
+
|
|
203
|
+
if (!pluginId) {
|
|
204
|
+
return NextResponse.json({ error: '缺少 pluginId 参数' }, { status: 400 });
|
|
205
|
+
}
|
|
206
|
+
if (!isValidPluginId(pluginId)) {
|
|
207
|
+
return NextResponse.json({ error: '非法插件 ID' }, { status: 400 });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const plugin = await verifyPlugin(pluginId);
|
|
211
|
+
if (!plugin) {
|
|
212
|
+
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const pluginDir = getPluginDir(pluginId);
|
|
216
|
+
const files: Record<string, string> = {};
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await fs.access(pluginDir);
|
|
220
|
+
} catch {
|
|
221
|
+
return NextResponse.json({ files: {}, message: '插件目录不存在' });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const walk = async (dir: string, basePath: string) => {
|
|
225
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
const fullPath = path.join(dir, entry.name);
|
|
228
|
+
const relativePath = path.join(basePath, entry.name).replace(/\\/g, '/');
|
|
229
|
+
|
|
230
|
+
if (entry.isDirectory()) {
|
|
231
|
+
await walk(fullPath, relativePath);
|
|
232
|
+
} else {
|
|
233
|
+
files[relativePath] = await fs.readFile(fullPath, 'utf-8');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
await walk(pluginDir, '');
|
|
239
|
+
|
|
240
|
+
return NextResponse.json({
|
|
241
|
+
success: true,
|
|
242
|
+
files,
|
|
243
|
+
fileCount: Object.keys(files).length
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error('读取插件所有文件失败:', e);
|
|
248
|
+
return NextResponse.json({ error: '读取文件失败' }, { status: 500 });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// app/api/plugins/files-write/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 {
|
|
4
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
5
|
+
import { execute } from '@/lib/server/db';
|
|
6
|
+
import {writePluginResource, getPluginDir, getPluginCodeRelativePath} from '@/lib/server/plugin-files';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { promises as fs } from 'fs';
|
|
9
9
|
|
|
@@ -168,35 +168,33 @@ export async function DELETE(request: NextRequest) {
|
|
|
168
168
|
return NextResponse.json({ error: '非法文件路径' }, { status: 400 });
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
// 禁止删除关键文件:manifest.json
|
|
171
|
+
// 禁止删除关键文件:manifest.json、入口文件
|
|
172
172
|
if (filePath === 'manifest.json') {
|
|
173
173
|
return NextResponse.json({ error: '不能删除 manifest.json' }, { status: 400 });
|
|
174
174
|
}
|
|
175
|
-
if (filePath === 'resources') {
|
|
176
|
-
return NextResponse.json({ error: '不能删除 resources 目录(资源文件目录,仅支持删除其中的文件)' }, { status: 400 });
|
|
177
|
-
}
|
|
178
175
|
|
|
179
176
|
const exists = await verifyPlugin(pluginId);
|
|
180
177
|
if (!exists) {
|
|
181
178
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
|
|
181
|
+
const pluginDir = getPluginDir(pluginId);
|
|
182
|
+
const fullPath = path.join(pluginDir, filePath);
|
|
183
|
+
const relativePath = getPluginCodeRelativePath(pluginId, filePath);
|
|
184
|
+
|
|
185
|
+
// 从数据库读取 file_entry,提取入口文件名,禁止删除入口文件
|
|
185
186
|
const [codeRows] = await execute(
|
|
186
|
-
'SELECT
|
|
187
|
+
'SELECT file_entry FROM extensions WHERE id = ?',
|
|
187
188
|
[pluginId]
|
|
188
189
|
);
|
|
189
190
|
const codeRowList = codeRows as Record<string, unknown>[];
|
|
190
|
-
if (codeRowList.length > 0 && codeRowList[0].
|
|
191
|
-
const entryFile =
|
|
192
|
-
if (entryFile &&
|
|
191
|
+
if (codeRowList.length > 0 && codeRowList[0].file_entry) {
|
|
192
|
+
const entryFile = codeRowList[0].file_entry as string;
|
|
193
|
+
if (entryFile && relativePath === entryFile) {
|
|
193
194
|
return NextResponse.json({ error: `不能删除入口文件 ${entryFile}` }, { status: 400 });
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const pluginDir = getPluginDir(pluginId);
|
|
198
|
-
const fullPath = path.join(pluginDir, filePath);
|
|
199
|
-
|
|
200
198
|
try {
|
|
201
199
|
const stat = await fs.stat(fullPath);
|
|
202
200
|
if (stat.isDirectory()) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// app/api/plugins/import/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';
|
|
6
|
-
import { parseManifestFromCode } from '@/lib/manifest-parser';
|
|
7
|
-
import { writePluginCode } from '@/lib/plugin-files';
|
|
4
|
+
import { execute } from '@/lib/server/db';
|
|
5
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
6
|
+
import { parseManifestFromCode } from '@/lib/shared/manifest-parser';
|
|
7
|
+
import { writePluginCode } from '@/lib/server/plugin-files';
|
|
8
8
|
|
|
9
9
|
/** 校验插件 ID 合法性(只允许字母、数字、连字符、下划线、点) */
|
|
10
10
|
function isValidPluginId(id: string): boolean {
|
|
@@ -88,7 +88,7 @@ export async function POST(request: NextRequest) {
|
|
|
88
88
|
// 将代码写入文件
|
|
89
89
|
const codePath = await writePluginCode(pId, pCode);
|
|
90
90
|
|
|
91
|
-
const sql = `INSERT INTO extensions (id, name, version, description, author, type, icon, min_app_version,
|
|
91
|
+
const sql = `INSERT INTO extensions (id, name, version, description, author, type, icon, min_app_version, file_entry, config_schema, ui_slots, ui_permissions, public_exports, dependencies) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, version = excluded.version, description = excluded.description, author = excluded.author, type = excluded.type, icon = excluded.icon, min_app_version = excluded.min_app_version, file_entry = excluded.file_entry, config_schema = excluded.config_schema, ui_slots = excluded.ui_slots, ui_permissions = excluded.ui_permissions, public_exports = excluded.public_exports, dependencies = excluded.dependencies`;
|
|
92
92
|
await execute(sql, [pId, pName, pVersion, pDescription, pAuthor, pType, pIcon, pMinAppVersion, codePath, pConfigSchema, pUiSlots, pUiPermissions, pPublicExports, pDependencies]);
|
|
93
93
|
|
|
94
94
|
// 自动创建全局绑定,config 从 configSchema 的 defaultValue 填充
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// app/api/plugins/import-package/route.ts - .xye 插件包导入 API
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { execute } from '@/lib/db';
|
|
5
|
-
import { ensureDb } from '@/lib/db-init';
|
|
6
|
-
import { parseManifestFromCode } from '@/lib/manifest-parser';
|
|
7
|
-
import { getPluginDir
|
|
4
|
+
import { execute } from '@/lib/server/db';
|
|
5
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
6
|
+
import { parseManifestFromCode } from '@/lib/shared/manifest-parser';
|
|
7
|
+
import {getPluginCodeRelativePath, getPluginDir} from '@/lib/server/plugin-files';
|
|
8
8
|
import AdmZip from 'adm-zip';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { promises as fs } from 'fs';
|
|
@@ -84,7 +84,7 @@ export async function POST(request: NextRequest) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// 查找入口文件
|
|
87
|
-
const entryPath =
|
|
87
|
+
const entryPath = path.basename(pluginEntry) || 'plugin.js';
|
|
88
88
|
const codeEntry = entries.find(e => e.entryName === entryPath || e.entryName === `/${entryPath}`);
|
|
89
89
|
|
|
90
90
|
if (!codeEntry) {
|
|
@@ -133,16 +133,16 @@ export async function POST(request: NextRequest) {
|
|
|
133
133
|
await fs.mkdir(pluginDir, { recursive: true });
|
|
134
134
|
const codeFilePath = path.join(pluginDir, entryPath);
|
|
135
135
|
await fs.writeFile(codeFilePath, pluginCode, 'utf-8');
|
|
136
|
-
const
|
|
136
|
+
const fileEntry = getPluginCodeRelativePath(pluginId, entryPath);
|
|
137
137
|
|
|
138
138
|
// 写入数据库
|
|
139
139
|
await execute(
|
|
140
|
-
`INSERT INTO extensions (id, name, version, description, author, type, icon, min_app_version,
|
|
140
|
+
`INSERT INTO extensions (id, name, version, description, author, type, icon, min_app_version, file_entry, config_schema, ui_slots, ui_permissions, public_exports, dependencies)
|
|
141
141
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
142
142
|
ON CONFLICT(id) DO UPDATE SET
|
|
143
143
|
name = excluded.name, version = excluded.version, description = excluded.description,
|
|
144
144
|
author = excluded.author, type = excluded.type, icon = excluded.icon,
|
|
145
|
-
min_app_version = excluded.min_app_version,
|
|
145
|
+
min_app_version = excluded.min_app_version, file_entry = excluded.file_entry,
|
|
146
146
|
config_schema = excluded.config_schema, ui_slots = excluded.ui_slots,
|
|
147
147
|
ui_permissions = excluded.ui_permissions,
|
|
148
148
|
public_exports = excluded.public_exports, dependencies = excluded.dependencies`,
|
|
@@ -150,7 +150,7 @@ export async function POST(request: NextRequest) {
|
|
|
150
150
|
pluginId, pluginName, pluginVersion,
|
|
151
151
|
String(manifest.description || ''), String(manifest.author || ''),
|
|
152
152
|
pluginType, String(manifest.icon || ''), String(manifest.minAppVersion || ''),
|
|
153
|
-
|
|
153
|
+
fileEntry, pConfigSchema, pUiSlots, pUiPermissions, pPublicExports, pDependencies,
|
|
154
154
|
]
|
|
155
155
|
);
|
|
156
156
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// app/api/plugins/resources/route.ts - 插件资源文件 HTTP 访问
|
|
2
2
|
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
import { ensureDb } from '@/lib/server/db-init';
|
|
8
|
+
import { execute } from '@/lib/server/db';
|
|
6
9
|
import path from 'path';
|
|
7
10
|
import { promises as fs } from 'fs';
|
|
8
11
|
|
|
@@ -72,6 +75,7 @@ export async function GET(request: NextRequest) {
|
|
|
72
75
|
[pluginId]
|
|
73
76
|
);
|
|
74
77
|
|
|
78
|
+
// @ts-ignore
|
|
75
79
|
if (rows.length === 0) {
|
|
76
80
|
return NextResponse.json({ error: '插件不存在' }, { status: 404 });
|
|
77
81
|
}
|