@abtnode/core 1.17.6-beta-20251217-011553-818fcc8e → 1.17.6-beta-20251218-120326-5b44dadf

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/api/node.js CHANGED
@@ -69,7 +69,7 @@ class NodeAPI {
69
69
 
70
70
  async getInfo() {
71
71
  const info = await this.state.read();
72
- const env = await this.state.getEnvironments();
72
+ const env = await this.state.getEnvironments(info);
73
73
  info.environments = Object.keys(env).map((x) => ({ key: x, value: env[x] }));
74
74
  info.uptime = process.uptime() * 1000;
75
75
  return info;
@@ -1184,7 +1184,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1184
1184
 
1185
1185
  const nodeInfo = await states.node.read();
1186
1186
 
1187
- const nodeEnvironments = await states.node.getEnvironments();
1187
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
1188
1188
 
1189
1189
  if (checkDockerRunHistory(nodeInfo)) {
1190
1190
  const needChownDirs = [];
@@ -1289,8 +1289,8 @@ class DiskBlockletManager extends BaseBlockletManager {
1289
1289
  }
1290
1290
 
1291
1291
  try {
1292
- const nodeEnvironments = await states.node.getEnvironments();
1293
1292
  const nodeInfo = await states.node.read();
1293
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
1294
1294
  await stopBlockletProcess(blocklet, {
1295
1295
  preStop: async (b, { ancestors }) => {
1296
1296
  const nextEnv = getRuntimeEnvironments(b, nodeEnvironments, ancestors);
@@ -1534,8 +1534,8 @@ class DiskBlockletManager extends BaseBlockletManager {
1534
1534
  throw new Error('Blocklet is protected from accidental deletion');
1535
1535
  }
1536
1536
 
1537
- const nodeEnvironments = await states.node.getEnvironments();
1538
1537
  const nodeInfo = await states.node.read();
1538
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
1539
1539
  await deleteBlockletProcess(blocklet, {
1540
1540
  preDelete: async (b, { ancestors }) => {
1541
1541
  const needRunDocker = await checkNeedRunDocker(
@@ -2055,9 +2055,9 @@ class DiskBlockletManager extends BaseBlockletManager {
2055
2055
 
2056
2056
  // run hook
2057
2057
  if (!skipHook) {
2058
- const nodeEnvironments = await states.node.getEnvironments();
2059
2058
  // FIXME: we should also call preConfig for child blocklets
2060
2059
  const nodeInfo = await states.node.read();
2060
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
2061
2061
  const nextEnv = { ...getRuntimeEnvironments(blocklet, nodeEnvironments, ancestors), ...configObj };
2062
2062
  const needRunDocker = await checkNeedRunDocker(blocklet.meta, nextEnv, nodeInfo, isExternalBlocklet(blocklet));
2063
2063
  const hookArgs = getHookArgs(blocklet);
@@ -2832,7 +2832,7 @@ class DiskBlockletManager extends BaseBlockletManager {
2832
2832
  {
2833
2833
  name: 'send-serverless-heartbeat',
2834
2834
  time: process.env.ABT_NODE_SERVERLESS_HEARTBEAT_INTERVAL || '*/5 * * * *', // default every 5 minutes
2835
- options: { runOnInit: false },
2835
+ options: { runOnInit: true },
2836
2836
  fn: () => launcher.sendServerlessHeartbeat(),
2837
2837
  },
2838
2838
  ];
@@ -4655,7 +4655,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4655
4655
  const nodeInfo = await states.node.read();
4656
4656
  // 等待磁盘映射完成
4657
4657
 
4658
- const nodeEnvironments = await states.node.getEnvironments();
4658
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
4659
4659
 
4660
4660
  const runMigration = async (b) => {
4661
4661
  if (!componentDids.includes(b.meta.did)) {
@@ -4762,8 +4762,8 @@ class DiskBlockletManager extends BaseBlockletManager {
4762
4762
  await this._updateBlockletEnvironment(did);
4763
4763
  blocklet = await this.getBlocklet(did);
4764
4764
 
4765
- const nodeEnvironments = await states.node.getEnvironments();
4766
4765
  const nodeInfo = await states.node.read();
4766
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
4767
4767
  const env = getRuntimeEnvironments(blocklet, nodeEnvironments, {});
4768
4768
  const needRunDocker = await checkNeedRunDocker(blocklet.meta, env, nodeInfo, isExternalBlocklet(blocklet));
4769
4769
 
@@ -5117,9 +5117,8 @@ class DiskBlockletManager extends BaseBlockletManager {
5117
5117
  }
5118
5118
 
5119
5119
  async _runUserHook(name, blocklet, context) {
5120
- const nodeEnvironments = await states.node.getEnvironments();
5121
-
5122
5120
  const nodeInfo = await states.node.read();
5121
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
5123
5122
  const hookFn = async (b, { ancestors }) => {
5124
5123
  const env = getRuntimeEnvironments(b, nodeEnvironments, ancestors);
5125
5124
  const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(blocklet));
@@ -5433,8 +5432,10 @@ class DiskBlockletManager extends BaseBlockletManager {
5433
5432
  this.emit(BlockletEvents.appDidChanged, blocklet);
5434
5433
 
5435
5434
  await updateBlockletDocument({
5436
- blocklet,
5435
+ did: blocklet.appPid,
5437
5436
  nodeInfo,
5437
+ states,
5438
+ teamManager: this.teamManager,
5438
5439
  })
5439
5440
  .then(() => {
5440
5441
  logger.info('updated blocklet dns document', { appPid: blocklet.appPid, appDid: blocklet.appDid });
@@ -295,7 +295,7 @@ const blueGreenStartBlocklet = async (
295
295
  tasks.push(async () => {
296
296
  try {
297
297
  const nodeInfo = await states.node.read();
298
- const nodeEnvironments = await states.node.getEnvironments();
298
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
299
299
 
300
300
  // 钩子函数设置
301
301
  const getHookFn =
@@ -39044,7 +39044,7 @@ module.exports = require("zlib");
39044
39044
  /***/ ((module) => {
39045
39045
 
39046
39046
  "use strict";
39047
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.17.5","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.17.5","@abtnode/auth":"1.17.5","@abtnode/certificate-manager":"1.17.5","@abtnode/constant":"1.17.5","@abtnode/cron":"1.17.5","@abtnode/db-cache":"1.17.5","@abtnode/docker-utils":"1.17.5","@abtnode/logger":"1.17.5","@abtnode/models":"1.17.5","@abtnode/queue":"1.17.5","@abtnode/rbac":"1.17.5","@abtnode/router-provider":"1.17.5","@abtnode/static-server":"1.17.5","@abtnode/timemachine":"1.17.5","@abtnode/util":"1.17.5","@aigne/aigne-hub":"^0.10.15","@arcblock/did":"^1.27.15","@arcblock/did-connect-js":"^1.27.15","@arcblock/did-ext":"^1.27.15","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"^1.27.15","@arcblock/event-hub":"^1.27.15","@arcblock/jwt":"^1.27.15","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"^1.27.15","@arcblock/vc":"^1.27.15","@blocklet/constant":"1.17.5","@blocklet/did-space-js":"^1.2.10","@blocklet/env":"1.17.5","@blocklet/error":"^0.3.5","@blocklet/meta":"1.17.5","@blocklet/resolver":"1.17.5","@blocklet/sdk":"1.17.5","@blocklet/server-js":"1.17.5","@blocklet/store":"1.17.5","@blocklet/theme":"^3.2.14","@fidm/x509":"^1.2.1","@ocap/mcrypto":"^1.27.15","@ocap/util":"^1.27.15","@ocap/wallet":"^1.27.15","@slack/webhook":"^7.0.6","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"axios-mock-adapter":"^2.1.0","expand-tilde":"^2.0.2","express":"^4.18.2","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
39047
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.17.5","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.17.5","@abtnode/auth":"1.17.5","@abtnode/certificate-manager":"1.17.5","@abtnode/constant":"1.17.5","@abtnode/cron":"1.17.5","@abtnode/db-cache":"1.17.5","@abtnode/docker-utils":"1.17.5","@abtnode/logger":"1.17.5","@abtnode/models":"1.17.5","@abtnode/queue":"1.17.5","@abtnode/rbac":"1.17.5","@abtnode/router-provider":"1.17.5","@abtnode/static-server":"1.17.5","@abtnode/timemachine":"1.17.5","@abtnode/util":"1.17.5","@aigne/aigne-hub":"^0.10.15","@arcblock/did":"^1.27.15","@arcblock/did-connect-js":"^1.27.15","@arcblock/did-ext":"^1.27.15","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"^1.27.15","@arcblock/event-hub":"^1.27.15","@arcblock/jwt":"^1.27.15","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"^1.27.15","@arcblock/vc":"^1.27.15","@blocklet/constant":"1.17.5","@blocklet/did-space-js":"^1.2.10","@blocklet/env":"1.17.5","@blocklet/error":"^0.3.5","@blocklet/meta":"1.17.5","@blocklet/resolver":"1.17.5","@blocklet/sdk":"1.17.5","@blocklet/server-js":"1.17.5","@blocklet/store":"1.17.5","@blocklet/theme":"^3.2.18","@fidm/x509":"^1.2.1","@ocap/mcrypto":"^1.27.15","@ocap/util":"^1.27.15","@ocap/wallet":"^1.27.15","@slack/webhook":"^7.0.6","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"axios-mock-adapter":"^2.1.0","expand-tilde":"^2.0.2","express":"^4.18.2","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
39048
39048
 
39049
39049
  /***/ }),
39050
39050
 
@@ -37,7 +37,12 @@ const { getBackupEndpoint, getBackupFilesUrlFromEndpoint, getDIDSpacesUrlFromEnd
37
37
  const { autoBackupHandlerFactory, autoBackupHandler } = require('./auto-backup-handler');
38
38
 
39
39
  const eventBusHandler = require('../blocklet/webhook/event-bus');
40
- const { isDevelopmentMode, deleteBlockletCache } = require('../util/blocklet');
40
+ const {
41
+ isDevelopmentMode,
42
+ deleteBlockletCache,
43
+ updateDidDocument,
44
+ updateDidDocumentStateOnly,
45
+ } = require('../util/blocklet');
41
46
  const {
42
47
  backupBlockletSites,
43
48
  cleanBlockletSitesBackup,
@@ -229,6 +234,14 @@ module.exports = ({
229
234
 
230
235
  await teamAPI.refreshBlockletInterfacePermissions(blocklet.meta);
231
236
  logger.info('refreshed blocklet interface permissions after installed', { did: blocklet.meta.did });
237
+
238
+ try {
239
+ const nodeInfo = await node.getNodeInfo();
240
+ await updateDidDocument({ did: blocklet.appPid, nodeInfo, teamManager, states });
241
+ logger.info('updated blocklet DID document after install', { did: blocklet.meta.did });
242
+ } catch (error) {
243
+ logger.error('update blocklet DID document failed after install', { did: blocklet.meta.did, error });
244
+ }
232
245
  } catch (error) {
233
246
  logger.error('create.url.mapping.error', { event: name, error });
234
247
  teamManager.createNotification({
@@ -261,6 +274,19 @@ module.exports = ({
261
274
  logger.error('remove blocklet routing rules error', { event: name, error });
262
275
  }
263
276
 
277
+ try {
278
+ const nodeInfo = await node.getNodeInfo();
279
+ await updateDidDocumentStateOnly({
280
+ did: blocklet.appPid,
281
+ blocklet,
282
+ state: 'deleted',
283
+ nodeInfo,
284
+ });
285
+ logger.info('updated blocklet DID document state to deleted', { did: blocklet.meta.did });
286
+ } catch (error) {
287
+ logger.error('update blocklet DID document failed after remove', { did: blocklet.meta.did, error });
288
+ }
289
+
264
290
  try {
265
291
  await blockletManager.prune();
266
292
  } catch (error) {
@@ -503,6 +529,23 @@ module.exports = ({
503
529
  } catch (error) {
504
530
  logger.error('monit runtime info failed', { eventName, error });
505
531
  }
532
+
533
+ if ([BlockletEvents.started, BlockletEvents.stopped].includes(eventName)) {
534
+ try {
535
+ const nodeInfo = await node.getNodeInfo();
536
+ await updateDidDocument({ did: blocklet.appPid, nodeInfo, teamManager, states });
537
+ logger.info('updated blocklet DID document after state change', {
538
+ did: blocklet.meta.did,
539
+ event: eventName,
540
+ });
541
+ } catch (error) {
542
+ logger.error('update blocklet DID document failed after state change', {
543
+ did: blocklet.meta.did,
544
+ event: eventName,
545
+ error,
546
+ });
547
+ }
548
+ }
506
549
  }
507
550
 
508
551
  if ([BlockletEvents.statusChange].includes(eventName) && isDevelopmentMode(blocklet)) {
@@ -704,8 +747,17 @@ module.exports = ({
704
747
  });
705
748
  });
706
749
 
707
- routerManager.on(EVENTS.UPDATE_DOMAIN_ALIAS, (did) => {
750
+ routerManager.on(EVENTS.UPDATE_DOMAIN_ALIAS, async (did) => {
708
751
  if (did) {
752
+ const nodeInfo = await node.getNodeInfo();
753
+ updateDidDocument({ did, nodeInfo, teamManager, states })
754
+ .then(() => {
755
+ logger.info('Update did document successfully on update domain alias', { did });
756
+ })
757
+ .catch((err) => {
758
+ logger.error('Update did document failed on update domain alias', { did, error: err });
759
+ });
760
+
709
761
  blockletManager
710
762
  .detail({ did })
711
763
  .then((blocklet) => {
@@ -29,6 +29,7 @@ const axios = require('@abtnode/util/lib/axios');
29
29
  const { getIpDnsDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-blocklet');
30
30
  const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
31
31
  const { processLogByDate } = require('@abtnode/analytics');
32
+ const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
32
33
  const pRetry = require('p-retry');
33
34
  const { buildLauncherUrl, buildRequestHeaders } = require('@abtnode/auth/lib/launcher');
34
35
  const {
@@ -105,6 +106,13 @@ const { get: getIp } = require('../util/ip');
105
106
 
106
107
  const isServiceFeDevelopment = process.env.ABT_NODE_SERVICE_FE_PORT;
107
108
 
109
+ // 使用 DBCache 实现分布式锁,防止同一个 blocklet 的路由操作并发执行
110
+ const ensureBlockletRoutingLock = new DBCache(() => ({
111
+ prefix: 'ensure-blocklet-routing-lock',
112
+ ttl: 1000 * 60, // 60 seconds timeout
113
+ ...getAbtNodeRedisAndSQLiteUrl(),
114
+ }));
115
+
108
116
  const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
109
117
 
110
118
  const pingWellknownRule = {
@@ -1311,19 +1319,22 @@ module.exports = function getRouterHelpers({
1311
1319
 
1312
1320
  const existSite = await states.site.findOne({ domain: domainGroup });
1313
1321
  updateBlockletDocument({
1314
- blocklet,
1322
+ did: blocklet.appPid,
1315
1323
  nodeInfo,
1324
+ teamManager,
1325
+ states,
1316
1326
  })
1317
1327
  .then(() => {
1318
- logger.info(`updated blocklet ${blocklet.appDid} dns`);
1328
+ logger.info('updated blocklet dns document', {
1329
+ did: blocklet.appPid,
1330
+ });
1319
1331
  })
1320
1332
  .catch((err) => {
1321
- logger.error(`update blocklet ${blocklet.appDid} dns failed`, { error: err });
1333
+ logger.error('update blocklet dns document failed', { did: blocklet.meta.did, error: err });
1322
1334
  });
1323
1335
 
1324
1336
  if (!existSite) {
1325
1337
  const domainAliases = getBlockletDidDomainList(blocklet, nodeInfo);
1326
-
1327
1338
  // let didDomain in front of ipEchoDnsDomain
1328
1339
  domainAliases.push({ value: getIpDnsDomainForBlocklet(blocklet), isProtected: true });
1329
1340
 
@@ -1496,13 +1507,21 @@ module.exports = function getRouterHelpers({
1496
1507
  * @returns {Promise<boolean>} if routing changed
1497
1508
  */
1498
1509
  const ensureBlockletRouting = async (blocklet, context = {}) => {
1499
- const nodeInfo = await nodeState.read();
1500
- const hasWebInterface = (blocklet.meta.interfaces || []).some((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
1501
- if (!hasWebInterface) {
1502
- return false;
1503
- }
1510
+ const lockName = `ensure-blocklet-routing-${blocklet.meta.did}`;
1511
+
1512
+ await ensureBlockletRoutingLock.acquire(lockName);
1504
1513
 
1505
- return _ensureBlockletSites(blocklet, nodeInfo, context);
1514
+ try {
1515
+ const nodeInfo = await nodeState.read();
1516
+ const hasWebInterface = (blocklet.meta.interfaces || []).some((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
1517
+ if (!hasWebInterface) {
1518
+ return false;
1519
+ }
1520
+
1521
+ return await _ensureBlockletSites(blocklet, nodeInfo, context);
1522
+ } finally {
1523
+ await ensureBlockletRoutingLock.releaseLock(lockName);
1524
+ }
1506
1525
  };
1507
1526
 
1508
1527
  /**
@@ -56,6 +56,32 @@ class BlockletChildState extends BaseState {
56
56
  return (children || []).map(ensureDeployedFrom);
57
57
  }
58
58
 
59
+ /**
60
+ * Get children by multiple parent blocklet IDs (batch query)
61
+ * @param {string[]} parentBlockletIds - Array of parent blocklet IDs
62
+ * @returns {Promise<Map<string, Array>>} - Map of parentBlockletId -> children array
63
+ */
64
+ async getChildrenByParentIds(parentBlockletIds) {
65
+ if (!Array.isArray(parentBlockletIds) || parentBlockletIds.length === 0) {
66
+ return new Map();
67
+ }
68
+
69
+ const children = await this.find({ parentBlockletId: { $in: parentBlockletIds } }, {}, { createdAt: 1 });
70
+
71
+ // Group children by parentBlockletId
72
+ const childrenMap = new Map();
73
+ parentBlockletIds.forEach((id) => childrenMap.set(id, []));
74
+
75
+ (children || []).forEach((child) => {
76
+ const processedChild = ensureDeployedFrom(child);
77
+ if (childrenMap.has(child.parentBlockletId)) {
78
+ childrenMap.get(child.parentBlockletId).push(processedChild);
79
+ }
80
+ });
81
+
82
+ return childrenMap;
83
+ }
84
+
59
85
  /**
60
86
  * Delete children by parent blocklet ID
61
87
  * @param {string} parentBlockletId - The parent blocklet ID
@@ -265,13 +265,13 @@ class BlockletState extends BaseState {
265
265
  * @param {string} blockletId - The blocklet ID
266
266
  * @returns {Promise<Array>} - Array of children
267
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
268
+ /**
269
+ * Process raw children records into structured format
270
+ * @param {Array} children - Raw children records from database
271
+ * @param {string} parentBlockletId - The parent blocklet ID (for logging)
272
+ * @returns {Array} - Processed children array
273
+ */
274
+ _processLoadedChildren(children, parentBlockletId) {
275
275
  if (!Array.isArray(children)) {
276
276
  return [];
277
277
  }
@@ -280,17 +280,17 @@ class BlockletState extends BaseState {
280
280
  // Ensure meta is an object and has required fields
281
281
  const meta = child.meta || {};
282
282
  if (!meta.did) {
283
- logger.warn('loadChildren: child missing meta.did', { childId: child.id, parentBlockletId: blockletId });
283
+ logger.warn('loadChildren: child missing meta.did', { childId: child.id, parentBlockletId });
284
284
  }
285
285
  if (!meta.name && !meta.bundleName) {
286
286
  logger.warn('loadChildren: child missing meta.name and meta.bundleName', {
287
287
  childId: child.id,
288
288
  childDid: meta.did,
289
- parentBlockletId: blockletId,
289
+ parentBlockletId,
290
290
  });
291
291
  }
292
292
 
293
- const childObj = {
293
+ return {
294
294
  id: child.id,
295
295
  mountPoint: child.mountPoint,
296
296
  meta,
@@ -312,15 +312,16 @@ class BlockletState extends BaseState {
312
312
  greenStatus: child.greenStatus,
313
313
  greenPorts: child.greenPorts,
314
314
  };
315
+ });
316
+ }
315
317
 
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
- }
318
+ async loadChildren(blockletId) {
319
+ if (!this.BlockletChildState || !blockletId) {
320
+ return [];
321
+ }
321
322
 
322
- return childObj;
323
- });
323
+ const children = await this.BlockletChildState.getChildrenByParentId(blockletId);
324
+ return this._processLoadedChildren(children, blockletId);
324
325
  }
325
326
 
326
327
  /**
@@ -525,16 +526,23 @@ class BlockletState extends BaseState {
525
526
 
526
527
  async getBlocklets(query = {}, projection = {}, sort = { createdAt: -1 }) {
527
528
  const docs = await this.find(query, projection, sort);
528
- const result = [];
529
+ const validDocs = docs.filter(Boolean);
529
530
 
530
- for (const doc of docs.filter(Boolean)) {
531
- // Load children for each blocklet
532
- const children = await this.loadChildren(doc.id);
533
- doc.children = children;
534
- result.push(formatBlocklet(doc, 'onRead', this.config.dek));
531
+ if (validDocs.length === 0) {
532
+ return [];
535
533
  }
536
534
 
537
- return result;
535
+ // Batch load all children in a single query (instead of N+1 queries)
536
+ let childrenMap = new Map();
537
+ if (this.BlockletChildState) {
538
+ const blockletIds = validDocs.map((doc) => doc.id);
539
+ childrenMap = await this.BlockletChildState.getChildrenByParentIds(blockletIds);
540
+ }
541
+
542
+ return validDocs.map((doc) => {
543
+ doc.children = this._processLoadedChildren(childrenMap.get(doc.id) || [], doc.id);
544
+ return formatBlocklet(doc, 'onRead', this.config.dek);
545
+ });
538
546
  }
539
547
 
540
548
  async deleteBlocklet(did) {
@@ -903,12 +911,22 @@ class BlockletState extends BaseState {
903
911
  }
904
912
 
905
913
  async getServices() {
906
- const blocklets = await this.getBlocklets({}, { meta: 1, ports: 1 });
914
+ const blocklets = await this.getBlocklets({}, { id: 1, meta: 1, ports: 1 });
907
915
  const services = [];
908
916
 
909
917
  blocklets.forEach((blocklet) => {
910
918
  const list = getBlockletServices(blocklet);
911
919
  list.forEach((x) => {
920
+ // 如果本地 53 端口被系统占用,调试可以配置: export ABT_NODE_REDIRECTION_SERVICE_PORTS="53:10053,553:101553" 等多个 Service 端口重定向
921
+ if (process.env.ABT_NODE_REDIRECTION_SERVICE_PORTS) {
922
+ const redirectionPorts = process.env.ABT_NODE_REDIRECTION_SERVICE_PORTS.split(',');
923
+ redirectionPorts.forEach((portString) => {
924
+ const [portA, portB] = portString.split(':');
925
+ if (x.port === +portA) {
926
+ x.port = +portB;
927
+ }
928
+ });
929
+ }
912
930
  if (!x.port) {
913
931
  logger.error('Missing service port', { appId: blocklet.meta.did, x });
914
932
  return;
@@ -941,7 +959,7 @@ class BlockletState extends BaseState {
941
959
  * @return {Object} { <did> : { interfaceName } }
942
960
  */
943
961
  async groupAllInterfaces() {
944
- const blocklets = await this.getBlocklets({}, { meta: 1 });
962
+ const blocklets = await this.getBlocklets({}, { id: 1, meta: 1 });
945
963
  const result = {};
946
964
  const fillResult = (component, { id }) => {
947
965
  const { interfaces } = component.meta;
@@ -1201,7 +1219,7 @@ class BlockletState extends BaseState {
1201
1219
  }
1202
1220
 
1203
1221
  async _getOccupiedPorts() {
1204
- const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, meta: 1 });
1222
+ const blocklets = await this.getBlocklets({}, { id: 1, port: 1, ports: 1, meta: 1 });
1205
1223
 
1206
1224
  const occupiedExternalPorts = new Map();
1207
1225
  const occupiedInternalPorts = new Map();
@@ -316,8 +316,9 @@ class NodeState extends BaseState {
316
316
  return this.updateNodeInfo({ previousMode: '', mode });
317
317
  }
318
318
 
319
- getEnvironments() {
320
- return this.read().then((info) => ({
319
+ async getEnvironments(nodeInfo) {
320
+ const info = nodeInfo || (await this.read());
321
+ return {
321
322
  ABT_NODE: info.version,
322
323
  ABT_NODE_VERSION: info.version,
323
324
  ABT_NODE_DID: info.did,
@@ -326,7 +327,7 @@ class NodeState extends BaseState {
326
327
  ABT_NODE_PORT: info.port,
327
328
  ABT_NODE_SERVICE_PORT: process.env.ABT_NODE_SERVICE_PORT,
328
329
  ABT_NODE_MODE: info.mode,
329
- }));
330
+ };
330
331
  }
331
332
 
332
333
  async updateGateway(updates) {
@@ -31,6 +31,7 @@ const { isValid: isValidDid, isEthereumDid } = require('@arcblock/did');
31
31
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
32
32
  const pm2 = require('@abtnode/util/lib/pm2/async-pm2');
33
33
  const sleep = require('@abtnode/util/lib/sleep');
34
+ const { isCustomDomain } = require('@abtnode/util/lib/url-evaluation');
34
35
  const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
35
36
  const { formatEnv, getSecurityNodeOptions, decrypt } = require('@abtnode/util/lib/security');
36
37
  const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
@@ -45,6 +46,7 @@ const {
45
46
  APP_STRUCT_VERSION,
46
47
  BLOCKLET_CACHE_TTL,
47
48
  AIGNE_CONFIG_ENCRYPT_SALT,
49
+ SLOT_FOR_IP_DNS_SITE,
48
50
  } = require('@abtnode/constant');
49
51
  const { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK } = require('@blocklet/theme');
50
52
  const {
@@ -127,6 +129,7 @@ const {
127
129
  templateReplace,
128
130
  getServerDidDomain,
129
131
  APP_CONFIG_IMAGE_KEYS,
132
+ replaceDomainSlot,
130
133
  } = require('./index');
131
134
  const { installExternalDependencies } = require('./install-external-dependencies');
132
135
  const parseDockerOptionsFromPm2 = require('./docker/parse-docker-options-from-pm2');
@@ -135,6 +138,7 @@ const getDockerRuntimeInfo = require('./docker/get-docker-runtime-info');
135
138
  const parseDockerName = require('./docker/parse-docker-name');
136
139
  const { createDockerNetwork } = require('./docker/docker-network');
137
140
  const { ensureBun } = require('./ensure-bun');
141
+ const { getFromCache: getAccessibleExternalNodeIp } = require('./get-accessible-external-node-ip');
138
142
 
139
143
  /**
140
144
  * Get actual listening port from Docker container or process
@@ -2402,9 +2406,16 @@ const validateStore = (nodeInfo, storeUrl) => {
2402
2406
  return;
2403
2407
  }
2404
2408
 
2409
+ const storeUrlObj = new URL(storeUrl);
2410
+ const registerUrlObj = new URL(nodeInfo.registerUrl);
2411
+
2412
+ // 信任 Launcher 打包的应用
2413
+ if (registerUrlObj.host === storeUrlObj.host) {
2414
+ return;
2415
+ }
2416
+
2405
2417
  const inStoreList = nodeInfo.blockletRegistryList.find((item) => {
2406
2418
  const itemURLObj = new URL(item.url);
2407
- const storeUrlObj = new URL(storeUrl);
2408
2419
 
2409
2420
  return itemURLObj.host === storeUrlObj.host;
2410
2421
  });
@@ -2736,8 +2747,10 @@ const getSlpDid = (serverDid, appPid) => {
2736
2747
  };
2737
2748
 
2738
2749
  // eslint-disable-next-line require-await
2739
- const updateDidDocument = async ({ blocklet, nodeInfo }) => {
2750
+ const publishDidDocument = async ({ blocklet, ownerInfo, nodeInfo }) => {
2740
2751
  const alsoKnownAs = getBlockletKnownAs(blocklet);
2752
+ logger.debug('updateDidDocument blocklet info', { blocklet });
2753
+
2741
2754
  const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
2742
2755
  const { mode, did: serverDid } = nodeInfo;
2743
2756
 
@@ -2761,6 +2774,58 @@ const updateDidDocument = async ({ blocklet, nodeInfo }) => {
2761
2774
  slpDomain: nodeInfo.slpDomain,
2762
2775
  });
2763
2776
 
2777
+ const name = blocklet.meta?.title || blocklet.meta?.name;
2778
+ const state = fromBlockletStatus(blocklet.status || BlockletStatus.stopped);
2779
+
2780
+ let launcher = null;
2781
+ if (!isEmpty(blocklet.controller)) {
2782
+ launcher = {
2783
+ did: toDid(blocklet.controller.did || nodeInfo.registerInfo.appPid), // 目前 controller 没有 launcher 的元信息, 默认在 nodeInfo 中存储
2784
+ name: blocklet.controller.launcherName || nodeInfo.registerInfo.appName || '',
2785
+ url: blocklet.controller.launcherUrl || nodeInfo.registerInfo.appUrl || '',
2786
+ userDid: toDid(blocklet.controller.nftOwner),
2787
+ };
2788
+ }
2789
+
2790
+ const isPrimaryDomain = (d) => {
2791
+ const appUrl = blocklet.environments.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_URL)?.value;
2792
+ try {
2793
+ const url = new URL(appUrl);
2794
+ return url.hostname === d;
2795
+ } catch (error) {
2796
+ logger.error('failed to get primary domain', { error, domain: d, appUrl });
2797
+ return false;
2798
+ }
2799
+ };
2800
+
2801
+ const domains = await Promise.all(
2802
+ blocklet.site.domainAliases.map(async (item) => {
2803
+ let type = isCustomDomain(item.value) ? 'custom' : 'internal';
2804
+ // 如果域名是 appUrl,则设置为 primary
2805
+ if (isPrimaryDomain(item.value)) {
2806
+ type = 'primary';
2807
+ }
2808
+
2809
+ if (item.value.includes(SLOT_FOR_IP_DNS_SITE)) {
2810
+ const nodeIp = await getAccessibleExternalNodeIp();
2811
+ item.value = replaceDomainSlot({ domain: item.value, nodeIp });
2812
+ }
2813
+
2814
+ return {
2815
+ type,
2816
+ host: item.value,
2817
+ url: `https://${item.value}`,
2818
+ source: 'dnsRecords', // 固定为 dnsRecords
2819
+ };
2820
+ })
2821
+ );
2822
+
2823
+ const owner = {
2824
+ did: toDid(ownerInfo.did),
2825
+ name: ownerInfo.fullName,
2826
+ avatar: ownerInfo.avatar,
2827
+ };
2828
+
2764
2829
  return didDocument.updateBlockletDocument({
2765
2830
  blocklet,
2766
2831
  wallet,
@@ -2772,6 +2837,56 @@ const updateDidDocument = async ({ blocklet, nodeInfo }) => {
2772
2837
  slpDomain: nodeInfo.slpDomain,
2773
2838
  serverDid,
2774
2839
  blockletServerVersion: nodeInfo.version,
2840
+ name,
2841
+ state,
2842
+ owner,
2843
+ launcher,
2844
+ domains,
2845
+ });
2846
+ };
2847
+
2848
+ const updateDidDocument = async ({ did, status, nodeInfo, teamManager, states }) => {
2849
+ const blocklet = await states.blocklet.getBlocklet(did);
2850
+ const blockletExtra = await states.blockletExtras.findOne({ did });
2851
+
2852
+ blocklet.site = await states.site.findOneByBlocklet(did);
2853
+ blocklet.settings = await states.blockletExtras.getSettings(did);
2854
+ blocklet.controller = blockletExtra.controller;
2855
+
2856
+ logger.debug('update did document', { blocklet });
2857
+
2858
+ if (isEmpty(blocklet.status)) {
2859
+ blocklet.status = status;
2860
+ }
2861
+
2862
+ const ownerDid = blocklet.settings.owner.did;
2863
+ logger.info('get owner info', { ownerDid, teamDid: blocklet.appPid });
2864
+ const userState = await teamManager.getUserState(blocklet.appPid);
2865
+ const ownerInfo = await userState.getUser(ownerDid);
2866
+
2867
+ logger.info('got user info', {
2868
+ owner: ownerInfo,
2869
+ blockletDid: blocklet.meta.did,
2870
+ ownerDid,
2871
+ teamDid: blocklet.appPid,
2872
+ });
2873
+
2874
+ return publishDidDocument({ blocklet, ownerInfo, nodeInfo });
2875
+ };
2876
+
2877
+ // Update DID document state only (e.g., to 'deleted') without fetching from database
2878
+ // Used when blocklet is being removed and database data may not be available
2879
+ const updateDidDocumentStateOnly = ({ did, blocklet, state, nodeInfo }) => {
2880
+ logger.debug('update did document state only', { did, state });
2881
+
2882
+ const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
2883
+
2884
+ return didDocument.updateBlockletStateOnly({
2885
+ did,
2886
+ state,
2887
+ didRegistryUrl: nodeInfo.didRegistry,
2888
+ wallet,
2889
+ blockletServerVersion: nodeInfo.version,
2775
2890
  });
2776
2891
  };
2777
2892
 
@@ -2983,7 +3098,9 @@ module.exports = {
2983
3098
  getBlockletURLForLauncher,
2984
3099
  ensureAppPortsNotOccupied,
2985
3100
  getComponentNamesWithVersion,
3101
+ publishDidDocument,
2986
3102
  updateDidDocument,
3103
+ updateDidDocumentStateOnly,
2987
3104
  getSlpDid,
2988
3105
  shouldEnableSlpDomain,
2989
3106
  getAppConfigsFromComponent,
@@ -33,7 +33,7 @@ async function installExternalDependencies({ appDir, forceInstall = false, nodeI
33
33
  }
34
34
 
35
35
  if (!(await isSameOs(appDir, isUseDocker))) {
36
- fs.removeSync(path.join(appDir, 'node_modules'), { recursive: true });
36
+ fs.removeSync(path.join(appDir, 'node_modules'), { recursive: true, force: true });
37
37
  }
38
38
 
39
39
  const isNeedInstall = forceInstall || externals.some((dependency) => !isDependencyInstalled(appDir, dependency));
@@ -1,7 +1,10 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
1
3
  const logger = require('@abtnode/logger')('@abtnode/core:util:reset');
2
4
 
3
5
  const states = require('../states');
4
6
  const { isE2E } = require('./env');
7
+ const { getDataDirs } = require('./index');
5
8
 
6
9
  const noop = () => true;
7
10
 
@@ -27,6 +30,36 @@ const resetOwner = async ({ teamManager }) => {
27
30
  return true;
28
31
  };
29
32
 
33
+ /* istanbul ignore next */
34
+ const resetDirs = () => {
35
+ try {
36
+ const dataDir = process.env.ABT_NODE_DATA_DIR;
37
+ if (!dataDir) {
38
+ logger.warn('ABT_NODE_DATA_DIR not set, skip reset bundles');
39
+ return false;
40
+ }
41
+ const dataDirs = getDataDirs(dataDir);
42
+
43
+ const dirs = [dataDirs.blocklets, dataDirs.cache];
44
+
45
+ for (const dir of dirs) {
46
+ if (fs.existsSync(dir)) {
47
+ // Remove all contents in blocklets directory
48
+ const entries = fs.readdirSync(dir);
49
+ for (const entry of entries) {
50
+ const entryPath = path.join(dir, entry);
51
+ fs.removeSync(entryPath, { recursive: true, force: true });
52
+ }
53
+ logger.info('reset bundles', { dir, count: entries.length });
54
+ }
55
+ }
56
+ return true;
57
+ } catch (error) {
58
+ logger.error('failed to reset bundles', { error });
59
+ return false;
60
+ }
61
+ };
62
+
30
63
  /* istanbul ignore next */
31
64
  const resetBlocklets = async ({ context, blockletManager }) => {
32
65
  const blocklets = await blockletManager.list({ includeRuntimeInfo: false }, context);
@@ -36,6 +69,8 @@ const resetBlocklets = async ({ context, blockletManager }) => {
36
69
  await blockletManager.delete({ did: blocklet.meta.did, keepData: false }, context);
37
70
  }
38
71
  logger.info('reset blocklets', blocklets.length);
72
+
73
+ resetDirs();
39
74
  return true;
40
75
  };
41
76
 
@@ -136,6 +171,7 @@ module.exports = async ({
136
171
  webhooks: false,
137
172
  accessKeys: false,
138
173
  users: false,
174
+ bundles: false,
139
175
  },
140
176
  params || {}
141
177
  );
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.6-beta-20251217-011553-818fcc8e",
6
+ "version": "1.17.6-beta-20251218-120326-5b44dadf",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -17,21 +17,21 @@
17
17
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
18
18
  "license": "Apache-2.0",
19
19
  "dependencies": {
20
- "@abtnode/analytics": "1.17.6-beta-20251217-011553-818fcc8e",
21
- "@abtnode/auth": "1.17.6-beta-20251217-011553-818fcc8e",
22
- "@abtnode/certificate-manager": "1.17.6-beta-20251217-011553-818fcc8e",
23
- "@abtnode/constant": "1.17.6-beta-20251217-011553-818fcc8e",
24
- "@abtnode/cron": "1.17.6-beta-20251217-011553-818fcc8e",
25
- "@abtnode/db-cache": "1.17.6-beta-20251217-011553-818fcc8e",
26
- "@abtnode/docker-utils": "1.17.6-beta-20251217-011553-818fcc8e",
27
- "@abtnode/logger": "1.17.6-beta-20251217-011553-818fcc8e",
28
- "@abtnode/models": "1.17.6-beta-20251217-011553-818fcc8e",
29
- "@abtnode/queue": "1.17.6-beta-20251217-011553-818fcc8e",
30
- "@abtnode/rbac": "1.17.6-beta-20251217-011553-818fcc8e",
31
- "@abtnode/router-provider": "1.17.6-beta-20251217-011553-818fcc8e",
32
- "@abtnode/static-server": "1.17.6-beta-20251217-011553-818fcc8e",
33
- "@abtnode/timemachine": "1.17.6-beta-20251217-011553-818fcc8e",
34
- "@abtnode/util": "1.17.6-beta-20251217-011553-818fcc8e",
20
+ "@abtnode/analytics": "1.17.6-beta-20251218-120326-5b44dadf",
21
+ "@abtnode/auth": "1.17.6-beta-20251218-120326-5b44dadf",
22
+ "@abtnode/certificate-manager": "1.17.6-beta-20251218-120326-5b44dadf",
23
+ "@abtnode/constant": "1.17.6-beta-20251218-120326-5b44dadf",
24
+ "@abtnode/cron": "1.17.6-beta-20251218-120326-5b44dadf",
25
+ "@abtnode/db-cache": "1.17.6-beta-20251218-120326-5b44dadf",
26
+ "@abtnode/docker-utils": "1.17.6-beta-20251218-120326-5b44dadf",
27
+ "@abtnode/logger": "1.17.6-beta-20251218-120326-5b44dadf",
28
+ "@abtnode/models": "1.17.6-beta-20251218-120326-5b44dadf",
29
+ "@abtnode/queue": "1.17.6-beta-20251218-120326-5b44dadf",
30
+ "@abtnode/rbac": "1.17.6-beta-20251218-120326-5b44dadf",
31
+ "@abtnode/router-provider": "1.17.6-beta-20251218-120326-5b44dadf",
32
+ "@abtnode/static-server": "1.17.6-beta-20251218-120326-5b44dadf",
33
+ "@abtnode/timemachine": "1.17.6-beta-20251218-120326-5b44dadf",
34
+ "@abtnode/util": "1.17.6-beta-20251218-120326-5b44dadf",
35
35
  "@aigne/aigne-hub": "^0.10.15",
36
36
  "@arcblock/did": "^1.27.15",
37
37
  "@arcblock/did-connect-js": "^1.27.15",
@@ -43,16 +43,16 @@
43
43
  "@arcblock/pm2-events": "^0.0.5",
44
44
  "@arcblock/validator": "^1.27.15",
45
45
  "@arcblock/vc": "^1.27.15",
46
- "@blocklet/constant": "1.17.6-beta-20251217-011553-818fcc8e",
46
+ "@blocklet/constant": "1.17.6-beta-20251218-120326-5b44dadf",
47
47
  "@blocklet/did-space-js": "^1.2.10",
48
- "@blocklet/env": "1.17.6-beta-20251217-011553-818fcc8e",
48
+ "@blocklet/env": "1.17.6-beta-20251218-120326-5b44dadf",
49
49
  "@blocklet/error": "^0.3.5",
50
- "@blocklet/meta": "1.17.6-beta-20251217-011553-818fcc8e",
51
- "@blocklet/resolver": "1.17.6-beta-20251217-011553-818fcc8e",
52
- "@blocklet/sdk": "1.17.6-beta-20251217-011553-818fcc8e",
53
- "@blocklet/server-js": "1.17.6-beta-20251217-011553-818fcc8e",
54
- "@blocklet/store": "1.17.6-beta-20251217-011553-818fcc8e",
55
- "@blocklet/theme": "^3.2.14",
50
+ "@blocklet/meta": "1.17.6-beta-20251218-120326-5b44dadf",
51
+ "@blocklet/resolver": "1.17.6-beta-20251218-120326-5b44dadf",
52
+ "@blocklet/sdk": "1.17.6-beta-20251218-120326-5b44dadf",
53
+ "@blocklet/server-js": "1.17.6-beta-20251218-120326-5b44dadf",
54
+ "@blocklet/store": "1.17.6-beta-20251218-120326-5b44dadf",
55
+ "@blocklet/theme": "^3.2.18",
56
56
  "@fidm/x509": "^1.2.1",
57
57
  "@ocap/mcrypto": "^1.27.15",
58
58
  "@ocap/util": "^1.27.15",
@@ -116,5 +116,5 @@
116
116
  "express": "^4.18.2",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "e4291b92297d4b9abfdc5c1e5bbe262882fe34f2"
119
+ "gitHead": "a07b4f70aa6dddea49ed794423b47d36dc206e53"
120
120
  }