@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.
@@ -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