@blocklet/pages-kit-block-studio 0.5.41 → 0.5.42
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/lib/cjs/middlewares/init-block-studio-router.js +120 -0
- package/lib/cjs/middlewares/init-resource-router.js +54 -5
- package/lib/cjs/plugins/_theme.js +347 -54
- package/lib/cjs/tsconfig.tsbuildinfo +1 -1
- package/lib/esm/middlewares/init-block-studio-router.js +120 -0
- package/lib/esm/middlewares/init-resource-router.js +55 -6
- package/lib/esm/plugins/_theme.js +348 -55
- package/lib/esm/tsconfig.tsbuildinfo +1 -1
- package/lib/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -54,6 +54,52 @@ const ts_morph_utils_1 = require("../utils/ts-morph-utils");
|
|
|
54
54
|
const zod_utils_1 = require("../utils/zod-utils");
|
|
55
55
|
exports.initBlockStudioRouter = (0, express_1.Router)();
|
|
56
56
|
const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg'];
|
|
57
|
+
// 递归删除目录
|
|
58
|
+
const removeDirectoryRecursive = (dirPath) => {
|
|
59
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const stats = fs_1.default.statSync(dirPath);
|
|
63
|
+
if (stats.isDirectory()) {
|
|
64
|
+
const files = fs_1.default.readdirSync(dirPath);
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
const filePath = path_1.default.join(dirPath, file);
|
|
67
|
+
const fileStats = fs_1.default.statSync(filePath);
|
|
68
|
+
if (fileStats.isDirectory()) {
|
|
69
|
+
removeDirectoryRecursive(filePath);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
fs_1.default.unlinkSync(filePath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
fs_1.default.rmdirSync(dirPath);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
fs_1.default.unlinkSync(dirPath);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// 根据路由查找对应的组件目录路径
|
|
82
|
+
const findComponentDirByRoute = async (route) => {
|
|
83
|
+
try {
|
|
84
|
+
// Remove leading slash from route to get component name
|
|
85
|
+
const componentName = route.replace(/^\//, '');
|
|
86
|
+
// Find all component files
|
|
87
|
+
const allComponents = await (0, helper_1.findComponentFiles)({
|
|
88
|
+
strictExistMetadata: true,
|
|
89
|
+
});
|
|
90
|
+
// Find the component that matches the block name
|
|
91
|
+
const matchedComponent = allComponents.find((component) => component.blockName === componentName);
|
|
92
|
+
if (matchedComponent) {
|
|
93
|
+
// Return the directory path (parent of the index file)
|
|
94
|
+
return path_1.default.dirname(matchedComponent.fullPath);
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error finding component directory:', error);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
57
103
|
// 共享的请求验证和文件路径处理
|
|
58
104
|
const validateRequest = (req, res) => {
|
|
59
105
|
if (!helper_1.isDev) {
|
|
@@ -131,6 +177,10 @@ exports.initBlockStudioRouter.post('/', async (req, res) => {
|
|
|
131
177
|
if (!currentMetadata) {
|
|
132
178
|
return res.status(404).json({ error: 'Metadata file not found' });
|
|
133
179
|
}
|
|
180
|
+
// 核对 id 是否一致,不一致可能是手动修改过
|
|
181
|
+
if (currentMetadata.id !== content.id) {
|
|
182
|
+
return res.status(400).json({ error: 'ID is not consistent, please refresh the page' });
|
|
183
|
+
}
|
|
134
184
|
const mergedContent = { ...currentMetadata, ...content };
|
|
135
185
|
if ((0, isEqual_1.default)(currentMetadata, mergedContent)) {
|
|
136
186
|
return res.json({ success: true, content: mergedContent, message: 'No changes' });
|
|
@@ -388,4 +438,74 @@ exports.initBlockStudioRouter.post('/interface-to-properties', async (req, res)
|
|
|
388
438
|
});
|
|
389
439
|
}
|
|
390
440
|
});
|
|
441
|
+
// 添加删除组件的路由
|
|
442
|
+
exports.initBlockStudioRouter.delete('/delete', async (req, res) => {
|
|
443
|
+
if (!helper_1.isDev) {
|
|
444
|
+
return res.status(403).json({ error: 'Only available in development mode' });
|
|
445
|
+
}
|
|
446
|
+
const { route } = req.body;
|
|
447
|
+
if (!route) {
|
|
448
|
+
return res.status(400).json({ error: 'Route is required' });
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
// Find the component directory path
|
|
452
|
+
const componentDir = await findComponentDirByRoute(route);
|
|
453
|
+
if (!componentDir) {
|
|
454
|
+
return res.status(404).json({
|
|
455
|
+
success: false,
|
|
456
|
+
error: 'Component directory not found',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
// Security check: ensure the path is safe and within expected bounds
|
|
460
|
+
if (!(0, helper_1.isPathSafe)(componentDir)) {
|
|
461
|
+
return res.status(403).json({
|
|
462
|
+
success: false,
|
|
463
|
+
error: 'Invalid component directory path',
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
// Additional safety check: ensure it contains @metadata.json to confirm it's a component
|
|
467
|
+
const metadataPath = path_1.default.join(componentDir, constants_1.METADATA_FILE_NAME);
|
|
468
|
+
if (!fs_1.default.existsSync(metadataPath)) {
|
|
469
|
+
return res.status(400).json({
|
|
470
|
+
success: false,
|
|
471
|
+
error: 'Invalid component directory: no metadata file found',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
// Verify that this directory is indeed for component development
|
|
475
|
+
// Check if it's in the expected component structure
|
|
476
|
+
const pattern = (0, helper_1.getBlockEntryFilesPattern)();
|
|
477
|
+
const baseDir = path_1.default.dirname(pattern.replace(/\*\*?/g, ''));
|
|
478
|
+
const relativePath = path_1.default.relative(baseDir, componentDir);
|
|
479
|
+
// Ensure the component is within the base directory and not trying to escape
|
|
480
|
+
if (relativePath.startsWith('..') || path_1.default.isAbsolute(relativePath)) {
|
|
481
|
+
return res.status(403).json({
|
|
482
|
+
success: false,
|
|
483
|
+
error: 'Component directory is outside allowed path',
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
// Perform the deletion
|
|
487
|
+
try {
|
|
488
|
+
removeDirectoryRecursive(componentDir);
|
|
489
|
+
return res.json({
|
|
490
|
+
success: true,
|
|
491
|
+
message: 'Component deleted successfully',
|
|
492
|
+
deletedPath: componentDir,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
catch (deleteError) {
|
|
496
|
+
console.error('Failed to delete component directory:', deleteError);
|
|
497
|
+
return res.status(500).json({
|
|
498
|
+
success: false,
|
|
499
|
+
error: `Failed to delete component directory: ${deleteError.message}`,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
console.error('Failed to delete component:', error);
|
|
505
|
+
return res.status(500).json({
|
|
506
|
+
success: false,
|
|
507
|
+
error: `Failed to delete component: ${error.message}`,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
391
511
|
exports.default = exports.initBlockStudioRouter;
|
|
@@ -41,6 +41,7 @@ exports.copyFile = copyFile;
|
|
|
41
41
|
exports.copyDirectory = copyDirectory;
|
|
42
42
|
exports.copyRecursive = copyRecursive;
|
|
43
43
|
const component_1 = require("@blocklet/sdk/lib/component");
|
|
44
|
+
const uploader_server_1 = require("@blocklet/uploader-server");
|
|
44
45
|
const child_process_1 = require("child_process");
|
|
45
46
|
const express_1 = require("express");
|
|
46
47
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -181,7 +182,7 @@ exports.initResourceRouter.post('/', async (req, res) => {
|
|
|
181
182
|
// get @metadata.json by glob
|
|
182
183
|
const canUseComponents = (0, helper_1.findComponentFiles)({ cwd: rootDir, filter: componentIds, strictExistMetadata: true });
|
|
183
184
|
// Filter and process metadata files
|
|
184
|
-
const metadataList = canUseComponents.map(({ fullPath, blockName, metadata: _metadata }) => {
|
|
185
|
+
const metadataList = await Promise.all(canUseComponents.map(async ({ fullPath, blockName, metadata: _metadata }) => {
|
|
185
186
|
// get metadata
|
|
186
187
|
const metadata = _metadata;
|
|
187
188
|
const jsName = `${blockName}.js`;
|
|
@@ -206,9 +207,6 @@ exports.initResourceRouter.post('/', async (req, res) => {
|
|
|
206
207
|
catch (error) {
|
|
207
208
|
// ignore error
|
|
208
209
|
}
|
|
209
|
-
// write metadata to metadataPath
|
|
210
|
-
const metadataYmlPath = path_1.default.join(componentsDir, `${metadata.name || 'unnamed'}.${metadata.id}.yml`);
|
|
211
|
-
fs_1.default.writeFileSync(metadataYmlPath, (0, helper_1.generateYaml)(metadata));
|
|
212
210
|
// Handle preview image if exists
|
|
213
211
|
if (metadata.previewImage) {
|
|
214
212
|
const imagePath = path_1.default.join(path_1.default.dirname(fullPath), (0, helper_1.getPreviewImageRelativePath)(metadata.previewImage));
|
|
@@ -217,8 +215,59 @@ exports.initResourceRouter.post('/', async (req, res) => {
|
|
|
217
215
|
fs_1.default.copyFileSync(imagePath, imageDestPath);
|
|
218
216
|
}
|
|
219
217
|
}
|
|
218
|
+
// Handle type:url properties url
|
|
219
|
+
if (metadata.properties) {
|
|
220
|
+
const downloadPromises = [];
|
|
221
|
+
Object.keys(metadata.properties).forEach((key) => {
|
|
222
|
+
const property = metadata.properties[key].data;
|
|
223
|
+
if (property.type === 'url') {
|
|
224
|
+
// 如果每个 locales 的默认值里面,存在 mediaKitUrl 的值,需要把图片下载放到 imagePath 里面
|
|
225
|
+
Object.keys(property.locales).forEach((locale) => {
|
|
226
|
+
if (property.locales[locale].defaultValue?.mediaKitUrl?.startsWith('mediakit://')) {
|
|
227
|
+
const downloadPromise = (async () => {
|
|
228
|
+
try {
|
|
229
|
+
const fileName = (0, path_1.basename)(property.locales[locale].defaultValue.mediaKitUrl);
|
|
230
|
+
const targetPath = path_1.default.join(componentsDir, fileName);
|
|
231
|
+
// Check if file already exists to avoid redundant downloads
|
|
232
|
+
if (fs_1.default.existsSync(targetPath)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const resWithStream = await (0, uploader_server_1.getMediaKitFileStream)(property.locales[locale].defaultValue.mediaKitUrl);
|
|
236
|
+
if (!resWithStream.data) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// create write stream
|
|
240
|
+
const writeStream = fs_1.default.createWriteStream(targetPath);
|
|
241
|
+
resWithStream.data.pipe(writeStream);
|
|
242
|
+
// wait for write stream to finish
|
|
243
|
+
await new Promise((resolve, reject) => {
|
|
244
|
+
writeStream.on('finish', () => {
|
|
245
|
+
// 修改 property.locales[locale].defaultValue.url 变为 mediakit:// 开头
|
|
246
|
+
property.locales[locale].defaultValue.url = property.locales[locale].defaultValue.mediaKitUrl;
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
249
|
+
writeStream.on('error', reject);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error('Download media kit file failed:', error.message);
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
downloadPromises.push(downloadPromise);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Wait for all downloads to complete for this metadata
|
|
262
|
+
if (downloadPromises.length > 0) {
|
|
263
|
+
await Promise.allSettled(downloadPromises);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// write metadata to metadataPath
|
|
267
|
+
const metadataYmlPath = path_1.default.join(componentsDir, `${metadata.name || 'unnamed'}.${metadata.id}.yml`);
|
|
268
|
+
fs_1.default.writeFileSync(metadataYmlPath, (0, helper_1.generateYaml)(metadata));
|
|
220
269
|
return metadata;
|
|
221
|
-
});
|
|
270
|
+
}));
|
|
222
271
|
// cp chunks dir
|
|
223
272
|
await copyRecursive((0, path_1.join)(codeDir, 'chunks'), chunksDir);
|
|
224
273
|
// write pages.config.yml
|