@abtnode/core 1.16.49-beta-20250827-025603-2bb1a7ee → 1.16.49-beta-20250828-094758-93e69d1f
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.
|
@@ -886,10 +886,65 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
886
886
|
throw new Error('No runnable component found');
|
|
887
887
|
}
|
|
888
888
|
|
|
889
|
+
const entryComponentIds = [];
|
|
890
|
+
const nonEntryComponentIds = [];
|
|
891
|
+
let nonEntryComponentRes;
|
|
892
|
+
const componentDidsSet = new Set(componentDids);
|
|
893
|
+
try {
|
|
894
|
+
await forEachBlocklet(
|
|
895
|
+
blocklet1,
|
|
896
|
+
/**
|
|
897
|
+
*
|
|
898
|
+
* @param {import('@blocklet/server-js').BlockletState} b
|
|
899
|
+
* @param {*} param1
|
|
900
|
+
* @returns
|
|
901
|
+
*/
|
|
902
|
+
(b) => {
|
|
903
|
+
if (!componentDidsSet.has(b.meta.did)) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (b.meta.group === BlockletGroup.gateway) {
|
|
908
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const engine = getBlockletEngine(b.meta);
|
|
913
|
+
if (engine.interpreter === 'blocklet') {
|
|
914
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (!hasStartEngine(b.meta)) {
|
|
919
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
entryComponentIds.push(b.meta.did);
|
|
923
|
+
},
|
|
924
|
+
{ parallel: true, concurrencyLimit: 4 }
|
|
925
|
+
);
|
|
926
|
+
if (nonEntryComponentIds.length) {
|
|
927
|
+
nonEntryComponentRes = await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
928
|
+
componentDids: nonEntryComponentIds,
|
|
929
|
+
operator,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
} catch (err) {
|
|
933
|
+
logger.error('Failed to categorize components into entry and non-entry types', { error: err.message });
|
|
934
|
+
throw err;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (!entryComponentIds.length) {
|
|
938
|
+
return nonEntryComponentRes;
|
|
939
|
+
}
|
|
940
|
+
|
|
889
941
|
try {
|
|
890
942
|
// check required config
|
|
891
943
|
for (const component of blocklet1.children) {
|
|
892
|
-
if (!
|
|
944
|
+
if (!entryComponentIds.includes(component.meta.did)) {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (!shouldSkipComponent(component.meta.did, entryComponentIds)) {
|
|
893
948
|
const missingProps = getComponentMissingConfigs(component, blocklet1);
|
|
894
949
|
if (missingProps.length) {
|
|
895
950
|
throw new Error(
|
|
@@ -902,12 +957,18 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
902
957
|
}
|
|
903
958
|
|
|
904
959
|
const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
|
|
905
|
-
componentDids,
|
|
960
|
+
componentDids: entryComponentIds,
|
|
906
961
|
operator,
|
|
907
962
|
});
|
|
963
|
+
|
|
908
964
|
blocklet1.status = BlockletStatus.starting;
|
|
909
965
|
this.emit(BlockletEvents.statusChange, doc1);
|
|
910
|
-
const blocklet = await ensureAppPortsNotOccupied({
|
|
966
|
+
const blocklet = await ensureAppPortsNotOccupied({
|
|
967
|
+
blocklet: blocklet1,
|
|
968
|
+
componentDids: entryComponentIds,
|
|
969
|
+
states,
|
|
970
|
+
manager: this,
|
|
971
|
+
});
|
|
911
972
|
|
|
912
973
|
const nodeInfo = await states.node.read();
|
|
913
974
|
|
|
@@ -966,7 +1027,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
966
1027
|
nodeEnvironments,
|
|
967
1028
|
nodeInfo: await states.node.read(),
|
|
968
1029
|
e2eMode,
|
|
969
|
-
componentDids,
|
|
1030
|
+
componentDids: entryComponentIds,
|
|
970
1031
|
configSynchronizer: this.configSynchronizer,
|
|
971
1032
|
});
|
|
972
1033
|
|
|
@@ -977,7 +1038,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
977
1038
|
context,
|
|
978
1039
|
minConsecutiveTime,
|
|
979
1040
|
timeout: startTimeout,
|
|
980
|
-
componentDids,
|
|
1041
|
+
componentDids: entryComponentIds,
|
|
981
1042
|
};
|
|
982
1043
|
|
|
983
1044
|
if (checkHealthImmediately) {
|
|
@@ -988,7 +1049,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
988
1049
|
entity: 'blocklet',
|
|
989
1050
|
action: 'check_if_started',
|
|
990
1051
|
...params,
|
|
991
|
-
id: `${did}/${(
|
|
1052
|
+
id: `${did}/${(entryComponentIds || []).join(',')}`,
|
|
992
1053
|
});
|
|
993
1054
|
}
|
|
994
1055
|
|
|
@@ -1002,7 +1063,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1002
1063
|
|
|
1003
1064
|
const error = Array.isArray(err) ? err[0] : err;
|
|
1004
1065
|
logger.error('Failed to start blocklet', { error, did, title: blocklet1.meta.title });
|
|
1005
|
-
const description = `${getComponentNamesWithVersion(blocklet1,
|
|
1066
|
+
const description = `${getComponentNamesWithVersion(blocklet1, entryComponentIds)} start failed for ${getDisplayName(
|
|
1006
1067
|
blocklet1
|
|
1007
1068
|
)}: ${error.message}`;
|
|
1008
1069
|
this._createNotification(did, {
|
|
@@ -1013,9 +1074,16 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1013
1074
|
severity: 'error',
|
|
1014
1075
|
});
|
|
1015
1076
|
|
|
1016
|
-
await this.deleteProcess({ did, componentDids });
|
|
1017
|
-
const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
1018
|
-
|
|
1077
|
+
await this.deleteProcess({ did, componentDids: entryComponentIds });
|
|
1078
|
+
const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
1079
|
+
componentDids: entryComponentIds,
|
|
1080
|
+
operator,
|
|
1081
|
+
});
|
|
1082
|
+
this.emit(BlockletEvents.startFailed, {
|
|
1083
|
+
...res,
|
|
1084
|
+
componentDids: entryComponentIds,
|
|
1085
|
+
error: { message: error.message },
|
|
1086
|
+
});
|
|
1019
1087
|
this.emit(BlockletEvents.statusChange, { ...res, error: { message: error.message } });
|
|
1020
1088
|
|
|
1021
1089
|
if (throwOnError) {
|
|
@@ -3327,9 +3395,11 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3327
3395
|
timeout,
|
|
3328
3396
|
componentDids,
|
|
3329
3397
|
enableDocker: nodeInfo.enableDocker,
|
|
3398
|
+
setBlockletRunning: async (componentDid) => {
|
|
3399
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, { componentDids: [componentDid] });
|
|
3400
|
+
},
|
|
3330
3401
|
});
|
|
3331
3402
|
|
|
3332
|
-
// update blocklet status after healthy check
|
|
3333
3403
|
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, { componentDids });
|
|
3334
3404
|
|
|
3335
3405
|
const res = await this.getBlocklet(did);
|
|
@@ -38923,7 +38923,7 @@ module.exports = require("zlib");
|
|
|
38923
38923
|
/***/ ((module) => {
|
|
38924
38924
|
|
|
38925
38925
|
"use strict";
|
|
38926
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.48","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.48","@abtnode/auth":"1.16.48","@abtnode/certificate-manager":"1.16.48","@abtnode/constant":"1.16.48","@abtnode/cron":"1.16.48","@abtnode/db-cache":"1.16.48","@abtnode/docker-utils":"1.16.48","@abtnode/logger":"1.16.48","@abtnode/models":"1.16.48","@abtnode/queue":"1.16.48","@abtnode/rbac":"1.16.48","@abtnode/router-provider":"1.16.48","@abtnode/static-server":"1.16.48","@abtnode/timemachine":"1.16.48","@abtnode/util":"1.16.48","@aigne/aigne-hub":"^0.6.
|
|
38926
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.48","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.48","@abtnode/auth":"1.16.48","@abtnode/certificate-manager":"1.16.48","@abtnode/constant":"1.16.48","@abtnode/cron":"1.16.48","@abtnode/db-cache":"1.16.48","@abtnode/docker-utils":"1.16.48","@abtnode/logger":"1.16.48","@abtnode/models":"1.16.48","@abtnode/queue":"1.16.48","@abtnode/rbac":"1.16.48","@abtnode/router-provider":"1.16.48","@abtnode/static-server":"1.16.48","@abtnode/timemachine":"1.16.48","@abtnode/util":"1.16.48","@aigne/aigne-hub":"^0.6.10","@arcblock/did":"1.22.2","@arcblock/did-connect-js":"1.22.2","@arcblock/did-ext":"1.22.2","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.22.2","@arcblock/event-hub":"1.22.2","@arcblock/jwt":"1.22.2","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.22.2","@arcblock/vc":"1.22.2","@blocklet/constant":"1.16.48","@blocklet/did-space-js":"^1.1.19","@blocklet/env":"1.16.48","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.48","@blocklet/resolver":"1.16.48","@blocklet/sdk":"1.16.48","@blocklet/server-js":"1.16.48","@blocklet/store":"1.16.48","@blocklet/theme":"^3.1.32","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.22.2","@ocap/util":"1.22.2","@ocap/wallet":"1.22.2","@slack/webhook":"^5.0.4","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":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
|
|
38927
38927
|
|
|
38928
38928
|
/***/ }),
|
|
38929
38929
|
|
package/lib/router/index.js
CHANGED
|
@@ -48,6 +48,7 @@ const expandSites = (sites = []) => {
|
|
|
48
48
|
const domain = typeof domainAlias === 'object' ? domainAlias.value : domainAlias;
|
|
49
49
|
const tmpSite = cloneDeep(site);
|
|
50
50
|
delete tmpSite.domainAliases;
|
|
51
|
+
tmpSite.serviceType = isBlockletSite(site.domain) ? 'blocklet' : 'daemon';
|
|
51
52
|
tmpSite.domain = domain;
|
|
52
53
|
tmpSite.corsAllowedOrigins = mergeAllowedOrigins(tmpSite.domain, site.corsAllowedOrigins);
|
|
53
54
|
result.push(tmpSite);
|
|
@@ -57,6 +58,7 @@ const expandSites = (sites = []) => {
|
|
|
57
58
|
|
|
58
59
|
// skip site if domain is BLOCKLET_SITE_GROUP
|
|
59
60
|
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
61
|
+
site.serviceType = 'daemon';
|
|
60
62
|
result.push(site);
|
|
61
63
|
}
|
|
62
64
|
});
|
|
@@ -74,7 +76,6 @@ const expandSites = (sites = []) => {
|
|
|
74
76
|
const isDefaultSite = (domain) => DOMAIN_FOR_DEFAULT_SITE === domain;
|
|
75
77
|
|
|
76
78
|
const isIpSite = (domain) => [DOMAIN_FOR_IP_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(domain);
|
|
77
|
-
// const isIpSite = (domain) => DOMAIN_FOR_IP_SITE === domain;
|
|
78
79
|
|
|
79
80
|
const filterSites = ({ sites, enableDefaultServer, enableIpServer }) => {
|
|
80
81
|
let result = cloneDeep(sites);
|
package/lib/util/blocklet.js
CHANGED
|
@@ -975,7 +975,10 @@ const validateBlockletChainInfo = (blocklet) => {
|
|
|
975
975
|
return chainInfo;
|
|
976
976
|
};
|
|
977
977
|
|
|
978
|
-
const checkBlockletProcessHealthy = async (
|
|
978
|
+
const checkBlockletProcessHealthy = async (
|
|
979
|
+
blocklet,
|
|
980
|
+
{ minConsecutiveTime, timeout, componentDids, setBlockletRunning } = {}
|
|
981
|
+
) => {
|
|
979
982
|
await forEachBlocklet(
|
|
980
983
|
blocklet,
|
|
981
984
|
async (b) => {
|
|
@@ -1002,9 +1005,18 @@ const checkBlockletProcessHealthy = async (blocklet, { minConsecutiveTime, timeo
|
|
|
1002
1005
|
const logToTerminal = [blocklet.mode, b.mode].includes(BLOCKLET_MODES.DEVELOPMENT);
|
|
1003
1006
|
|
|
1004
1007
|
const startedAt = Date.now();
|
|
1008
|
+
|
|
1005
1009
|
// eslint-disable-next-line no-use-before-define
|
|
1006
1010
|
await _checkProcessHealthy(b, { minConsecutiveTime, timeout, logToTerminal });
|
|
1007
1011
|
logger.info('done check component healthy', { processId: b.env.processId, time: Date.now() - startedAt });
|
|
1012
|
+
|
|
1013
|
+
if (setBlockletRunning) {
|
|
1014
|
+
try {
|
|
1015
|
+
await setBlockletRunning(b.meta.did);
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
logger.error(`Failed to set blocklet as running for DID: ${b.meta.name || b.meta.did}`, { error });
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1008
1020
|
},
|
|
1009
1021
|
{ parallel: true }
|
|
1010
1022
|
);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.49-beta-
|
|
6
|
+
"version": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,22 +19,22 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/analytics": "1.16.49-beta-
|
|
23
|
-
"@abtnode/auth": "1.16.49-beta-
|
|
24
|
-
"@abtnode/certificate-manager": "1.16.49-beta-
|
|
25
|
-
"@abtnode/constant": "1.16.49-beta-
|
|
26
|
-
"@abtnode/cron": "1.16.49-beta-
|
|
27
|
-
"@abtnode/db-cache": "1.16.49-beta-
|
|
28
|
-
"@abtnode/docker-utils": "1.16.49-beta-
|
|
29
|
-
"@abtnode/logger": "1.16.49-beta-
|
|
30
|
-
"@abtnode/models": "1.16.49-beta-
|
|
31
|
-
"@abtnode/queue": "1.16.49-beta-
|
|
32
|
-
"@abtnode/rbac": "1.16.49-beta-
|
|
33
|
-
"@abtnode/router-provider": "1.16.49-beta-
|
|
34
|
-
"@abtnode/static-server": "1.16.49-beta-
|
|
35
|
-
"@abtnode/timemachine": "1.16.49-beta-
|
|
36
|
-
"@abtnode/util": "1.16.49-beta-
|
|
37
|
-
"@aigne/aigne-hub": "^0.6.
|
|
22
|
+
"@abtnode/analytics": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
23
|
+
"@abtnode/auth": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
24
|
+
"@abtnode/certificate-manager": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
25
|
+
"@abtnode/constant": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
26
|
+
"@abtnode/cron": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
27
|
+
"@abtnode/db-cache": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
28
|
+
"@abtnode/docker-utils": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
29
|
+
"@abtnode/logger": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
30
|
+
"@abtnode/models": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
31
|
+
"@abtnode/queue": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
32
|
+
"@abtnode/rbac": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
33
|
+
"@abtnode/router-provider": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
34
|
+
"@abtnode/static-server": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
35
|
+
"@abtnode/timemachine": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
36
|
+
"@abtnode/util": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
37
|
+
"@aigne/aigne-hub": "^0.6.10",
|
|
38
38
|
"@arcblock/did": "1.22.2",
|
|
39
39
|
"@arcblock/did-connect-js": "1.22.2",
|
|
40
40
|
"@arcblock/did-ext": "1.22.2",
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"@arcblock/pm2-events": "^0.0.5",
|
|
46
46
|
"@arcblock/validator": "1.22.2",
|
|
47
47
|
"@arcblock/vc": "1.22.2",
|
|
48
|
-
"@blocklet/constant": "1.16.49-beta-
|
|
49
|
-
"@blocklet/did-space-js": "^1.1.
|
|
50
|
-
"@blocklet/env": "1.16.49-beta-
|
|
48
|
+
"@blocklet/constant": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
49
|
+
"@blocklet/did-space-js": "^1.1.19",
|
|
50
|
+
"@blocklet/env": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
51
51
|
"@blocklet/error": "^0.2.5",
|
|
52
|
-
"@blocklet/meta": "1.16.49-beta-
|
|
53
|
-
"@blocklet/resolver": "1.16.49-beta-
|
|
54
|
-
"@blocklet/sdk": "1.16.49-beta-
|
|
55
|
-
"@blocklet/server-js": "1.16.49-beta-
|
|
56
|
-
"@blocklet/store": "1.16.49-beta-
|
|
57
|
-
"@blocklet/theme": "^3.1.
|
|
52
|
+
"@blocklet/meta": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
53
|
+
"@blocklet/resolver": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
54
|
+
"@blocklet/sdk": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
55
|
+
"@blocklet/server-js": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
56
|
+
"@blocklet/store": "1.16.49-beta-20250828-094758-93e69d1f",
|
|
57
|
+
"@blocklet/theme": "^3.1.32",
|
|
58
58
|
"@fidm/x509": "^1.2.1",
|
|
59
59
|
"@ocap/mcrypto": "1.22.2",
|
|
60
60
|
"@ocap/util": "1.22.2",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"jest": "^29.7.0",
|
|
119
119
|
"unzipper": "^0.10.11"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "587711a6df767cafaadbb503daeac586e22c3988"
|
|
122
122
|
}
|