@abtnode/core 1.17.5-beta-20251209-090953-3a59e7ac → 1.17.5-beta-20251214-122206-29056e8c

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.
@@ -145,6 +145,8 @@ const getExternalPortsFromMeta = (meta) =>
145
145
  const formatBlocklet = (blocklet, phase, dek) => {
146
146
  if (phase === 'onRead') {
147
147
  blocklet.status = getBlockletStatus(blocklet);
148
+ // Ensure children exists before forEachBlockletSync accesses it
149
+ blocklet.children = blocklet.children || [];
148
150
  }
149
151
 
150
152
  forEachBlockletSync(blocklet, (b) => {
@@ -253,6 +255,199 @@ class BlockletState extends BaseState {
253
255
  // @didMap: { [did: string]: metaDid: string }
254
256
  this.didMap = new Map();
255
257
  this.statusLocks = new Map();
258
+
259
+ // BlockletChildState instance passed from outside
260
+ this.BlockletChildState = config.BlockletChildState || null;
261
+ }
262
+
263
+ /**
264
+ * Load children for a blocklet
265
+ * @param {string} blockletId - The blocklet ID
266
+ * @returns {Promise<Array>} - Array of children
267
+ */
268
+ async loadChildren(blockletId) {
269
+ if (!this.BlockletChildState || !blockletId) {
270
+ return [];
271
+ }
272
+
273
+ const children = await this.BlockletChildState.getChildrenByParentId(blockletId);
274
+ // Ensure children is always an array
275
+ if (!Array.isArray(children)) {
276
+ return [];
277
+ }
278
+
279
+ return children.map((child) => {
280
+ // Ensure meta is an object and has required fields
281
+ const meta = child.meta || {};
282
+ if (!meta.did) {
283
+ logger.warn('loadChildren: child missing meta.did', { childId: child.id, parentBlockletId: blockletId });
284
+ }
285
+ if (!meta.name && !meta.bundleName) {
286
+ logger.warn('loadChildren: child missing meta.name and meta.bundleName', {
287
+ childId: child.id,
288
+ childDid: meta.did,
289
+ parentBlockletId: blockletId,
290
+ });
291
+ }
292
+
293
+ const childObj = {
294
+ id: child.id,
295
+ mountPoint: child.mountPoint,
296
+ meta,
297
+ bundleSource: child.bundleSource || {},
298
+ source: child.source || 0,
299
+ deployedFrom: child.deployedFrom || '',
300
+ mode: child.mode || 'production',
301
+ status: child.status || 0,
302
+ ports: child.ports || {},
303
+ environments: child.environments || [],
304
+ children: child.children || [],
305
+ migratedFrom: child.migratedFrom || [],
306
+ installedAt: child.installedAt,
307
+ startedAt: child.startedAt,
308
+ stoppedAt: child.stoppedAt,
309
+ pausedAt: child.pausedAt,
310
+ operator: child.operator,
311
+ inProgressStart: child.inProgressStart,
312
+ greenStatus: child.greenStatus,
313
+ greenPorts: child.greenPorts,
314
+ };
315
+
316
+ // Recursively load children for this child
317
+ if (child.children && child.children.length > 0) {
318
+ // Note: children array now contains IDs, need to load them
319
+ // This will be handled by forEachComponentV2 or similar methods
320
+ }
321
+
322
+ return childObj;
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Save children to BlockletChild table
328
+ * @param {string} blockletId - The parent blocklet ID
329
+ * @param {string} blockletDid - The parent blocklet DID
330
+ * @param {Array} children - Array of children to save
331
+ */
332
+ async saveChildren(blockletId, blockletDid, children) {
333
+ if (!this.BlockletChildState) {
334
+ logger.warn('BlockletChildState is not initialized, cannot save children');
335
+ return;
336
+ }
337
+
338
+ if (!blockletId || !blockletDid) {
339
+ logger.warn('saveChildren called with invalid blockletId or blockletDid', { blockletId, blockletDid });
340
+ return;
341
+ }
342
+
343
+ if (!children || children.length === 0) {
344
+ // If no children provided, delete all existing children
345
+ await this.BlockletChildState.deleteByParentId(blockletId);
346
+ return;
347
+ }
348
+
349
+ // Get existing children
350
+ const existingChildren = await this.BlockletChildState.getChildrenByParentId(blockletId);
351
+ const existingChildrenMap = new Map();
352
+ existingChildren.forEach((child) => {
353
+ existingChildrenMap.set(child.childDid, child);
354
+ });
355
+
356
+ // Track which children should be kept
357
+ const childrenToKeep = new Set();
358
+
359
+ // Process each child: update if exists, insert if new
360
+ for (const child of children) {
361
+ const childDid = child.meta?.did;
362
+ if (!childDid) {
363
+ logger.warn('saveChildren: child missing meta.did, skipping', {
364
+ blockletId,
365
+ blockletDid,
366
+ child: child.meta?.name || 'unknown',
367
+ });
368
+ continue;
369
+ }
370
+
371
+ childrenToKeep.add(childDid);
372
+
373
+ const existingChild = existingChildrenMap.get(childDid);
374
+ const updates = {};
375
+
376
+ // Only update fields that have changed or are necessary
377
+ // Fields that should be updated: meta, bundleSource, source, deployedFrom, mode, ports, children, migratedFrom
378
+ // Fields that should be preserved if not provided: status, installedAt, startedAt, stoppedAt, pausedAt, operator, inProgressStart, greenStatus, greenPorts
379
+
380
+ // Always update these fields
381
+ if (child.mountPoint !== undefined) updates.mountPoint = child.mountPoint;
382
+ if (child.meta !== undefined) updates.meta = child.meta;
383
+ if (child.bundleSource !== undefined) updates.bundleSource = child.bundleSource;
384
+ if (child.source !== undefined) updates.source = child.source;
385
+ if (child.deployedFrom !== undefined) updates.deployedFrom = child.deployedFrom;
386
+ if (child.mode !== undefined) updates.mode = child.mode;
387
+ if (child.ports !== undefined) updates.ports = child.ports;
388
+ if (child.environments !== undefined) updates.environments = child.environments;
389
+
390
+ // Only update status-related fields if explicitly provided
391
+ if (child.status !== undefined) updates.status = child.status;
392
+ // Note: installedAt should only be set on first install, never updated
393
+ if (child.startedAt !== undefined) updates.startedAt = child.startedAt;
394
+ if (child.stoppedAt !== undefined) updates.stoppedAt = child.stoppedAt;
395
+ if (child.pausedAt !== undefined) updates.pausedAt = child.pausedAt;
396
+ if (child.operator !== undefined) updates.operator = child.operator;
397
+ if (child.inProgressStart !== undefined) updates.inProgressStart = child.inProgressStart;
398
+ if (child.greenStatus !== undefined) updates.greenStatus = child.greenStatus;
399
+ if (child.greenPorts !== undefined) updates.greenPorts = child.greenPorts;
400
+
401
+ try {
402
+ if (existingChild) {
403
+ // Update existing child only if there are changes
404
+ if (Object.keys(updates).length > 0) {
405
+ await this.BlockletChildState.update({ id: existingChild.id }, { $set: updates });
406
+ }
407
+ } else {
408
+ // Insert new child
409
+ await this.BlockletChildState.insert({
410
+ parentBlockletId: blockletId,
411
+ parentBlockletDid: blockletDid,
412
+ childDid,
413
+ mountPoint: child.mountPoint,
414
+ meta: child.meta,
415
+ bundleSource: child.bundleSource,
416
+ source: child.source || 0,
417
+ deployedFrom: child.deployedFrom || '',
418
+ mode: child.mode || 'production',
419
+ status: child.status || 0,
420
+ ports: child.ports || {},
421
+ environments: child.environments || [],
422
+ installedAt: new Date(),
423
+ startedAt: child.startedAt,
424
+ stoppedAt: child.stoppedAt,
425
+ pausedAt: child.pausedAt,
426
+ operator: child.operator,
427
+ inProgressStart: child.inProgressStart,
428
+ greenStatus: child.greenStatus,
429
+ greenPorts: child.greenPorts,
430
+ });
431
+ }
432
+ } catch (error) {
433
+ logger.error('saveChildren: failed to save child', {
434
+ blockletId,
435
+ blockletDid,
436
+ childDid,
437
+ isUpdate: !!existingChild,
438
+ error: error.message,
439
+ });
440
+ throw error;
441
+ }
442
+ }
443
+
444
+ // Delete children that are no longer in the list
445
+ const childrenToDelete = existingChildren.filter((child) => !childrenToKeep.has(child.childDid));
446
+ if (childrenToDelete.length > 0) {
447
+ for (const childToDelete of childrenToDelete) {
448
+ await this.BlockletChildState.remove({ id: childToDelete.id });
449
+ }
450
+ }
256
451
  }
257
452
 
258
453
  /**
@@ -268,7 +463,15 @@ class BlockletState extends BaseState {
268
463
  }
269
464
 
270
465
  const doc = await this.findOne({ $or: getConditions(did) });
271
- return doc ? formatBlocklet(doc, 'onRead', decryptSk ? this.config.dek : null) : null;
466
+ if (!doc) {
467
+ return null;
468
+ }
469
+
470
+ // Load children from BlockletChild table
471
+ const children = await this.loadChildren(doc.id);
472
+ doc.children = children;
473
+
474
+ return formatBlocklet(doc, 'onRead', decryptSk ? this.config.dek : null);
272
475
  }
273
476
 
274
477
  async getBlockletMetaDid(did) {
@@ -304,7 +507,16 @@ class BlockletState extends BaseState {
304
507
 
305
508
  async getBlocklets(query = {}, projection = {}, sort = { createdAt: -1 }) {
306
509
  const docs = await this.find(query, projection, sort);
307
- return docs.filter(Boolean).map((doc) => formatBlocklet(doc, 'onRead', this.config.dek));
510
+ const result = [];
511
+
512
+ for (const doc of docs.filter(Boolean)) {
513
+ // Load children for each blocklet
514
+ const children = await this.loadChildren(doc.id);
515
+ doc.children = children;
516
+ result.push(formatBlocklet(doc, 'onRead', this.config.dek));
517
+ }
518
+
519
+ return result;
308
520
  }
309
521
 
310
522
  async deleteBlocklet(did) {
@@ -313,6 +525,11 @@ class BlockletState extends BaseState {
313
525
  throw new CustomError(404, `Try to remove non-existing blocklet ${did}`);
314
526
  }
315
527
 
528
+ // Delete children from BlockletChild table
529
+ if (this.BlockletChildState) {
530
+ await this.BlockletChildState.deleteByParentId(doc.id);
531
+ }
532
+
316
533
  await this.remove({ id: doc.id });
317
534
 
318
535
  this.didMap.delete(doc.meta?.did);
@@ -376,7 +593,6 @@ class BlockletState extends BaseState {
376
593
  deployedFrom,
377
594
  ports,
378
595
  environments: [],
379
- children,
380
596
  migratedFrom,
381
597
  externalSk,
382
598
  externalSkSource,
@@ -384,6 +600,10 @@ class BlockletState extends BaseState {
384
600
  });
385
601
 
386
602
  doc = await this.findOne({ id: doc.id });
603
+ doc.children = children;
604
+
605
+ // Save children to BlockletChild table
606
+ await this.saveChildren(doc.id, doc.meta.did, children);
387
607
 
388
608
  this.emit('add', doc);
389
609
 
@@ -403,6 +623,15 @@ class BlockletState extends BaseState {
403
623
  const formatted = formatBlocklet(omit(cloneDeep(updates), ['vaults']), 'onUpdate', this.config.dek);
404
624
  const [, [updated]] = await this.update({ id: doc.id }, { $set: formatted });
405
625
 
626
+ // If children are being updated, set them on the updated object before calculating status
627
+ // This ensures getBlockletStatus can correctly calculate status based on children
628
+ if (updates.children !== undefined) {
629
+ updated.children = updates.children;
630
+ if (this.BlockletChildState) {
631
+ await this.saveChildren(updated.id, updated.meta.did, updates.children);
632
+ }
633
+ }
634
+
406
635
  updated.status = getBlockletStatus(updated);
407
636
 
408
637
  return updated;
@@ -475,10 +704,13 @@ class BlockletState extends BaseState {
475
704
  meta: omit(sanitized, ['htmlAst']),
476
705
  source,
477
706
  deployedFrom,
478
- children,
479
707
  ports,
480
708
  });
481
709
 
710
+ // Save children to BlockletChild table
711
+ await this.saveChildren(newDoc.id, newDoc.meta.did, children);
712
+ newDoc.children = children;
713
+
482
714
  this.emit('upgrade', newDoc);
483
715
  return newDoc;
484
716
  } finally {
@@ -630,9 +862,19 @@ class BlockletState extends BaseState {
630
862
  });
631
863
 
632
864
  if (actuallyRefreshedDids.length > 0) {
633
- await this.updateBlocklet(did, {
634
- children: blocklet.children,
635
- });
865
+ await this.updateBlocklet(did, {});
866
+
867
+ // Only update ports/greenPorts to avoid overwriting status during concurrent operations
868
+ if (this.BlockletChildState) {
869
+ for (const component of blocklet.children) {
870
+ if (actuallyRefreshedDids.includes(component.meta?.did)) {
871
+ await this.BlockletChildState.updateChildPorts(blocklet.id, component.meta.did, {
872
+ ports: component.ports,
873
+ greenPorts: component.greenPorts,
874
+ });
875
+ }
876
+ }
877
+ }
636
878
  }
637
879
 
638
880
  return {
@@ -643,7 +885,7 @@ class BlockletState extends BaseState {
643
885
  }
644
886
 
645
887
  async getServices() {
646
- const blocklets = await this.getBlocklets({}, { meta: 1, children: 1, ports: 1 });
888
+ const blocklets = await this.getBlocklets({}, { meta: 1, ports: 1 });
647
889
  const services = [];
648
890
 
649
891
  blocklets.forEach((blocklet) => {
@@ -681,7 +923,7 @@ class BlockletState extends BaseState {
681
923
  * @return {Object} { <did> : { interfaceName } }
682
924
  */
683
925
  async groupAllInterfaces() {
684
- const blocklets = await this.getBlocklets({}, { meta: 1, children: 1 });
926
+ const blocklets = await this.getBlocklets({}, { meta: 1 });
685
927
  const result = {};
686
928
  const fillResult = (component, { id }) => {
687
929
  const { interfaces } = component.meta;
@@ -701,6 +943,22 @@ class BlockletState extends BaseState {
701
943
  return result;
702
944
  }
703
945
 
946
+ /**
947
+ * Reset the blocklet state by clearing all blocklets and their children
948
+ * This overrides BaseState.reset() to handle foreign key constraints
949
+ */
950
+ async reset() {
951
+ // First, delete all children to avoid foreign key constraint errors
952
+ if (this.BlockletChildState) {
953
+ const allBlocklets = await this.getBlocklets({}, { id: 1 });
954
+ for (const blocklet of allBlocklets) {
955
+ await this.BlockletChildState.deleteByParentId(blocklet.id);
956
+ }
957
+ }
958
+ // Then call parent reset to clear the blocklets table
959
+ return super.reset();
960
+ }
961
+
704
962
  /**
705
963
  * @param {Did} did blocklet did
706
964
  * @param {BlockletStatus} status blocklet status
@@ -736,6 +994,8 @@ class BlockletState extends BaseState {
736
994
  return res;
737
995
  }
738
996
 
997
+ // Collect components to update
998
+ const componentsToUpdate = [];
739
999
  for (const component of doc.children || []) {
740
1000
  if (component.meta.group === BlockletGroup.gateway) {
741
1001
  continue;
@@ -745,6 +1005,9 @@ class BlockletState extends BaseState {
745
1005
  continue;
746
1006
  }
747
1007
 
1008
+ componentsToUpdate.push(component.meta.did);
1009
+
1010
+ // Update in-memory for return value
748
1011
  component[isGreen ? 'greenStatus' : 'status'] = status;
749
1012
  if (isGreenAndBlue) {
750
1013
  component.greenStatus = status;
@@ -764,14 +1027,32 @@ class BlockletState extends BaseState {
764
1027
 
765
1028
  const shouldSetStatus = status === BlockletStatus.downloading || status === BlockletStatus.waiting;
766
1029
  const updateData = {
767
- children: doc.children,
768
1030
  operator,
769
1031
  };
770
1032
  if (shouldSetStatus) {
771
1033
  updateData.status = status;
772
1034
  }
773
1035
 
1036
+ // Update blocklet without children to avoid overwriting status during concurrent operations
774
1037
  const res = await this.updateBlocklet(did, updateData);
1038
+
1039
+ // Update each component's status individually using BlockletChildState
1040
+ if (this.BlockletChildState && componentsToUpdate.length > 0) {
1041
+ for (const componentDid of componentsToUpdate) {
1042
+ await this.BlockletChildState.updateChildStatus(doc.id, componentDid, {
1043
+ status,
1044
+ isGreen,
1045
+ isGreenAndBlue,
1046
+ operator,
1047
+ });
1048
+ }
1049
+ }
1050
+
1051
+ const children = await this.loadChildren(doc.id);
1052
+
1053
+ res.children = children;
1054
+ // Recalculate status after children are loaded with updated status
1055
+ res.status = getBlockletStatus(res);
775
1056
  return res;
776
1057
  } finally {
777
1058
  await lock.releaseLock(lockName);
@@ -902,7 +1183,7 @@ class BlockletState extends BaseState {
902
1183
  }
903
1184
 
904
1185
  async _getOccupiedPorts() {
905
- const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, meta: 1, children: 1 });
1186
+ const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, meta: 1 });
906
1187
 
907
1188
  const occupiedExternalPorts = new Map();
908
1189
  const occupiedInternalPorts = new Map();
@@ -4,6 +4,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:states');
4
4
 
5
5
  const NodeState = require('./node');
6
6
  const BlockletState = require('./blocklet');
7
+ const BlockletChildState = require('./blocklet-child');
7
8
  const NotificationState = require('./notification');
8
9
  const SiteState = require('./site');
9
10
  const AccessKeyState = require('./access-key');
@@ -33,7 +34,8 @@ const init = (dataDirs, config = {}) => {
33
34
  const notificationState = new NotificationState(models.Notification, config, models);
34
35
 
35
36
  const nodeState = new NodeState(models.Server, config, dataDirs, notificationState);
36
- const blockletState = new BlockletState(models.Blocklet, config);
37
+ const blockletChildState = new BlockletChildState(models.BlockletChild, config);
38
+ const blockletState = new BlockletState(models.Blocklet, { ...config, BlockletChildState: blockletChildState });
37
39
  const siteState = new SiteState(models.Site, config);
38
40
  const accessKeyState = new AccessKeyState(models.AccessKey, config);
39
41
  const webhookState = new WebhookState(models.WebHook, config);
@@ -52,6 +54,7 @@ const init = (dataDirs, config = {}) => {
52
54
  return {
53
55
  node: nodeState,
54
56
  blocklet: blockletState,
57
+ blockletChild: blockletChildState,
55
58
  notification: notificationState,
56
59
  site: siteState,
57
60
  accessKey: accessKeyState,
@@ -965,11 +965,13 @@ class NotificationState extends BaseState {
965
965
  throw new Error('Invalid since format. Expected format: "1h", "2h", "24h", etc.');
966
966
  }
967
967
 
968
- const hours = parseInt(sinceMatch[1], 10);
968
+ let hours = parseInt(sinceMatch[1], 10);
969
969
 
970
970
  // 验证范围:最小 1h,最大 24h
971
971
  if (hours < 1 || hours > 24) {
972
- throw new Error('The since parameter must be between 1h and 24h.');
972
+ logger.warn('The since parameter must be between 1h and 24h.');
973
+ // 限制 hours 在 1-24 范围内
974
+ hours = Math.min(Math.max(hours, 1), 24);
973
975
  }
974
976
 
975
977
  // 计算时间范围
@@ -416,36 +416,6 @@ const getBlockletConfigObj = (blocklet, { excludeSecure } = {}) => {
416
416
 
417
417
  return obj;
418
418
  };
419
- /**
420
- * set 'configs', configObj', 'environmentObj' to blocklet TODO
421
- * @param {*} blocklet
422
- * @param {*} configs
423
- */
424
- const fillBlockletConfigs = (blocklet, configs) => {
425
- blocklet.configs = configs || [];
426
- blocklet.configObj = getBlockletConfigObj(blocklet);
427
- blocklet.environments = blocklet.environments || [];
428
- blocklet.environmentObj = blocklet.environments.reduce((acc, x) => {
429
- acc[x.key] = templateReplace(x.value, blocklet);
430
- return acc;
431
- }, {});
432
- };
433
-
434
- const ensureBlockletExpanded = async (_meta, appDir) => {
435
- const bundlePath = path.join(appDir, BLOCKLET_BUNDLE_FILE);
436
- if (fs.existsSync(bundlePath)) {
437
- try {
438
- const nodeModulesPath = path.join(appDir, 'node_modules');
439
- if (fs.existsSync(nodeModulesPath)) {
440
- fs.removeSync(nodeModulesPath);
441
- }
442
- await expandBundle(bundlePath, appDir);
443
- fs.removeSync(bundlePath);
444
- } catch (err) {
445
- throw new Error(`Failed to expand blocklet bundle: ${err.message}`);
446
- }
447
- }
448
- };
449
419
 
450
420
  const getAppSystemEnvironments = (blocklet, nodeInfo, dataDirs) => {
451
421
  const { did, name, title, description } = blocklet.meta;
@@ -562,6 +532,81 @@ const getComponentSystemEnvironments = (blocklet) => {
562
532
  };
563
533
  };
564
534
 
535
+ /**
536
+ * set 'configs', configObj', 'environmentObj' to blocklet TODO
537
+ * @param {*} blocklet
538
+ * @param {*} configs
539
+ * @param {*} options - Optional: { rootBlocklet, nodeInfo, dataDirs }
540
+ */
541
+ const fillBlockletConfigs = (blocklet, configs, options = {}) => {
542
+ blocklet.configs = configs || [];
543
+ blocklet.configObj = getBlockletConfigObj(blocklet);
544
+ blocklet.environments = blocklet.environments || [];
545
+ blocklet.environmentObj = blocklet.environments.reduce((acc, x) => {
546
+ acc[x.key] = templateReplace(x.value, blocklet);
547
+ return acc;
548
+ }, {});
549
+
550
+ // After migration: ensure all component system environments are set from blocklet.env if available
551
+ // This ensures children loaded from blocklet_children table have all required env vars in environmentObj
552
+ if (blocklet.env) {
553
+ try {
554
+ const componentSystemEnvs = getComponentSystemEnvironments(blocklet);
555
+ // Only set env vars that are not already set
556
+ Object.entries(componentSystemEnvs).forEach(([key, value]) => {
557
+ if (value !== undefined && value !== null && !blocklet.environmentObj[key]) {
558
+ blocklet.environmentObj[key] = value;
559
+ }
560
+ });
561
+ } catch (error) {
562
+ // If getting component system environments fails, log warning but continue
563
+ logger.warn('fillBlockletConfigs: failed to get component system environments', {
564
+ blockletDid: blocklet.meta?.did,
565
+ error: error.message,
566
+ });
567
+ }
568
+ }
569
+
570
+ // For children: also set app-level environment variables from root blocklet
571
+ // This ensures children have app-level env vars like BLOCKLET_APP_ID
572
+ const { rootBlocklet, nodeInfo, dataDirs } = options;
573
+ if (rootBlocklet && nodeInfo && dataDirs && blocklet !== rootBlocklet) {
574
+ try {
575
+ const appSystemEnvs = getAppSystemEnvironments(rootBlocklet, nodeInfo, dataDirs);
576
+ const appOverwrittenEnvs = getAppOverwrittenEnvironments(rootBlocklet, nodeInfo);
577
+ // Only set env vars that are not already set
578
+ Object.entries({ ...appSystemEnvs, ...appOverwrittenEnvs }).forEach(([key, value]) => {
579
+ if (value !== undefined && value !== null && !blocklet.environmentObj[key]) {
580
+ blocklet.environmentObj[key] = value;
581
+ }
582
+ });
583
+ } catch (error) {
584
+ // If getting app system environments fails, log warning but continue
585
+ logger.warn('fillBlockletConfigs: failed to get app system environments', {
586
+ blockletDid: blocklet.meta?.did,
587
+ rootDid: rootBlocklet.meta?.did,
588
+ error: error.message,
589
+ });
590
+ }
591
+ }
592
+ };
593
+
594
+ const ensureBlockletExpanded = async (_meta, appDir) => {
595
+ const bundlePath = path.join(appDir, BLOCKLET_BUNDLE_FILE);
596
+ if (fs.existsSync(bundlePath)) {
597
+ try {
598
+ const nodeModulesPath = path.join(appDir, 'node_modules');
599
+ if (fs.existsSync(nodeModulesPath)) {
600
+ fs.removeSync(nodeModulesPath);
601
+ }
602
+ await expandBundle(bundlePath, appDir);
603
+ fs.removeSync(bundlePath);
604
+ } catch (err) {
605
+ throw new Error(`Failed to expand blocklet bundle: ${err.message}`);
606
+ }
607
+ }
608
+ };
609
+
565
610
  const getRuntimeEnvironments = (blocklet, nodeEnvironments, ancestors, isGreen = false) => {
566
611
  const root = (ancestors || [])[0] || blocklet;
567
612
 
@@ -1847,6 +1892,7 @@ const formatBlockletTheme = (rawTheme) => {
1847
1892
  themeConfig = {
1848
1893
  ...concept.themeConfig,
1849
1894
  prefer: concept.prefer,
1895
+ name: concept.name,
1850
1896
  };
1851
1897
  } else {
1852
1898
  // 兼容旧数据
@@ -1855,6 +1901,7 @@ const formatBlockletTheme = (rawTheme) => {
1855
1901
  dark: rawTheme.dark || {},
1856
1902
  common: rawTheme.common || {},
1857
1903
  prefer: rawTheme.prefer || 'system',
1904
+ name: rawTheme.name || 'Default',
1858
1905
  };
1859
1906
  }
1860
1907
  }
@@ -1970,21 +2017,44 @@ const _getBlocklet = async ({
1970
2017
 
1971
2018
  await forEachBlocklet(blocklet, async (component, { id, level, ancestors }) => {
1972
2019
  // component env
1973
- component.env = {
1974
- id,
1975
- name: getComponentName(component, ancestors),
1976
- processId: getComponentProcessId(component, ancestors),
1977
- ...getComponentDirs(component, {
1978
- dataDirs,
1979
- ensure: ensureIntegrity,
1980
- ancestors,
1981
- e2eMode: level === 0 ? e2eMode : false,
1982
- }),
1983
- };
2020
+ try {
2021
+ // Validate component has required meta fields for getComponentDirs
2022
+ if (!component.meta) {
2023
+ throw new Error(`Component missing meta field: ${component.meta?.did || id}`);
2024
+ }
2025
+ if (!component.meta.name && !component.meta.bundleName) {
2026
+ throw new Error(
2027
+ `Component missing meta.name and meta.bundleName: ${component.meta.did || id}. ` +
2028
+ 'This may indicate a migration issue with blocklet_children table.'
2029
+ );
2030
+ }
2031
+
2032
+ component.env = {
2033
+ id,
2034
+ name: getComponentName(component, ancestors),
2035
+ processId: getComponentProcessId(component, ancestors),
2036
+ ...getComponentDirs(component, {
2037
+ dataDirs,
2038
+ ensure: ensureIntegrity,
2039
+ ancestors,
2040
+ e2eMode: level === 0 ? e2eMode : false,
2041
+ }),
2042
+ };
2043
+ } catch (error) {
2044
+ logger.error('Failed to set component env in _getBlocklet', {
2045
+ componentDid: component.meta?.did,
2046
+ componentName: component.meta?.name,
2047
+ componentBundleName: component.meta?.bundleName,
2048
+ error: error.message,
2049
+ stack: error.stack,
2050
+ });
2051
+ throw error;
2052
+ }
1984
2053
 
1985
2054
  // component config
1986
2055
  const configs = await states.blockletExtras.getConfigs([...ancestors.map((x) => x.meta.did), component.meta.did]);
1987
- fillBlockletConfigs(component, configs);
2056
+ const rootBlocklet = ancestors.length > 0 ? ancestors[0] : blocklet;
2057
+ fillBlockletConfigs(component, configs, { rootBlocklet, nodeInfo, dataDirs });
1988
2058
  });
1989
2059
 
1990
2060
  if (getOptionalComponents) {