@abtnode/core 1.16.52-beta-20251003-083412-fdfc4e36 → 1.16.52-beta-20251008-091027-c46c73e3

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.
@@ -73,6 +73,7 @@ const { encrypt } = require('@blocklet/sdk/lib/security');
73
73
  const { generateRandomString } = require('@abtnode/models/lib/util');
74
74
  const { getComponentApiKey, getBlockletLogos } = require('@abtnode/util/lib/blocklet');
75
75
  const { getEmailServiceProvider, emailConfigSchema } = require('@abtnode/auth/lib/email');
76
+ const { Joi } = require('@arcblock/validator');
76
77
 
77
78
  const {
78
79
  BlockletStatus,
@@ -134,6 +135,9 @@ const {
134
135
  refresh: refreshAccessibleExternalNodeIp,
135
136
  getFromCache: getAccessibleExternalNodeIp,
136
137
  } = require('../../util/get-accessible-external-node-ip');
138
+ const { blueGreenStartBlocklet } = require('./helper/blue-green-start-blocklet.js');
139
+ const { blueGreenUpgradeBlocklet } = require('./helper/blue-green-upgrade-blocklet.js');
140
+
137
141
  const {
138
142
  getAppSystemEnvironments,
139
143
  getComponentSystemEnvironments,
@@ -175,6 +179,7 @@ const {
175
179
  getPackConfig,
176
180
  copyPackImages,
177
181
  filterRequiredComponents,
182
+ getHookArgs,
178
183
  } = require('../../util/blocklet');
179
184
  const states = require('../../states');
180
185
  const BaseBlockletManager = require('./base');
@@ -246,6 +251,7 @@ const { blockletThemeSchema } = require('../../validators/theme');
246
251
  const { removeDockerNetwork } = require('../../util/docker/docker-network.js');
247
252
  const parseDockerName = require('../../util/docker/parse-docker-name.js');
248
253
  const { verifyAigneConfig, decryptValue } = require('../../util/aigne-verify');
254
+ const { blueGreenGetComponentIds } = require('./helper/blue-green-get-componentids.js');
249
255
 
250
256
  const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
251
257
 
@@ -271,16 +277,6 @@ const USER_PROFILE_SYNC_FIELDS = [
271
277
  'phoneVerified',
272
278
  ];
273
279
 
274
- const getHookArgs = (blocklet) => ({
275
- output: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '' : path.join(blocklet.env.logsDir, 'output.log'),
276
- error: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '' : path.join(blocklet.env.logsDir, 'error.log'),
277
- timeout:
278
- Math.max(
279
- get(blocklet, 'meta.timeout.script', 120),
280
- ...(blocklet?.children || []).map((child) => child.meta?.timeout?.script || 0)
281
- ) * 1000,
282
- });
283
-
284
280
  const pm2StatusMap = {
285
281
  online: BlockletStatus.running,
286
282
  stop: BlockletStatus.stopped,
@@ -730,7 +726,8 @@ class DiskBlockletManager extends BaseBlockletManager {
730
726
  }
731
727
 
732
728
  async checkComponentsForUpdates({ did }) {
733
- await this.checkControllerStatus(await this.ensureBlocklet(did), 'upgrade');
729
+ const blocklet = await this.ensureBlocklet(did);
730
+ await this.checkControllerStatus(blocklet, 'upgrade');
734
731
  return UpgradeComponents.check({ did, states });
735
732
  }
736
733
 
@@ -876,6 +873,12 @@ class DiskBlockletManager extends BaseBlockletManager {
876
873
  ) {
877
874
  const operator = _operator || context?.user?.did;
878
875
 
876
+ try {
877
+ await this.deleteProcess({ did, componentDids, isGreen: true });
878
+ } catch {
879
+ // do nothing
880
+ }
881
+
879
882
  logger.info('start blocklet', {
880
883
  did: did || inputBlocklet.meta.did,
881
884
  componentDids,
@@ -1047,7 +1050,10 @@ class DiskBlockletManager extends BaseBlockletManager {
1047
1050
  });
1048
1051
 
1049
1052
  // check blocklet healthy
1050
- const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(blocklet, { checkHealthImmediately });
1053
+ const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(blocklet, {
1054
+ checkHealthImmediately,
1055
+ componentDids,
1056
+ });
1051
1057
  const params = {
1052
1058
  did,
1053
1059
  context,
@@ -1117,7 +1123,11 @@ class DiskBlockletManager extends BaseBlockletManager {
1117
1123
  const { processId } = blocklet.env;
1118
1124
 
1119
1125
  if (updateStatus) {
1120
- const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping, { componentDids, operator });
1126
+ const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping, {
1127
+ componentDids,
1128
+ operator,
1129
+ isGreenAndBlue: true,
1130
+ });
1121
1131
  blocklet.status = BlockletStatus.stopping;
1122
1132
  this.emit(BlockletEvents.statusChange, doc);
1123
1133
  }
@@ -1159,11 +1169,16 @@ class DiskBlockletManager extends BaseBlockletManager {
1159
1169
  });
1160
1170
  },
1161
1171
  componentDids,
1172
+ isStopGreenAndBlue: true,
1162
1173
  });
1163
1174
  } catch (error) {
1164
1175
  logger.error('Failed to stop blocklet', { error, did });
1165
1176
  if (updateStatus) {
1166
- const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids, operator });
1177
+ const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
1178
+ componentDids,
1179
+ operator,
1180
+ isGreenAndBlue: true,
1181
+ });
1167
1182
  this.emit(BlockletEvents.statusChange, res);
1168
1183
  }
1169
1184
  throw error;
@@ -1174,7 +1189,11 @@ class DiskBlockletManager extends BaseBlockletManager {
1174
1189
  launcher.notifyBlockletStopped(blocklet);
1175
1190
 
1176
1191
  if (updateStatus) {
1177
- const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, { componentDids, operator });
1192
+ const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
1193
+ componentDids,
1194
+ operator,
1195
+ isGreenAndBlue: true,
1196
+ });
1178
1197
  // send notification to websocket channel
1179
1198
  this.emit(BlockletEvents.statusChange, res);
1180
1199
 
@@ -1286,10 +1305,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1286
1305
  logger.info('restart blocklet', { did, operator });
1287
1306
  const blocklet = await this.getBlocklet(did);
1288
1307
  await this.checkControllerStatus(blocklet, 'restart');
1289
- await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping, {
1290
- componentDids,
1291
- operator,
1292
- });
1308
+
1293
1309
  const result = await states.blocklet.getBlocklet(did);
1294
1310
  this.emit(BlockletEvents.statusChange, result);
1295
1311
 
@@ -1302,12 +1318,9 @@ class DiskBlockletManager extends BaseBlockletManager {
1302
1318
  operator,
1303
1319
  context,
1304
1320
  });
1305
- ticket.on('failed', async (err) => {
1321
+ ticket.on('failed', (err) => {
1306
1322
  logger.error('failed to restart blocklet', { did, error: err });
1307
1323
 
1308
- const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, { componentDids, operator });
1309
- this.emit(BlockletEvents.statusChange, state);
1310
-
1311
1324
  const description = `${getComponentNamesWithVersion(result, componentDids)} restart failed for ${getDisplayName(
1312
1325
  result
1313
1326
  )}: ${err.message || 'queue exception'}`;
@@ -1640,20 +1653,23 @@ class DiskBlockletManager extends BaseBlockletManager {
1640
1653
 
1641
1654
  const { name, did, version } = blocklet.meta;
1642
1655
 
1643
- if (![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.status)) {
1656
+ if (
1657
+ ![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.greenStatus) &&
1658
+ ![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.status)
1659
+ ) {
1644
1660
  throw new Error(`Can not cancel blocklet that status is ${fromBlockletStatus(blocklet.status)}`);
1645
1661
  }
1646
1662
 
1647
1663
  const job = await this.installQueue.get(did);
1648
1664
 
1649
1665
  // cancel job
1650
- if (blocklet.status === BlockletStatus.downloading) {
1666
+ if (blocklet.greenStatus === BlockletStatus.downloading || blocklet.status === BlockletStatus.downloading) {
1651
1667
  try {
1652
1668
  await this.blockletDownloader.cancelDownload(blocklet.meta.did);
1653
1669
  } catch (error) {
1654
1670
  logger.error('failed to exec blockletDownloader.download', { did: blocklet.meta.did, error });
1655
1671
  }
1656
- } else if (blocklet.status === BlockletStatus.waiting) {
1672
+ } else if (blocklet.greenStatus === BlockletStatus.waiting || blocklet.status === BlockletStatus.waiting) {
1657
1673
  try {
1658
1674
  await this.installQueue.cancel(blocklet.meta.did);
1659
1675
  } catch (error) {
@@ -1670,7 +1686,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1670
1686
  if (data) {
1671
1687
  const { action, oldBlocklet } = data;
1672
1688
  await this._rollback(action, did, oldBlocklet);
1673
- await this._rollbackCache.remove({ did });
1689
+ await this._rollbackCache.remove({ did: inputDid });
1674
1690
  } else {
1675
1691
  throw new Error(`Cannot find rollback data in queue or backup file of blocklet ${inputDid}`);
1676
1692
  }
@@ -1686,7 +1702,10 @@ class DiskBlockletManager extends BaseBlockletManager {
1686
1702
  if (blocklet) {
1687
1703
  const componentDids = [];
1688
1704
  forEachComponentV2Sync(blocklet, (x) => {
1689
- if ([BlockletStatus.waiting, BlockletStatus.downloading].includes(x.status)) {
1705
+ if (
1706
+ [BlockletStatus.waiting, BlockletStatus.downloading].includes(x.status) ||
1707
+ [BlockletStatus.waiting, BlockletStatus.downloading].includes(x.greenStatus)
1708
+ ) {
1690
1709
  componentDids.push(x.meta.did);
1691
1710
  }
1692
1711
  });
@@ -1712,23 +1731,23 @@ class DiskBlockletManager extends BaseBlockletManager {
1712
1731
  * @param {*} context
1713
1732
  * @returns
1714
1733
  */
1715
- async deleteProcess({ did, componentDids, shouldUpdateBlockletStatus = true }, context) {
1734
+ deleteProcess = async ({ did, componentDids, shouldUpdateBlockletStatus = true, isGreen = false }, context) => {
1716
1735
  const blocklet = await this.getBlocklet(did);
1717
1736
 
1718
1737
  logger.info('delete blocklet process', { did, componentDids });
1719
1738
 
1720
- await deleteBlockletProcess(blocklet, { ...context, componentDids });
1739
+ await deleteBlockletProcess(blocklet, { ...context, componentDids, isGreen });
1721
1740
  logger.info('blocklet process deleted successfully', { did, componentDids });
1722
1741
 
1723
1742
  // 有些情况不需要更新 blocklet 状态, 比如下载完成,安装之前清理 process 时, 不需要更新 blocklet 状态
1724
1743
  if (shouldUpdateBlockletStatus) {
1725
- const result = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, { componentDids });
1744
+ const result = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, { componentDids, isGreen });
1726
1745
  logger.info('blocklet status updated to stopped after deleted processes', { did, componentDids });
1727
1746
  return result;
1728
1747
  }
1729
1748
 
1730
1749
  return blocklet;
1731
- }
1750
+ };
1732
1751
 
1733
1752
  // Get blocklet by blockletDid or appDid
1734
1753
  async detail(
@@ -2907,7 +2926,7 @@ class DiskBlockletManager extends BaseBlockletManager {
2907
2926
  return summary;
2908
2927
  }
2909
2928
 
2910
- async updateBlockletSettings({ did, enableSessionHardening, invite, gateway, aigne }, context) {
2929
+ async updateBlockletSettings({ did, enableSessionHardening, invite, gateway, aigne, org }, context) {
2911
2930
  const params = {};
2912
2931
  if (!isNil(enableSessionHardening)) {
2913
2932
  params.enableSessionHardening = enableSessionHardening;
@@ -2939,6 +2958,26 @@ class DiskBlockletManager extends BaseBlockletManager {
2939
2958
  url: getOriginUrl(aigne.url),
2940
2959
  };
2941
2960
  }
2961
+
2962
+ let shouldRotateSession = false;
2963
+ if (!isNil(org)) {
2964
+ const ORG_SCHEMA = Joi.object({
2965
+ enabled: Joi.boolean().required(),
2966
+ maxMemberPerOrg: Joi.number().required().min(2).default(100),
2967
+ maxOrgPerUser: Joi.number().required().min(1).default(10),
2968
+ });
2969
+ const { error } = ORG_SCHEMA.validate(org);
2970
+ if (error) {
2971
+ throw new CustomError(400, error.message);
2972
+ }
2973
+ const currentState = await this.getBlocklet(did, { useCache: true });
2974
+ const orgEnabled = get(currentState, 'settings.org.enabled', false);
2975
+ if (orgEnabled !== org.enabled) {
2976
+ shouldRotateSession = true;
2977
+ }
2978
+ params.org = org;
2979
+ }
2980
+
2942
2981
  const keys = Object.keys(params);
2943
2982
  if (!keys.length) {
2944
2983
  throw new Error('No settings to update');
@@ -2956,6 +2995,10 @@ class DiskBlockletManager extends BaseBlockletManager {
2956
2995
  this.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
2957
2996
  this.emit(BlockletEvents.updated, { ...newState, context });
2958
2997
 
2998
+ if (shouldRotateSession) {
2999
+ this.teamAPI.rotateSessionKey({ teamDid: did });
3000
+ }
3001
+
2959
3002
  return newState;
2960
3003
  }
2961
3004
 
@@ -3208,7 +3251,22 @@ class DiskBlockletManager extends BaseBlockletManager {
3208
3251
  return x;
3209
3252
  });
3210
3253
 
3254
+ const blueGreenComponentIds = await blueGreenGetComponentIds(oldBlocklet || blocklet, componentDids);
3255
+
3211
3256
  try {
3257
+ await Promise.all(
3258
+ blueGreenComponentIds.map(async (item) => {
3259
+ if (item.componentDids.length === 0) {
3260
+ return;
3261
+ }
3262
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading, {
3263
+ componentDids: item.componentDids,
3264
+ isGreen: item.changeToGreen,
3265
+ });
3266
+ })
3267
+ );
3268
+ const state = await states.blocklet.getBlocklet(did);
3269
+ this.emit(BlockletEvents.statusChange, state);
3212
3270
  const { isCancelled } = await this._downloadBlocklet(
3213
3271
  {
3214
3272
  ...blocklet,
@@ -3269,8 +3327,10 @@ class DiskBlockletManager extends BaseBlockletManager {
3269
3327
 
3270
3328
  // rollback on download failed
3271
3329
  await statusLock.acquire('rollback-on-download-failed');
3330
+
3272
3331
  try {
3273
- if ((await states.blocklet.getBlockletStatus(did)) === BlockletStatus.downloading) {
3332
+ const status = await states.blocklet.getBlockletStatus(did);
3333
+ if ([BlockletStatus.downloading, BlockletStatus.waiting].includes(status)) {
3274
3334
  await this._rollback(postAction, did, oldBlocklet);
3275
3335
  }
3276
3336
  } catch (error) {
@@ -3289,25 +3349,38 @@ class DiskBlockletManager extends BaseBlockletManager {
3289
3349
  // update status
3290
3350
  await statusLock.acquire('download-update-status');
3291
3351
  try {
3292
- if ((await states.blocklet.getBlockletStatus(did)) !== BlockletStatus.downloading) {
3293
- throw new Error('blocklet status changed durning download');
3294
- }
3352
+ await Promise.all(
3353
+ blueGreenComponentIds.map(async (item) => {
3354
+ if (item.componentDids.length === 0) {
3355
+ return;
3356
+ }
3357
+ if ((await states.blocklet.getBlockletStatus(did)) !== BlockletStatus.downloading) {
3358
+ throw new Error('blocklet status changed durning download');
3359
+ }
3295
3360
 
3296
- if (postAction === INSTALL_ACTIONS.INSTALL) {
3297
- const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing, { componentDids });
3298
- this.emit(BlockletEvents.statusChange, state);
3299
- }
3361
+ if (postAction === INSTALL_ACTIONS.INSTALL) {
3362
+ const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing, {
3363
+ componentDids: item.componentDids,
3364
+ isGreen: item.changeToGreen,
3365
+ });
3366
+ this.emit(BlockletEvents.statusChange, state);
3367
+ }
3300
3368
 
3301
- if (
3302
- [
3303
- INSTALL_ACTIONS.INSTALL_COMPONENT,
3304
- INSTALL_ACTIONS.UPGRADE_COMPONENT,
3305
- 'upgrade', // for backward compatibility
3306
- ].includes(postAction)
3307
- ) {
3308
- const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading, { componentDids });
3309
- this.emit(BlockletEvents.statusChange, state);
3310
- }
3369
+ if (
3370
+ [
3371
+ INSTALL_ACTIONS.INSTALL_COMPONENT,
3372
+ INSTALL_ACTIONS.UPGRADE_COMPONENT,
3373
+ 'upgrade', // for backward compatibility
3374
+ ].includes(postAction)
3375
+ ) {
3376
+ const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading, {
3377
+ componentDids: item.componentDids,
3378
+ isGreen: item.changeToGreen,
3379
+ });
3380
+ this.emit(BlockletEvents.statusChange, state);
3381
+ }
3382
+ })
3383
+ );
3311
3384
  } catch (error) {
3312
3385
  logger.error(error.message);
3313
3386
  } finally {
@@ -3335,15 +3408,30 @@ class DiskBlockletManager extends BaseBlockletManager {
3335
3408
  logger.info('do upgrade blocklet', { did, version, postAction, componentDids });
3336
3409
 
3337
3410
  try {
3338
- await this._upgradeBlocklet({
3339
- newBlocklet: blocklet,
3340
- oldBlocklet,
3341
- componentDids,
3342
- context,
3343
- action: postAction,
3344
- shouldCleanUploadFile,
3345
- url,
3346
- });
3411
+ if (postAction === INSTALL_ACTIONS.UPGRADE_COMPONENT) {
3412
+ await blueGreenUpgradeBlocklet(
3413
+ {
3414
+ newBlocklet: blocklet,
3415
+ oldBlocklet,
3416
+ componentDids,
3417
+ action: postAction,
3418
+ shouldCleanUploadFile,
3419
+ url,
3420
+ },
3421
+ context,
3422
+ this,
3423
+ states
3424
+ );
3425
+ } else {
3426
+ await this._upgradeBlocklet({
3427
+ newBlocklet: blocklet,
3428
+ oldBlocklet,
3429
+ componentDids,
3430
+ action: postAction,
3431
+ shouldCleanUploadFile,
3432
+ url,
3433
+ });
3434
+ }
3347
3435
 
3348
3436
  const newBlocklet = await this.getBlocklet(did);
3349
3437
 
@@ -3402,11 +3490,13 @@ class DiskBlockletManager extends BaseBlockletManager {
3402
3490
  }
3403
3491
 
3404
3492
  async _onRestart({ did, componentDids, context, operator }) {
3405
- await this.stop({ did, componentDids, updateStatus: false, operator }, context);
3406
- await this.start({ did, componentDids, operator }, context);
3493
+ await blueGreenStartBlocklet({ did, componentDids, operator }, context, this, states);
3407
3494
  }
3408
3495
 
3409
- async _onCheckIfStarted(jobInfo, { throwOnError, skipRunningCheck = false } = {}) {
3496
+ _onCheckIfStarted = async (
3497
+ jobInfo,
3498
+ { throwOnError, skipRunningCheck = false, isGreen = false, needUpdateBlueStatus = true } = {}
3499
+ ) => {
3410
3500
  const startedAt = Date.now();
3411
3501
  const { did, context, minConsecutiveTime = 2000, timeout, componentDids } = jobInfo;
3412
3502
  const blocklet = await this.getBlocklet(did);
@@ -3415,38 +3505,73 @@ class DiskBlockletManager extends BaseBlockletManager {
3415
3505
  const { name } = meta;
3416
3506
 
3417
3507
  const nodeInfo = await states.node.read();
3508
+ const successBlockletIds = new Set();
3418
3509
  try {
3419
3510
  if (!skipRunningCheck) {
3420
3511
  await checkBlockletProcessHealthy(blocklet, {
3421
3512
  minConsecutiveTime,
3422
3513
  timeout,
3423
3514
  componentDids,
3515
+ isGreen,
3424
3516
  enableDocker: nodeInfo.enableDocker,
3425
3517
  setBlockletRunning: async (componentDid) => {
3426
- await states.blocklet.setBlockletStatus(did, BlockletStatus.running, { componentDids: [componentDid] });
3518
+ successBlockletIds.add(componentDid);
3519
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
3520
+ componentDids: [componentDid],
3521
+ isGreen,
3522
+ });
3427
3523
  },
3428
3524
  });
3429
3525
  }
3430
3526
 
3431
- const runningRes = await states.blocklet.setBlockletStatus(did, BlockletStatus.running, { componentDids });
3527
+ const runningRes = await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
3528
+ componentDids: successBlockletIds.size > 0 ? Array.from(successBlockletIds) : componentDids,
3529
+ isGreen,
3530
+ });
3432
3531
 
3433
- const res = await this.getBlocklet(did);
3532
+ if (needUpdateBlueStatus) {
3533
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
3534
+ componentDids,
3535
+ isGreen: !isGreen,
3536
+ });
3537
+ }
3434
3538
 
3539
+ const nextBlocklet = await this.getBlocklet(did);
3540
+
3541
+ await this.configSynchronizer.throttledSyncAppConfig(nextBlocklet);
3542
+ const componentsInfo = getComponentsInternalInfo(nextBlocklet);
3435
3543
  this.emit(BlockletInternalEvents.componentStarted, {
3436
3544
  appDid: blocklet.appDid,
3437
- components: (componentDids || []).map((x) => ({ did: x })),
3545
+ components: componentsInfo,
3438
3546
  });
3439
- this.configSynchronizer.throttledSyncAppConfig(res);
3440
3547
 
3441
- this.emit(BlockletEvents.statusChange, res);
3442
- this.emit(BlockletEvents.started, { ...res, componentDids });
3548
+ this.emit(BlockletEvents.statusChange, nextBlocklet);
3549
+ this.emit(BlockletEvents.started, { ...nextBlocklet, componentDids });
3443
3550
 
3444
3551
  launcher.notifyBlockletStarted(blocklet);
3445
3552
 
3446
3553
  logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
3447
3554
 
3555
+ if (needUpdateBlueStatus) {
3556
+ try {
3557
+ await this.deleteProcess({ did, componentDids, isGreen: !isGreen }, context);
3558
+ } catch {
3559
+ logger.error('delete process failed', { did, componentDids, isGreen });
3560
+ }
3561
+ }
3562
+
3448
3563
  return runningRes;
3449
3564
  } catch (error) {
3565
+ const errorBlockletIds = [];
3566
+ for (const componentDid of componentDids) {
3567
+ if (!successBlockletIds.has(componentDid)) {
3568
+ errorBlockletIds.push(componentDid);
3569
+ }
3570
+ }
3571
+ if (errorBlockletIds.length === 0) {
3572
+ return this.getBlocklet(did);
3573
+ }
3574
+
3450
3575
  const status = await states.blocklet.getBlockletStatus(did);
3451
3576
  if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
3452
3577
  logger.info(`Check blocklet healthy failing because blocklet is ${fromBlockletStatus(status)}`);
@@ -3455,12 +3580,21 @@ class DiskBlockletManager extends BaseBlockletManager {
3455
3580
 
3456
3581
  logger.error('check blocklet if started failed', { did, name, context, timeout, error });
3457
3582
 
3458
- await this.deleteProcess({ did, componentDids }, context);
3459
- const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
3583
+ await this.deleteProcess({ did, componentDids: errorBlockletIds, isGreen }, context);
3460
3584
 
3461
- const componentNames = getComponentNamesWithVersion(blocklet, componentDids);
3585
+ let doc;
3586
+ if (isGreen) {
3587
+ doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
3588
+ componentDids: errorBlockletIds,
3589
+ isGreen,
3590
+ });
3591
+ } else {
3592
+ doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids: errorBlockletIds });
3593
+ }
3594
+
3595
+ const componentNames = getComponentNamesWithVersion(blocklet, errorBlockletIds);
3462
3596
  const description = componentNames
3463
- ? `${getComponentNamesWithVersion(blocklet, componentDids)} start failed for ${getDisplayName(blocklet)}: ${
3597
+ ? `${getComponentNamesWithVersion(blocklet, errorBlockletIds)} start failed for ${getDisplayName(blocklet)}: ${
3464
3598
  error.message
3465
3599
  }`
3466
3600
  : `${blocklet.meta.title} start failed: ${error.message}`;
@@ -3473,7 +3607,11 @@ class DiskBlockletManager extends BaseBlockletManager {
3473
3607
  severity: 'error',
3474
3608
  });
3475
3609
 
3476
- this.emit(BlockletEvents.startFailed, { ...doc, componentDids, error: { message: error.message } });
3610
+ this.emit(BlockletEvents.startFailed, {
3611
+ ...doc,
3612
+ componentDids: errorBlockletIds,
3613
+ error: { message: error.message },
3614
+ });
3477
3615
  this.emit(BlockletEvents.statusChange, { ...doc, error });
3478
3616
 
3479
3617
  if (throwOnError) {
@@ -3482,7 +3620,7 @@ class DiskBlockletManager extends BaseBlockletManager {
3482
3620
 
3483
3621
  return doc;
3484
3622
  }
3485
- }
3623
+ };
3486
3624
 
3487
3625
  /**
3488
3626
  * @param {{
@@ -4401,7 +4539,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4401
4539
  }
4402
4540
  }
4403
4541
 
4404
- async _runMigration({ needRunDocker, did, blocklet, oldBlocklet, componentDids }) {
4542
+ async _runMigration({ parallel, did, blocklet, oldBlocklet, componentDids }) {
4405
4543
  logger.info('start migration on upgrading', { did, componentDids });
4406
4544
  const oldVersions = {};
4407
4545
  forEachComponentV2Sync(oldBlocklet, (b) => {
@@ -4471,8 +4609,8 @@ class DiskBlockletManager extends BaseBlockletManager {
4471
4609
  }
4472
4610
  };
4473
4611
  await forEachComponentV2(blocklet, runMigration, {
4474
- parallel: !needRunDocker,
4475
- concurrencyLimit: needRunDocker ? 1 : 4,
4612
+ parallel,
4613
+ concurrencyLimit: parallel ? 4 : 1,
4476
4614
  });
4477
4615
  logger.info('done migration on upgrading', { did, componentDids });
4478
4616
  }
@@ -4530,7 +4668,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4530
4668
 
4531
4669
  await this._runUserHook('preFlight', blocklet, context);
4532
4670
 
4533
- await this._runMigration({ needRunDocker, did, blocklet, oldBlocklet, componentDids });
4671
+ await this._runMigration({ parallel: !needRunDocker, did, blocklet, oldBlocklet, componentDids });
4534
4672
 
4535
4673
  // handle component status
4536
4674
  const runningDids = [];
@@ -4696,15 +4834,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4696
4834
 
4697
4835
  return this.blockletDownloader.download(blocklet, {
4698
4836
  ...context,
4699
- preDownload: async ({ downloadComponentIds }) => {
4700
- // update children status
4701
- if (downloadComponentIds?.length) {
4702
- const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading, {
4703
- componentDids: downloadComponentIds,
4704
- });
4705
- this.emit(BlockletEvents.statusChange, blocklet1);
4706
- }
4707
- },
4837
+ // preDownload: () => {},
4708
4838
  onProgress: (data) => {
4709
4839
  this.emit(BlockletEvents.downloadBundleProgress, { appDid: appDid || did, meta: { did }, ...data });
4710
4840
  },
@@ -177,7 +177,8 @@ class EnsureBlockletRunning {
177
177
  const { did } = rootBlocklet.meta;
178
178
  if (rootBlocklet.children) {
179
179
  for (const childBlocklet of rootBlocklet.children) {
180
- const isRunning = runningStatuses.includes(childBlocklet.status);
180
+ const isRunning =
181
+ runningStatuses.includes(childBlocklet.status) || childBlocklet.greenStatus === BlockletStatus.running;
181
182
  const isInProgress = inProgressStatuses.includes(childBlocklet.status);
182
183
  if (isRunning || isInProgress) {
183
184
  if (!this.runningBlocklets[did]) {
@@ -207,7 +208,7 @@ class EnsureBlockletRunning {
207
208
  return async () => {
208
209
  if (!shouldCheckHealthy(blocklet)) {
209
210
  // 如果 blocklet 是不需要启动的,并且不是 running,则设置为 running 状态
210
- if (blocklet.status !== BlockletStatus.running) {
211
+ if (blocklet.status !== BlockletStatus.running && blocklet.greenStatus !== BlockletStatus.running) {
211
212
  await this.states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
212
213
  componentDids: [blocklet.meta.did],
213
214
  });
@@ -0,0 +1,59 @@
1
+ const { BlockletStatus } = require('../../../states/blocklet');
2
+ const { forEachBlocklet } = require('../../../util/blocklet');
3
+
4
+ const blueGreenGetComponentIds = async (blocklet, componentDids) => {
5
+ if (!blocklet) {
6
+ return {
7
+ componentDids: componentDids || [],
8
+ changeToGreen: false,
9
+ };
10
+ }
11
+ const componentDidsSet = new Set(componentDids);
12
+ const runningStatuses = new Set([BlockletStatus.running]);
13
+ const greenComponentIds = [];
14
+ const blueComponentIds = [];
15
+
16
+ const activeStatuses = new Set([BlockletStatus.running, BlockletStatus.starting]);
17
+ const children = blocklet.children || [];
18
+ const hasActiveInstance =
19
+ activeStatuses.has(blocklet.status) ||
20
+ activeStatuses.has(blocklet.greenStatus) ||
21
+ children.some((child) => activeStatuses.has(child.status) || activeStatuses.has(child.greenStatus));
22
+
23
+ if (!hasActiveInstance) {
24
+ return [
25
+ {
26
+ componentDids: Array.from(componentDidsSet),
27
+ changeToGreen: false,
28
+ },
29
+ ];
30
+ }
31
+
32
+ await forEachBlocklet(
33
+ blocklet,
34
+ (b) => {
35
+ if (!componentDidsSet.has(b.meta.did)) {
36
+ return;
37
+ }
38
+ if (runningStatuses.has(b.greenStatus)) {
39
+ greenComponentIds.push(b.meta.did);
40
+ } else {
41
+ blueComponentIds.push(b.meta.did);
42
+ }
43
+ },
44
+ { parallel: true, concurrencyLimit: 10 }
45
+ );
46
+
47
+ return [
48
+ {
49
+ componentDids: greenComponentIds,
50
+ changeToGreen: false,
51
+ },
52
+ {
53
+ componentDids: blueComponentIds,
54
+ changeToGreen: true,
55
+ },
56
+ ];
57
+ };
58
+
59
+ module.exports = { blueGreenGetComponentIds };