@blocklet/pages-kit-block-studio 0.5.41 → 0.5.43
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
|
@@ -15,6 +15,52 @@ import { tsFileToZodSchema } from '../utils/ts-morph-utils';
|
|
|
15
15
|
import { propertiesToZodSchema, zodSchemaToTypeString, zodSchemaToJsonSchema, jsonSchemaToProperties, } from '../utils/zod-utils';
|
|
16
16
|
export const initBlockStudioRouter = Router();
|
|
17
17
|
const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg'];
|
|
18
|
+
// 递归删除目录
|
|
19
|
+
const removeDirectoryRecursive = (dirPath) => {
|
|
20
|
+
if (!fs.existsSync(dirPath)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const stats = fs.statSync(dirPath);
|
|
24
|
+
if (stats.isDirectory()) {
|
|
25
|
+
const files = fs.readdirSync(dirPath);
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const filePath = path.join(dirPath, file);
|
|
28
|
+
const fileStats = fs.statSync(filePath);
|
|
29
|
+
if (fileStats.isDirectory()) {
|
|
30
|
+
removeDirectoryRecursive(filePath);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
fs.unlinkSync(filePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
fs.rmdirSync(dirPath);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
fs.unlinkSync(dirPath);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
// 根据路由查找对应的组件目录路径
|
|
43
|
+
const findComponentDirByRoute = async (route) => {
|
|
44
|
+
try {
|
|
45
|
+
// Remove leading slash from route to get component name
|
|
46
|
+
const componentName = route.replace(/^\//, '');
|
|
47
|
+
// Find all component files
|
|
48
|
+
const allComponents = await findComponentFiles({
|
|
49
|
+
strictExistMetadata: true,
|
|
50
|
+
});
|
|
51
|
+
// Find the component that matches the block name
|
|
52
|
+
const matchedComponent = allComponents.find((component) => component.blockName === componentName);
|
|
53
|
+
if (matchedComponent) {
|
|
54
|
+
// Return the directory path (parent of the index file)
|
|
55
|
+
return path.dirname(matchedComponent.fullPath);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error('Error finding component directory:', error);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
18
64
|
// 共享的请求验证和文件路径处理
|
|
19
65
|
const validateRequest = (req, res) => {
|
|
20
66
|
if (!isDev) {
|
|
@@ -92,6 +138,10 @@ initBlockStudioRouter.post('/', async (req, res) => {
|
|
|
92
138
|
if (!currentMetadata) {
|
|
93
139
|
return res.status(404).json({ error: 'Metadata file not found' });
|
|
94
140
|
}
|
|
141
|
+
// 核对 id 是否一致,不一致可能是手动修改过
|
|
142
|
+
if (currentMetadata.id !== content.id) {
|
|
143
|
+
return res.status(400).json({ error: 'ID is not consistent, please refresh the page' });
|
|
144
|
+
}
|
|
95
145
|
const mergedContent = { ...currentMetadata, ...content };
|
|
96
146
|
if (isEqual(currentMetadata, mergedContent)) {
|
|
97
147
|
return res.json({ success: true, content: mergedContent, message: 'No changes' });
|
|
@@ -349,4 +399,74 @@ initBlockStudioRouter.post('/interface-to-properties', async (req, res) => {
|
|
|
349
399
|
});
|
|
350
400
|
}
|
|
351
401
|
});
|
|
402
|
+
// 添加删除组件的路由
|
|
403
|
+
initBlockStudioRouter.delete('/delete', async (req, res) => {
|
|
404
|
+
if (!isDev) {
|
|
405
|
+
return res.status(403).json({ error: 'Only available in development mode' });
|
|
406
|
+
}
|
|
407
|
+
const { route } = req.body;
|
|
408
|
+
if (!route) {
|
|
409
|
+
return res.status(400).json({ error: 'Route is required' });
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
// Find the component directory path
|
|
413
|
+
const componentDir = await findComponentDirByRoute(route);
|
|
414
|
+
if (!componentDir) {
|
|
415
|
+
return res.status(404).json({
|
|
416
|
+
success: false,
|
|
417
|
+
error: 'Component directory not found',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
// Security check: ensure the path is safe and within expected bounds
|
|
421
|
+
if (!isPathSafe(componentDir)) {
|
|
422
|
+
return res.status(403).json({
|
|
423
|
+
success: false,
|
|
424
|
+
error: 'Invalid component directory path',
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
// Additional safety check: ensure it contains @metadata.json to confirm it's a component
|
|
428
|
+
const metadataPath = path.join(componentDir, METADATA_FILE_NAME);
|
|
429
|
+
if (!fs.existsSync(metadataPath)) {
|
|
430
|
+
return res.status(400).json({
|
|
431
|
+
success: false,
|
|
432
|
+
error: 'Invalid component directory: no metadata file found',
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
// Verify that this directory is indeed for component development
|
|
436
|
+
// Check if it's in the expected component structure
|
|
437
|
+
const pattern = getBlockEntryFilesPattern();
|
|
438
|
+
const baseDir = path.dirname(pattern.replace(/\*\*?/g, ''));
|
|
439
|
+
const relativePath = path.relative(baseDir, componentDir);
|
|
440
|
+
// Ensure the component is within the base directory and not trying to escape
|
|
441
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
442
|
+
return res.status(403).json({
|
|
443
|
+
success: false,
|
|
444
|
+
error: 'Component directory is outside allowed path',
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
// Perform the deletion
|
|
448
|
+
try {
|
|
449
|
+
removeDirectoryRecursive(componentDir);
|
|
450
|
+
return res.json({
|
|
451
|
+
success: true,
|
|
452
|
+
message: 'Component deleted successfully',
|
|
453
|
+
deletedPath: componentDir,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
catch (deleteError) {
|
|
457
|
+
console.error('Failed to delete component directory:', deleteError);
|
|
458
|
+
return res.status(500).json({
|
|
459
|
+
success: false,
|
|
460
|
+
error: `Failed to delete component directory: ${deleteError.message}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
console.error('Failed to delete component:', error);
|
|
466
|
+
return res.status(500).json({
|
|
467
|
+
success: false,
|
|
468
|
+
error: `Failed to delete component: ${error.message}`,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
352
472
|
export default initBlockStudioRouter;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getResourceExportDir } from '@blocklet/sdk/lib/component';
|
|
2
|
+
import { getMediaKitFileStream } from '@blocklet/uploader-server';
|
|
2
3
|
import { spawn } from 'child_process';
|
|
3
4
|
import { Router } from 'express';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import get from 'lodash/get';
|
|
6
7
|
import set from 'lodash/set';
|
|
7
|
-
import path, { join } from 'path';
|
|
8
|
+
import path, { join, basename } from 'path';
|
|
8
9
|
import { findComponentFiles, libDir, getPreviewImageRelativePath, generateYaml, logger, getEditComponentBlockName, } from '../utils/helper';
|
|
9
10
|
const DID = 'z2qa7BQdkEb3TwYyEYC1psK6uvmGnHSUHt5RM';
|
|
10
11
|
const RESOURCE_TYPE = 'page';
|
|
@@ -139,7 +140,7 @@ initResourceRouter.post('/', async (req, res) => {
|
|
|
139
140
|
// get @metadata.json by glob
|
|
140
141
|
const canUseComponents = findComponentFiles({ cwd: rootDir, filter: componentIds, strictExistMetadata: true });
|
|
141
142
|
// Filter and process metadata files
|
|
142
|
-
const metadataList = canUseComponents.map(({ fullPath, blockName, metadata: _metadata }) => {
|
|
143
|
+
const metadataList = await Promise.all(canUseComponents.map(async ({ fullPath, blockName, metadata: _metadata }) => {
|
|
143
144
|
// get metadata
|
|
144
145
|
const metadata = _metadata;
|
|
145
146
|
const jsName = `${blockName}.js`;
|
|
@@ -164,9 +165,6 @@ initResourceRouter.post('/', async (req, res) => {
|
|
|
164
165
|
catch (error) {
|
|
165
166
|
// ignore error
|
|
166
167
|
}
|
|
167
|
-
// write metadata to metadataPath
|
|
168
|
-
const metadataYmlPath = path.join(componentsDir, `${metadata.name || 'unnamed'}.${metadata.id}.yml`);
|
|
169
|
-
fs.writeFileSync(metadataYmlPath, generateYaml(metadata));
|
|
170
168
|
// Handle preview image if exists
|
|
171
169
|
if (metadata.previewImage) {
|
|
172
170
|
const imagePath = path.join(path.dirname(fullPath), getPreviewImageRelativePath(metadata.previewImage));
|
|
@@ -175,8 +173,59 @@ initResourceRouter.post('/', async (req, res) => {
|
|
|
175
173
|
fs.copyFileSync(imagePath, imageDestPath);
|
|
176
174
|
}
|
|
177
175
|
}
|
|
176
|
+
// Handle type:url properties url
|
|
177
|
+
if (metadata.properties) {
|
|
178
|
+
const downloadPromises = [];
|
|
179
|
+
Object.keys(metadata.properties).forEach((key) => {
|
|
180
|
+
const property = metadata.properties[key].data;
|
|
181
|
+
if (property.type === 'url') {
|
|
182
|
+
// 如果每个 locales 的默认值里面,存在 mediaKitUrl 的值,需要把图片下载放到 imagePath 里面
|
|
183
|
+
Object.keys(property.locales).forEach((locale) => {
|
|
184
|
+
if (property.locales[locale].defaultValue?.mediaKitUrl?.startsWith('mediakit://')) {
|
|
185
|
+
const downloadPromise = (async () => {
|
|
186
|
+
try {
|
|
187
|
+
const fileName = basename(property.locales[locale].defaultValue.mediaKitUrl);
|
|
188
|
+
const targetPath = path.join(componentsDir, fileName);
|
|
189
|
+
// Check if file already exists to avoid redundant downloads
|
|
190
|
+
if (fs.existsSync(targetPath)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const resWithStream = await getMediaKitFileStream(property.locales[locale].defaultValue.mediaKitUrl);
|
|
194
|
+
if (!resWithStream.data) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// create write stream
|
|
198
|
+
const writeStream = fs.createWriteStream(targetPath);
|
|
199
|
+
resWithStream.data.pipe(writeStream);
|
|
200
|
+
// wait for write stream to finish
|
|
201
|
+
await new Promise((resolve, reject) => {
|
|
202
|
+
writeStream.on('finish', () => {
|
|
203
|
+
// 修改 property.locales[locale].defaultValue.url 变为 mediakit:// 开头
|
|
204
|
+
property.locales[locale].defaultValue.url = property.locales[locale].defaultValue.mediaKitUrl;
|
|
205
|
+
resolve();
|
|
206
|
+
});
|
|
207
|
+
writeStream.on('error', reject);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.error('Download media kit file failed:', error.message);
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
214
|
+
downloadPromises.push(downloadPromise);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
// Wait for all downloads to complete for this metadata
|
|
220
|
+
if (downloadPromises.length > 0) {
|
|
221
|
+
await Promise.allSettled(downloadPromises);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// write metadata to metadataPath
|
|
225
|
+
const metadataYmlPath = path.join(componentsDir, `${metadata.name || 'unnamed'}.${metadata.id}.yml`);
|
|
226
|
+
fs.writeFileSync(metadataYmlPath, generateYaml(metadata));
|
|
178
227
|
return metadata;
|
|
179
|
-
});
|
|
228
|
+
}));
|
|
180
229
|
// cp chunks dir
|
|
181
230
|
await copyRecursive(join(codeDir, 'chunks'), chunksDir);
|
|
182
231
|
// write pages.config.yml
|