@abtnode/core 1.6.14 → 1.6.18
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/blocklet/hooks.js +16 -63
- package/lib/blocklet/manager/disk.js +426 -144
- package/lib/blocklet/migration.js +16 -52
- package/lib/event.js +1 -1
- package/lib/index.js +3 -11
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/router/helper.js +33 -19
- package/lib/router/manager.js +64 -42
- package/lib/states/blocklet-extras.js +39 -3
- package/lib/states/blocklet.js +18 -20
- package/lib/states/node.js +17 -2
- package/lib/util/blocklet.js +276 -67
- package/lib/util/default-node-config.js +1 -1
- package/lib/util/get-domain-for-blocklet.js +2 -2
- package/lib/util/router.js +13 -0
- package/lib/validators/node.js +11 -12
- package/lib/validators/permission.js +16 -1
- package/package.json +21 -21
package/lib/states/blocklet.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
/* eslint-disable no-underscore-dangle */
|
|
5
5
|
const omit = require('lodash/omit');
|
|
6
6
|
const uniq = require('lodash/uniq');
|
|
7
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
7
8
|
const detectPort = require('detect-port');
|
|
8
9
|
const Lock = require('@abtnode/util/lib/lock');
|
|
9
10
|
const security = require('@abtnode/util/lib/security');
|
|
@@ -119,7 +120,7 @@ class BlockletState extends BaseState {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
this.emit('remove', doc);
|
|
122
|
-
return resolve(doc);
|
|
123
|
+
return resolve(formatBlocklet(doc, 'onRead', this.options.dek));
|
|
123
124
|
});
|
|
124
125
|
})
|
|
125
126
|
);
|
|
@@ -130,9 +131,9 @@ class BlockletState extends BaseState {
|
|
|
130
131
|
meta,
|
|
131
132
|
source = BlockletSource.registry,
|
|
132
133
|
status = BlockletStatus.added,
|
|
133
|
-
deployedFrom,
|
|
134
|
+
deployedFrom = '',
|
|
134
135
|
mode = BLOCKLET_MODES.PRODUCTION,
|
|
135
|
-
|
|
136
|
+
children: rawChildren = [],
|
|
136
137
|
} = {}) {
|
|
137
138
|
return this.getBlocklet(did).then(
|
|
138
139
|
(doc) =>
|
|
@@ -152,7 +153,7 @@ class BlockletState extends BaseState {
|
|
|
152
153
|
|
|
153
154
|
const ports = await this.getBlockletPorts({ interfaces: sanitized.interfaces || [] });
|
|
154
155
|
|
|
155
|
-
const children = await this.fillChildrenPorts(
|
|
156
|
+
const children = await this.fillChildrenPorts(rawChildren, {
|
|
156
157
|
defaultPort: getMaxPort(ports),
|
|
157
158
|
});
|
|
158
159
|
|
|
@@ -196,7 +197,7 @@ class BlockletState extends BaseState {
|
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
try {
|
|
199
|
-
const formatted = formatBlocklet(updates, 'onUpdate', this.options.dek);
|
|
200
|
+
const formatted = formatBlocklet(cloneDeep(updates), 'onUpdate', this.options.dek);
|
|
200
201
|
const newDoc = await this.updateById(doc._id, { $set: formatted });
|
|
201
202
|
resolve(newDoc);
|
|
202
203
|
} catch (err) {
|
|
@@ -206,7 +207,7 @@ class BlockletState extends BaseState {
|
|
|
206
207
|
);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
upgradeBlocklet({ meta, source, deployedFrom, children } = {}) {
|
|
210
|
+
upgradeBlocklet({ meta, source, deployedFrom = '', children } = {}) {
|
|
210
211
|
return this.getBlocklet(meta.did).then(
|
|
211
212
|
(doc) =>
|
|
212
213
|
// eslint-disable-next-line no-async-promise-executor
|
|
@@ -247,8 +248,10 @@ class BlockletState extends BaseState {
|
|
|
247
248
|
},
|
|
248
249
|
});
|
|
249
250
|
lock.release();
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
|
|
252
|
+
const formatted = formatBlocklet(newDoc, 'onRead', this.options.dek);
|
|
253
|
+
this.emit('upgrade', formatted);
|
|
254
|
+
resolve(formatted);
|
|
252
255
|
} catch (err) {
|
|
253
256
|
lock.release();
|
|
254
257
|
reject(err);
|
|
@@ -392,11 +395,12 @@ class BlockletState extends BaseState {
|
|
|
392
395
|
if (typeof status === 'undefined') {
|
|
393
396
|
throw new Error('Unsupported blocklet status');
|
|
394
397
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
return
|
|
398
|
+
|
|
399
|
+
const doc = await this.getBlocklet(did);
|
|
400
|
+
if (doc.status === status) {
|
|
401
|
+
return formatBlocklet(doc, 'onRead', this.options.dek);
|
|
399
402
|
}
|
|
403
|
+
|
|
400
404
|
const updates = { status, startedAt: undefined, stoppedAt: undefined };
|
|
401
405
|
if (status === BlockletStatus.running) {
|
|
402
406
|
updates.startedAt = new Date();
|
|
@@ -408,14 +412,8 @@ class BlockletState extends BaseState {
|
|
|
408
412
|
updates.stoppedAt = new Date();
|
|
409
413
|
}
|
|
410
414
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
});
|
|
414
|
-
return res;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
getChildrenFromMetas(childrenMeta) {
|
|
418
|
-
return childrenMeta.map((x) => ({ meta: x }));
|
|
415
|
+
await this.update(doc._id, { $set: updates });
|
|
416
|
+
return formatBlocklet({ ...doc, ...updates }, 'onRead', this.options.dek);
|
|
419
417
|
}
|
|
420
418
|
|
|
421
419
|
async fillChildrenPorts(children, { defaultPort = 0, oldChildren } = {}) {
|
package/lib/states/node.js
CHANGED
|
@@ -49,7 +49,10 @@ class NodeState extends BaseState {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
isInitialized(doc) {
|
|
52
|
-
|
|
52
|
+
const isOwnerConnected = !!doc.nodeOwner;
|
|
53
|
+
const isControlledBy3rdParty =
|
|
54
|
+
!doc.enablePassportIssuance && Array.isArray(doc.trustedPassports) && doc.trustedPassports.length > 0;
|
|
55
|
+
return isOwnerConnected || isControlledBy3rdParty;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -92,13 +95,15 @@ class NodeState extends BaseState {
|
|
|
92
95
|
launcherInfo,
|
|
93
96
|
didRegistry,
|
|
94
97
|
didDomain,
|
|
98
|
+
enablePassportIssuance = true,
|
|
99
|
+
trustedPassports = [],
|
|
95
100
|
} = this.options;
|
|
96
101
|
|
|
97
102
|
if (nodeOwner && !validateOwner(nodeOwner)) {
|
|
98
103
|
return reject(new Error('Node owner is invalid'));
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
const initialized = this.isInitialized({ nodeOwner });
|
|
106
|
+
const initialized = this.isInitialized({ nodeOwner, enablePassportIssuance, trustedPassports });
|
|
102
107
|
|
|
103
108
|
return getDefaultConfigs()
|
|
104
109
|
.then((defaultConfigs) =>
|
|
@@ -126,6 +131,9 @@ class NodeState extends BaseState {
|
|
|
126
131
|
launcherInfo: launcherInfo || undefined,
|
|
127
132
|
didRegistry,
|
|
128
133
|
didDomain,
|
|
134
|
+
enablePassportIssuance,
|
|
135
|
+
trustedPassports,
|
|
136
|
+
customBlockletNumber: 0,
|
|
129
137
|
},
|
|
130
138
|
async (e, data) => {
|
|
131
139
|
if (e) {
|
|
@@ -262,6 +270,13 @@ class NodeState extends BaseState {
|
|
|
262
270
|
getBlockletRegistry() {
|
|
263
271
|
return this.read().then((info) => info.blockletRegistryList.find((item) => item.selected).url);
|
|
264
272
|
}
|
|
273
|
+
|
|
274
|
+
async increaseCustomBlockletNumber() {
|
|
275
|
+
const { _id, customBlockletNumber = 0 } = await this.read();
|
|
276
|
+
const num = customBlockletNumber + 1;
|
|
277
|
+
await this.update(_id, { $set: { customBlockletNumber: num } });
|
|
278
|
+
return num;
|
|
279
|
+
}
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
module.exports = NodeState;
|
package/lib/util/blocklet.js
CHANGED
|
@@ -6,9 +6,11 @@ const os = require('os');
|
|
|
6
6
|
const tar = require('tar');
|
|
7
7
|
const get = require('lodash/get');
|
|
8
8
|
const intersection = require('lodash/intersection');
|
|
9
|
+
const intersectionBy = require('lodash/intersectionBy');
|
|
9
10
|
const streamToPromise = require('stream-to-promise');
|
|
10
11
|
const { Throttle } = require('stream-throttle');
|
|
11
12
|
const ssri = require('ssri');
|
|
13
|
+
const diff = require('deep-diff');
|
|
12
14
|
|
|
13
15
|
const { toHex } = require('@ocap/util');
|
|
14
16
|
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
|
|
@@ -17,7 +19,10 @@ const sleep = require('@abtnode/util/lib/sleep');
|
|
|
17
19
|
const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
|
|
18
20
|
const CustomError = require('@abtnode/util/lib/custom-error');
|
|
19
21
|
const getFolderSize = require('@abtnode/util/lib/get-folder-size');
|
|
22
|
+
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
23
|
+
const hashFiles = require('@abtnode/util/lib/hash-files');
|
|
20
24
|
const { BLOCKLET_MAX_MEM_LIMIT_IN_MB } = require('@abtnode/constant');
|
|
25
|
+
|
|
21
26
|
const {
|
|
22
27
|
BlockletStatus,
|
|
23
28
|
BlockletSource,
|
|
@@ -29,6 +34,7 @@ const {
|
|
|
29
34
|
BLOCKLET_DEFAULT_PORT_NAME,
|
|
30
35
|
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
31
36
|
BLOCKLET_CONFIGURABLE_KEY,
|
|
37
|
+
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
32
38
|
fromBlockletStatus,
|
|
33
39
|
} = require('@blocklet/meta/lib/constants');
|
|
34
40
|
const verifyMultiSig = require('@blocklet/meta/lib/verify-multi-sig');
|
|
@@ -41,6 +47,7 @@ const { forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
|
41
47
|
const { validate: validateEngine, get: getEngine } = require('../blocklet/manager/engine');
|
|
42
48
|
|
|
43
49
|
const isRequirementsSatisfied = require('./requirement');
|
|
50
|
+
const { getDidDomainForBlocklet } = require('./get-domain-for-blocklet');
|
|
44
51
|
const { getServices } = require('./service');
|
|
45
52
|
const {
|
|
46
53
|
isBeforeInstalled,
|
|
@@ -60,8 +67,6 @@ const getBlockletEngineNameByPlatform = (blockletMeta) => getBlockletEngine(bloc
|
|
|
60
67
|
|
|
61
68
|
const noop = () => {};
|
|
62
69
|
|
|
63
|
-
const asyncFs = fs.promises;
|
|
64
|
-
|
|
65
70
|
const statusMap = {
|
|
66
71
|
online: BlockletStatus.running,
|
|
67
72
|
launching: BlockletStatus.starting,
|
|
@@ -79,11 +84,14 @@ const PRIVATE_NODE_ENVS = [
|
|
|
79
84
|
'ABT_NODE_TOKEN_SECRET',
|
|
80
85
|
'ABT_NODE_SK',
|
|
81
86
|
'ABT_NODE_SESSION_SECRET',
|
|
82
|
-
'ABT_NODE_NAME',
|
|
83
|
-
'ABT_NODE_DESCRIPTION',
|
|
84
87
|
'ABT_NODE_BASE_URL',
|
|
85
88
|
'ABT_NODE_LOG_LEVEL',
|
|
86
89
|
'ABT_NODE_LOG_DIR',
|
|
90
|
+
// in /core/cli/bin/blocklet.js
|
|
91
|
+
'CLI_MODE',
|
|
92
|
+
'ABT_NODE_HOME',
|
|
93
|
+
'PM2_HOME',
|
|
94
|
+
'ABT_NODE_CONFIG_FILE',
|
|
87
95
|
];
|
|
88
96
|
|
|
89
97
|
/**
|
|
@@ -114,19 +122,9 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
114
122
|
logsDir = path.join(dataDirs.logs, rootBlocklet.meta.name);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
if (ensure) {
|
|
118
|
-
try {
|
|
119
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
120
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
121
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
122
|
-
} catch (err) {
|
|
123
|
-
logger.error('make blocklet dir failed', { error: err });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
125
|
// get app dirs
|
|
128
126
|
|
|
129
|
-
const {
|
|
127
|
+
const { main, group } = blocklet.meta;
|
|
130
128
|
|
|
131
129
|
const startFromDevEntry =
|
|
132
130
|
blocklet.mode === BLOCKLET_MODES.DEVELOPMENT && blocklet.meta.scripts && blocklet.meta.scripts.dev;
|
|
@@ -141,13 +139,24 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
141
139
|
if (blocklet.source === BlockletSource.local) {
|
|
142
140
|
appDir = blocklet.deployedFrom;
|
|
143
141
|
} else {
|
|
144
|
-
appDir =
|
|
142
|
+
appDir = getBundleDir(dataDirs.blocklets, blocklet.meta);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
145
|
if (!appDir) {
|
|
148
146
|
throw new Error('Can not determine blocklet directory, maybe invalid deployment from local blocklets');
|
|
149
147
|
}
|
|
150
148
|
|
|
149
|
+
if (ensure) {
|
|
150
|
+
try {
|
|
151
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
152
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
153
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
154
|
+
fs.mkdirSync(appDir, { recursive: true }); // prevent getDiskInfo failed from custom blocklet
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.error('make blocklet dir failed', { error: err });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
151
160
|
mainDir = appDir;
|
|
152
161
|
|
|
153
162
|
if (!startFromDevEntry && !isBeforeInstalled(rootBlocklet.status)) {
|
|
@@ -242,12 +251,16 @@ const getRootSystemEnvironments = (blocklet, nodeInfo) => {
|
|
|
242
251
|
const appName = title || name || result.name;
|
|
243
252
|
const appDescription = description || result.description;
|
|
244
253
|
|
|
254
|
+
// FIXME: we should use https here when possible, eg, when did-gateway is available
|
|
255
|
+
const appUrl = `http://${getDidDomainForBlocklet({ name, daemonDid: nodeInfo.did, didDomain: nodeInfo.didDomain })}`;
|
|
256
|
+
|
|
245
257
|
return {
|
|
246
258
|
BLOCKLET_DID: did,
|
|
247
259
|
BLOCKLET_APP_SK: appSk,
|
|
248
260
|
BLOCKLET_APP_ID: appId,
|
|
249
261
|
BLOCKLET_APP_NAME: appName,
|
|
250
262
|
BLOCKLET_APP_DESCRIPTION: appDescription,
|
|
263
|
+
BLOCKLET_APP_URL: appUrl,
|
|
251
264
|
};
|
|
252
265
|
};
|
|
253
266
|
|
|
@@ -427,11 +440,13 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
427
440
|
const options = {
|
|
428
441
|
namespace: 'blocklets',
|
|
429
442
|
name: appId,
|
|
430
|
-
|
|
443
|
+
cwd: appCwd,
|
|
431
444
|
time: true,
|
|
432
445
|
output: path.join(logsDir, 'output.log'),
|
|
433
446
|
error: path.join(logsDir, 'error.log'),
|
|
434
|
-
|
|
447
|
+
wait_ready: process.env.NODE_ENV !== 'test',
|
|
448
|
+
listen_timeout: 5000,
|
|
449
|
+
max_memory_restart: `${maxMemoryRestart}M`,
|
|
435
450
|
max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
|
|
436
451
|
env: {
|
|
437
452
|
...env,
|
|
@@ -459,17 +474,17 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
459
474
|
const engine = getEngine(blockletEngineInfo.interpreter);
|
|
460
475
|
options.interpreter = engine.interpreter === 'node' ? '' : engine.interpreter;
|
|
461
476
|
options.interpreterArgs = engine.args || '';
|
|
462
|
-
|
|
463
477
|
options.script = blockletEngineInfo.script || appMain;
|
|
464
|
-
|
|
465
|
-
logger.debug('start.blocklet.engine.info', { blockletEngineInfo });
|
|
466
|
-
logger.debug('start.blocklet.max_memory_restart', { maxMemoryRestart });
|
|
467
478
|
}
|
|
468
479
|
|
|
469
480
|
await pm2.startAsync(options);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
481
|
+
|
|
482
|
+
const status = await getProcessState(appId);
|
|
483
|
+
if (status === BlockletStatus.error) {
|
|
484
|
+
throw new Error(`${appId} is not running within 5 seconds`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
logger.info('blocklet started', { appId, status });
|
|
473
488
|
},
|
|
474
489
|
{ parallel: true }
|
|
475
490
|
);
|
|
@@ -532,6 +547,10 @@ const getBlockletStatusFromProcess = async (blocklet) => {
|
|
|
532
547
|
|
|
533
548
|
const list = await Promise.all(tasks);
|
|
534
549
|
|
|
550
|
+
if (!list.length) {
|
|
551
|
+
return blocklet.status;
|
|
552
|
+
}
|
|
553
|
+
|
|
535
554
|
return getRootBlockletStatus(list);
|
|
536
555
|
};
|
|
537
556
|
|
|
@@ -610,26 +629,76 @@ const reloadProcess = (appId) =>
|
|
|
610
629
|
});
|
|
611
630
|
});
|
|
612
631
|
|
|
613
|
-
const
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
632
|
+
const findWebInterface = (blocklet) => {
|
|
633
|
+
const meta = blocklet.meta || blocklet || {};
|
|
634
|
+
const { interfaces = [] } = meta;
|
|
635
|
+
|
|
636
|
+
if (!Array.isArray(interfaces)) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return interfaces.find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* this function has side effect on children
|
|
645
|
+
*/
|
|
646
|
+
const parseChildren = async (src, { children, dynamic } = {}) => {
|
|
647
|
+
const configs = Array.isArray(src) ? src : src.children || [];
|
|
648
|
+
|
|
649
|
+
if (children) {
|
|
650
|
+
children.forEach((x) => {
|
|
651
|
+
x.dynamic = !!dynamic;
|
|
652
|
+
});
|
|
653
|
+
mergeMeta(configs, children);
|
|
654
|
+
return children;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (!configs || !configs.length) {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const results = [];
|
|
662
|
+
|
|
663
|
+
for (const config of configs) {
|
|
664
|
+
const mountPoint = config.mountPoint || config.mountPoints[0].root.prefix;
|
|
665
|
+
if (!mountPoint) {
|
|
666
|
+
throw new Error(`MountPoint does not found in child ${config.name}`);
|
|
629
667
|
}
|
|
668
|
+
|
|
669
|
+
const m = await getBlockletMetaFromUrl(config.resolved);
|
|
670
|
+
if (m.name !== config.name) {
|
|
671
|
+
logger.error('Resolved child blocklet name does not match in the configuration', {
|
|
672
|
+
expected: config.name,
|
|
673
|
+
resolved: m.name,
|
|
674
|
+
});
|
|
675
|
+
throw new Error(
|
|
676
|
+
`Child blocklet name does not match in the configuration. expected: ${config.name}, resolved: ${m.name}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
validateBlockletMeta(m, { ensureDist: true });
|
|
680
|
+
|
|
681
|
+
const webInterface = findWebInterface(m);
|
|
682
|
+
if (!webInterface) {
|
|
683
|
+
throw new Error(`Web interface does not found in child ${config.name}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const rule = webInterface.prefix;
|
|
687
|
+
if (rule !== BLOCKLET_DYNAMIC_PATH_PREFIX && normalizePathPrefix(rule) !== normalizePathPrefix(mountPoint)) {
|
|
688
|
+
throw new Error(`Prefix does not match in child ${config.name}. expected: ${rule}, resolved: ${mountPoint}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
results.push({
|
|
692
|
+
mountPoint,
|
|
693
|
+
meta: m,
|
|
694
|
+
dynamic: !!dynamic,
|
|
695
|
+
sourceUrl: config.resolved,
|
|
696
|
+
});
|
|
630
697
|
}
|
|
631
698
|
|
|
632
|
-
|
|
699
|
+
mergeMeta(configs, results);
|
|
700
|
+
|
|
701
|
+
return results;
|
|
633
702
|
};
|
|
634
703
|
|
|
635
704
|
const validateBlocklet = (blocklet) =>
|
|
@@ -727,7 +796,7 @@ const verifyIntegrity = async ({ file, integrity: expected }) => {
|
|
|
727
796
|
return true;
|
|
728
797
|
};
|
|
729
798
|
|
|
730
|
-
const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
799
|
+
const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings }) => {
|
|
731
800
|
for (const blocklet of blocklets) {
|
|
732
801
|
if (
|
|
733
802
|
[
|
|
@@ -745,53 +814,104 @@ const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
|
745
814
|
}
|
|
746
815
|
}
|
|
747
816
|
|
|
748
|
-
// blockletMap: { <name/version>: true }
|
|
749
|
-
const blockletMap =
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
817
|
+
// blockletMap: { <[scope/]name/version>: true }
|
|
818
|
+
const blockletMap = {};
|
|
819
|
+
for (const blocklet of blocklets) {
|
|
820
|
+
blockletMap[`${blocklet.meta.name}/${blocklet.meta.version}`] = true;
|
|
821
|
+
for (const child of blocklet.children || []) {
|
|
822
|
+
blockletMap[`${child.meta.name}/${child.meta.version}`] = true;
|
|
753
823
|
}
|
|
754
|
-
|
|
755
|
-
|
|
824
|
+
}
|
|
825
|
+
for (const setting of blockletSettings) {
|
|
826
|
+
for (const child of setting.children || []) {
|
|
827
|
+
blockletMap[`${child.meta.name}/${child.meta.version}`] = true;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
756
830
|
|
|
757
|
-
// appDirs: [{ key: <name/version>, dir: appDir }]
|
|
831
|
+
// appDirs: [{ key: <[scope/]name/version>, dir: appDir }]
|
|
758
832
|
const appDirs = [];
|
|
759
833
|
|
|
760
834
|
// fill appDirs
|
|
761
835
|
try {
|
|
762
|
-
|
|
763
|
-
|
|
836
|
+
// @return root/scope/bundle/version
|
|
837
|
+
const getNextLevel = (level, name) => {
|
|
838
|
+
if (level === 'root') {
|
|
839
|
+
if (name.startsWith('@')) {
|
|
840
|
+
return 'scope';
|
|
841
|
+
}
|
|
842
|
+
return 'bundle';
|
|
843
|
+
}
|
|
844
|
+
if (level === 'scope') {
|
|
845
|
+
return 'bundle';
|
|
846
|
+
}
|
|
847
|
+
if (level === 'bundle') {
|
|
848
|
+
return 'version';
|
|
849
|
+
}
|
|
850
|
+
throw new Error(`Invalid level ${level}`);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
const fillAppDirs = async (dir, level = 'root') => {
|
|
854
|
+
if (level === 'version') {
|
|
855
|
+
if (!fs.existsSync(path.join(dir, 'blocklet.yml'))) {
|
|
856
|
+
logger.error('blocklet.yml does not exist in blocklet bundle dir', { dir });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
764
860
|
appDirs.push({
|
|
765
861
|
key: path.relative(installDir, dir),
|
|
766
862
|
dir,
|
|
767
863
|
});
|
|
864
|
+
|
|
768
865
|
return;
|
|
769
866
|
}
|
|
867
|
+
|
|
770
868
|
const nextDirs = [];
|
|
771
|
-
for (const x of await
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
return;
|
|
869
|
+
for (const x of await fs.promises.readdir(dir)) {
|
|
870
|
+
if (!fs.lstatSync(path.join(dir, x)).isDirectory()) {
|
|
871
|
+
logger.error('pruneBlockletBundle: invalid file in bundle storage', { dir, file: x });
|
|
872
|
+
// eslint-disable-next-line no-continue
|
|
873
|
+
continue;
|
|
777
874
|
}
|
|
778
|
-
nextDirs.push(
|
|
875
|
+
nextDirs.push(x);
|
|
779
876
|
}
|
|
780
877
|
|
|
781
878
|
for (const x of nextDirs) {
|
|
782
|
-
await fillAppDirs(x);
|
|
879
|
+
await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
|
|
783
880
|
}
|
|
784
881
|
};
|
|
785
|
-
await fillAppDirs(installDir);
|
|
882
|
+
await fillAppDirs(installDir, 'root');
|
|
786
883
|
} catch (error) {
|
|
787
884
|
logger.error('fill app dirs failed', { error });
|
|
788
885
|
}
|
|
789
886
|
|
|
887
|
+
const ensureBundleDirRemoved = async (dir) => {
|
|
888
|
+
const relativeDir = path.relative(installDir, dir);
|
|
889
|
+
const arr = relativeDir.split('/').filter(Boolean);
|
|
890
|
+
const { length } = arr;
|
|
891
|
+
const bundleName = arr[length - 2];
|
|
892
|
+
const scopeName = length > 2 ? arr[length - 3] : '';
|
|
893
|
+
const bundleDir = path.join(installDir, scopeName, bundleName);
|
|
894
|
+
const isEmpty = (await fs.promises.readdir(bundleDir)).length === 0;
|
|
895
|
+
if (isEmpty) {
|
|
896
|
+
logger.info('Remove bundle folder', { bundleDir });
|
|
897
|
+
await fs.remove(bundleDir);
|
|
898
|
+
}
|
|
899
|
+
if (scopeName) {
|
|
900
|
+
const scopeDir = path.join(installDir, scopeName);
|
|
901
|
+
const isScopeEmpty = (await fs.promises.readdir(scopeDir)).length === 0;
|
|
902
|
+
if (isScopeEmpty) {
|
|
903
|
+
logger.info('Remove scope folder', { scopeDir });
|
|
904
|
+
await fs.remove(scopeDir);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
790
909
|
// remove trash
|
|
791
910
|
for (const app of appDirs) {
|
|
792
911
|
if (!blockletMap[app.key]) {
|
|
793
|
-
logger.info('Remove
|
|
912
|
+
logger.info('Remove app folder', { dir: app.dir });
|
|
794
913
|
await fs.remove(app.dir);
|
|
914
|
+
await ensureBundleDirRemoved(app.dir);
|
|
795
915
|
}
|
|
796
916
|
}
|
|
797
917
|
|
|
@@ -829,15 +949,25 @@ const getRuntimeInfo = async (appId) => {
|
|
|
829
949
|
};
|
|
830
950
|
};
|
|
831
951
|
|
|
832
|
-
|
|
952
|
+
/**
|
|
953
|
+
* merge services
|
|
954
|
+
* from meta.children[].mountPoints[].services
|
|
955
|
+
* to childrenMeta[].interfaces[].services
|
|
956
|
+
*
|
|
957
|
+
* @param {array<child>|object{children:array}} source e.g. [<config>] or { children: [<config>] }
|
|
958
|
+
* @param {array<meta|{meta}>} childrenMeta e.g. [<meta>] or [{ meta: <meta> }]
|
|
959
|
+
*/
|
|
960
|
+
|
|
961
|
+
const mergeMeta = (source, childrenMeta = []) => {
|
|
833
962
|
// configMap
|
|
834
963
|
const configMap = {};
|
|
835
|
-
(
|
|
964
|
+
(Array.isArray(source) ? source : source.children || []).forEach((x) => {
|
|
836
965
|
configMap[x.name] = x;
|
|
837
966
|
});
|
|
838
967
|
|
|
839
968
|
// merge service from config to child meta
|
|
840
|
-
childrenMeta.forEach((
|
|
969
|
+
childrenMeta.forEach((child) => {
|
|
970
|
+
const childMeta = child.meta || child;
|
|
841
971
|
const config = configMap[childMeta.name];
|
|
842
972
|
if (!config) {
|
|
843
973
|
return;
|
|
@@ -897,10 +1027,84 @@ const getUpdateMetaList = (oldMetas = [], newMetas = []) => {
|
|
|
897
1027
|
return newMetas.filter(({ version, did }) => did && version !== oldMap[did]);
|
|
898
1028
|
};
|
|
899
1029
|
|
|
1030
|
+
const getSourceFromInstallParams = (params) => {
|
|
1031
|
+
if (params.url) {
|
|
1032
|
+
return BlockletSource.url;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (params.file) {
|
|
1036
|
+
return BlockletSource.upload;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (params.did) {
|
|
1040
|
+
return BlockletSource.registry;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (params.title && params.description) {
|
|
1044
|
+
return BlockletSource.custom;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
throw new Error('Can only install blocklet from store/url/upload/custom');
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
const checkDuplicateComponents = (dynamicComponents, staticComponents) => {
|
|
1051
|
+
const duplicates = intersectionBy(dynamicComponents, staticComponents, 'meta.did');
|
|
1052
|
+
if (duplicates.length) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Cannot add duplicate component${duplicates.length > 1 ? 's' : ''}: ${duplicates
|
|
1055
|
+
.map((x) => x.meta.title || x.meta.name)
|
|
1056
|
+
.join(', ')}`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const getDiffFiles = async (inputFiles, sourceDir) => {
|
|
1062
|
+
if (!fs.existsSync(sourceDir)) {
|
|
1063
|
+
throw new Error(`${sourceDir} does not exist`);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const files = inputFiles.reduce((obj, item) => {
|
|
1067
|
+
obj[item.file] = item.hash;
|
|
1068
|
+
return obj;
|
|
1069
|
+
}, {});
|
|
1070
|
+
|
|
1071
|
+
const { files: sourceFiles } = await hashFiles(sourceDir, {
|
|
1072
|
+
filter: (x) => x.indexOf('node_modules') === -1,
|
|
1073
|
+
concurrentHash: 1,
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const addSet = [];
|
|
1077
|
+
const changeSet = [];
|
|
1078
|
+
const deleteSet = [];
|
|
1079
|
+
|
|
1080
|
+
const diffFiles = diff(sourceFiles, files);
|
|
1081
|
+
if (diffFiles) {
|
|
1082
|
+
diffFiles.forEach((item) => {
|
|
1083
|
+
if (item.kind === 'D') {
|
|
1084
|
+
deleteSet.push(item.path[0]);
|
|
1085
|
+
}
|
|
1086
|
+
if (item.kind === 'E') {
|
|
1087
|
+
changeSet.push(item.path[0]);
|
|
1088
|
+
}
|
|
1089
|
+
if (item.kind === 'N') {
|
|
1090
|
+
addSet.push(item.path[0]);
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return {
|
|
1096
|
+
addSet,
|
|
1097
|
+
changeSet,
|
|
1098
|
+
deleteSet,
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
const getBundleDir = (installDir, bundle) => path.join(installDir, bundle.name, bundle.version);
|
|
1103
|
+
|
|
900
1104
|
module.exports = {
|
|
901
1105
|
forEachBlocklet,
|
|
902
1106
|
getBlockletMetaFromUrl,
|
|
903
|
-
|
|
1107
|
+
parseChildren,
|
|
904
1108
|
getBlockletDirs,
|
|
905
1109
|
getRootSystemEnvironments,
|
|
906
1110
|
getSystemEnvironments,
|
|
@@ -926,4 +1130,9 @@ module.exports = {
|
|
|
926
1130
|
mergeMeta,
|
|
927
1131
|
fixAndVerifyBlockletMeta,
|
|
928
1132
|
getUpdateMetaList,
|
|
1133
|
+
getSourceFromInstallParams,
|
|
1134
|
+
findWebInterface,
|
|
1135
|
+
checkDuplicateComponents,
|
|
1136
|
+
getDiffFiles,
|
|
1137
|
+
getBundleDir,
|
|
929
1138
|
};
|