@abtnode/core 1.6.16 → 1.6.17

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.
@@ -3,12 +3,13 @@
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const get = require('lodash/get');
6
+ const pick = require('lodash/pick');
6
7
  const cloneDeep = require('lodash/cloneDeep');
7
8
  const semver = require('semver');
8
9
  const capitalize = require('lodash/capitalize');
9
- const diff = require('deep-diff');
10
10
  const { Throttle } = require('stream-throttle');
11
11
  const LRU = require('lru-cache');
12
+ const joi = require('joi');
12
13
 
13
14
  const { isValid: isValidDid } = require('@arcblock/did');
14
15
  const { verifyPresentation } = require('@arcblock/vc');
@@ -16,7 +17,6 @@ const { toBase58, isHex } = require('@ocap/util');
16
17
  const { fromSecretKey } = require('@ocap/wallet');
17
18
 
18
19
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
19
- const hashFiles = require('@abtnode/util/lib/hash-files');
20
20
  const downloadFile = require('@abtnode/util/lib/download-file');
21
21
  const Lock = require('@abtnode/util/lib/lock');
22
22
  const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
@@ -24,9 +24,17 @@ const { updateBlocklet: updateDidDocument } = require('@abtnode/util/lib/did-doc
24
24
  const { BLOCKLET_PURCHASE_NFT_TYPE } = require('@abtnode/constant');
25
25
 
26
26
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
27
- const { isFreeBlocklet, isDeletableBlocklet, getRequiredMissingConfigs } = require('@blocklet/meta/lib/util');
27
+ const {
28
+ isFreeBlocklet,
29
+ isDeletableBlocklet,
30
+ getRequiredMissingConfigs,
31
+ hasRunnableComponent,
32
+ } = require('@blocklet/meta/lib/util');
28
33
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
29
34
  const { getBlockletInfo } = require('@blocklet/meta/lib');
35
+ const toBlockletDid = require('@blocklet/meta/lib/did');
36
+ const { validateMeta } = require('@blocklet/meta/lib/validate');
37
+ const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
30
38
 
31
39
  const {
32
40
  BlockletStatus,
@@ -37,6 +45,15 @@ const {
37
45
  BlockletGroup,
38
46
  fromBlockletStatus,
39
47
  fromBlockletSource,
48
+ BLOCKLET_DEFAULT_PORT_NAME,
49
+ BLOCKLET_INTERFACE_TYPE_WEB,
50
+ BLOCKLET_INTERFACE_PUBLIC,
51
+ BLOCKLET_DYNAMIC_PATH_PREFIX,
52
+ BLOCKLET_INTERFACE_PROTOCOL_HTTP,
53
+ BLOCKLET_DEFAULT_PATH_REWRITE,
54
+ BLOCKLET_DEFAULT_VERSION,
55
+ BLOCKLET_LATEST_SPEC_VERSION,
56
+ BLOCKLET_META_FILE,
40
57
  } = require('@blocklet/meta/lib/constants');
41
58
  const util = require('../../util');
42
59
  const {
@@ -60,16 +77,19 @@ const {
60
77
  getBlockletStatusFromProcess,
61
78
  checkBlockletProcessHealthy,
62
79
  validateBlocklet,
63
- getChildrenMeta,
64
80
  statusMap,
65
81
  expandTarball,
66
82
  verifyIntegrity,
67
83
  pruneBlockletBundle,
68
84
  getDiskInfo,
69
85
  getRuntimeInfo,
70
- mergeMeta,
71
86
  getUpdateMetaList,
72
87
  getRuntimeEnvironments,
88
+ getSourceFromInstallParams,
89
+ parseChildren,
90
+ checkDuplicateComponents,
91
+ getDiffFiles,
92
+ getBundleDir,
73
93
  } = require('../../util/blocklet');
74
94
  const states = require('../../states');
75
95
  const BlockletRegistry = require('../registry');
@@ -153,20 +173,43 @@ class BlockletManager extends BaseBlockletManager {
153
173
  */
154
174
  async install(params, context) {
155
175
  logger.debug('install blocklet', { params, context });
156
- if (params.url) {
176
+
177
+ const source = getSourceFromInstallParams(params);
178
+
179
+ if (source === BlockletSource.url) {
157
180
  return this._installFromUrl({ url: params.url, sync: params.sync }, context);
158
181
  }
159
182
 
160
- if (params.file) {
183
+ if (source === BlockletSource.upload) {
161
184
  const { file, did, diffVersion, deleteSet } = params;
162
185
  return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
163
186
  }
164
187
 
165
- if (params.did) {
166
- return this._installFromRegistry({ did: params.did }, context);
188
+ if (source === BlockletSource.registry) {
189
+ return this._installFromStore({ did: params.did }, context);
190
+ }
191
+
192
+ if (source === BlockletSource.custom) {
193
+ return this._installFromCreate({ title: params.title, description: params.description }, context);
167
194
  }
168
195
 
169
- throw new Error('Can only install blocklet from url/file/did');
196
+ // should not be here
197
+ throw new Error('Unknown source');
198
+ }
199
+
200
+ async installComponent({ rootDid, mountPoint, url, file, did, diffVersion, deleteSet }, context = {}) {
201
+ logger.debug('start install component', { rootDid, mountPoint, url });
202
+
203
+ if (file) {
204
+ return this._installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context });
205
+ }
206
+
207
+ if (url) {
208
+ return this._installComponentFromUrl({ rootDid, mountPoint, url, context });
209
+ }
210
+
211
+ // should not be here
212
+ throw new Error('Unknown source');
170
213
  }
171
214
 
172
215
  // eslint-disable-next-line no-unused-vars
@@ -210,7 +253,7 @@ class BlockletManager extends BaseBlockletManager {
210
253
  const did = get(vc, 'credentialSubject.purchased.blocklet.id');
211
254
  const registry = urlObject.origin;
212
255
 
213
- return this._installFromRegistry({ did, registry }, { ...context, blockletPurchaseVerified: true });
256
+ return this._installFromStore({ did, registry }, { ...context, blockletPurchaseVerified: true });
214
257
  }
215
258
 
216
259
  async start({ did, checkHealthImmediately = false, throwOnError }, context) {
@@ -218,6 +261,10 @@ class BlockletManager extends BaseBlockletManager {
218
261
  const blocklet = await this.ensureBlocklet(did);
219
262
 
220
263
  try {
264
+ if (!hasRunnableComponent(blocklet)) {
265
+ throw new Error('No runnable component found');
266
+ }
267
+
221
268
  // check required config
222
269
  const missingProps = getRequiredMissingConfigs(blocklet);
223
270
  if (missingProps.length) {
@@ -412,6 +459,22 @@ class BlockletManager extends BaseBlockletManager {
412
459
  }
413
460
  }
414
461
 
462
+ async deleteComponent({ did, rootDid }, context) {
463
+ logger.info('delete blocklet component', { did, rootDid });
464
+
465
+ const doc = await states.blocklet.getBlocklet(rootDid);
466
+
467
+ doc.children = doc.children.filter((x) => x.meta.did !== did);
468
+ const children = (await this._getDynamicChildrenFromSettings(rootDid)).filter((x) => x.meta.did !== did);
469
+
470
+ await states.blocklet.updateBlocklet(rootDid, doc);
471
+ states.blockletExtras.setSettings(rootDid, { children });
472
+
473
+ const newBlocklet = await this.ensureBlocklet(rootDid);
474
+ this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context }); // trigger router refresh
475
+ return newBlocklet;
476
+ }
477
+
415
478
  async cancelDownload({ did }, context) {
416
479
  await preDownloadLock.acquire();
417
480
  try {
@@ -664,56 +727,45 @@ class BlockletManager extends BaseBlockletManager {
664
727
  }
665
728
 
666
729
  // eslint-disable-next-line no-unused-vars
667
- async diff({ did, hashFiles: clientFiles }, context) {
730
+ async diff({ did, hashFiles: clientFiles, rootDid: inputRootDid }, context) {
668
731
  if (!did) {
669
732
  throw new Error('did is empty');
670
733
  }
734
+
671
735
  if (!clientFiles || !clientFiles.length) {
672
736
  throw new Error('hashFiles is empty');
673
737
  }
674
738
 
675
- logger.info('Get blocklet diff', { did, clientFilesNumber: clientFiles.length });
739
+ const rootDid = inputRootDid || did;
740
+ const childDid = inputRootDid ? did : '';
741
+
742
+ if (childDid === rootDid) {
743
+ throw new Error('Cannot add self as a component');
744
+ }
745
+
746
+ logger.info('Get blocklet diff', { rootDid, childDid, clientFilesNumber: clientFiles.length });
747
+
748
+ const rootBlocklet = await states.blocklet.getBlocklet(rootDid);
749
+ if (childDid && !rootBlocklet) {
750
+ throw new Error('Root blocklet does not exist');
751
+ }
752
+
753
+ const state = childDid ? await (rootBlocklet.children || []).find((x) => x.meta.did === childDid) : rootBlocklet;
676
754
 
677
- const state = await states.blocklet.getBlocklet(did);
678
755
  if (!state) {
679
756
  return {
680
757
  hasBlocklet: false,
681
758
  };
682
759
  }
760
+
683
761
  if (state.source === BlockletSource.local) {
684
762
  throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
685
763
  }
686
- const { name, version } = state.meta;
687
- const installDir = path.join(this.installDir, name, version);
688
- // eslint-disable-next-line no-param-reassign
689
- clientFiles = clientFiles.reduce((obj, item) => {
690
- obj[item.file] = item.hash;
691
- return obj;
692
- }, {});
693
764
 
694
- const { files } = await hashFiles(installDir, {
695
- filter: (x) => x.indexOf('node_modules') === -1,
696
- concurrentHash: 1,
697
- });
698
- logger.info('Get files hash', { filesNum: Object.keys(files).length });
699
-
700
- const addSet = [];
701
- const changeSet = [];
702
- const deleteSet = [];
703
- const diffFiles = diff(files, clientFiles);
704
- if (diffFiles) {
705
- diffFiles.forEach((item) => {
706
- if (item.kind === 'D') {
707
- deleteSet.push(item.path[0]);
708
- }
709
- if (item.kind === 'E') {
710
- changeSet.push(item.path[0]);
711
- }
712
- if (item.kind === 'N') {
713
- addSet.push(item.path[0]);
714
- }
715
- });
716
- }
765
+ const { version } = state.meta;
766
+ const bundleDir = getBundleDir(this.installDir, state.meta);
767
+ const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
768
+
717
769
  logger.info('Diff files', {
718
770
  name: state.meta.name,
719
771
  did: state.meta.did,
@@ -722,6 +774,7 @@ class BlockletManager extends BaseBlockletManager {
722
774
  changeNum: changeSet.length,
723
775
  deleteNum: deleteSet.length,
724
776
  });
777
+
725
778
  return {
726
779
  hasBlocklet: true,
727
780
  version,
@@ -733,10 +786,20 @@ class BlockletManager extends BaseBlockletManager {
733
786
 
734
787
  async checkChildrenForUpdates({ did }) {
735
788
  const blocklet = await states.blocklet.getBlocklet(did);
736
- const childrenMeta = await getChildrenMeta(blocklet.meta);
789
+ const newStaticChildren = await parseChildren(blocklet.meta);
790
+
791
+ const oldDynamicChildren = await this._getDynamicChildrenFromSettings(did);
792
+ const noneSourceUrlChildren = oldDynamicChildren.filter((x) => !x.sourceUrl);
793
+ const dynamicConfig = oldDynamicChildren
794
+ .filter((x) => x.sourceUrl)
795
+ .map((x) => ({ resolved: x.sourceUrl, name: x.meta.name, mountPoint: x.mountPoint }));
796
+ const newDynamicChildren = [...noneSourceUrlChildren, ...(await parseChildren(dynamicConfig, { dynamic: true }))];
797
+
798
+ checkDuplicateComponents(newDynamicChildren, newStaticChildren);
799
+
737
800
  const updateList = getUpdateMetaList(
738
- blocklet.children.map((x) => x.meta),
739
- childrenMeta
801
+ [...blocklet.children.map((x) => x.meta), ...oldDynamicChildren.map((x) => x.meta)],
802
+ [...newStaticChildren.map((x) => x.meta), ...newDynamicChildren.map((x) => x.meta)]
740
803
  );
741
804
 
742
805
  if (!updateList.length) {
@@ -746,7 +809,8 @@ class BlockletManager extends BaseBlockletManager {
746
809
  // start session
747
810
  const { id: updateId } = await states.session.start({
748
811
  did,
749
- childrenMeta,
812
+ staticChildren: newStaticChildren,
813
+ dynamicChildren: newDynamicChildren,
750
814
  });
751
815
 
752
816
  return {
@@ -755,8 +819,18 @@ class BlockletManager extends BaseBlockletManager {
755
819
  };
756
820
  }
757
821
 
758
- async updateChildren({ updateId }, context) {
759
- const { did, childrenMeta } = await states.session.end(updateId);
822
+ async updateChildren({ updateId, did: inputDid, children: inputChildren }, context) {
823
+ let did;
824
+ let children;
825
+ if (!updateId && inputDid && inputChildren) {
826
+ did = inputDid;
827
+ children = inputChildren;
828
+ } else {
829
+ const sessionData = await states.session.end(updateId);
830
+ did = sessionData.did;
831
+ const { staticChildren = [], dynamicChildren = [] } = sessionData;
832
+ children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
833
+ }
760
834
 
761
835
  // get old blocklet
762
836
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -769,14 +843,13 @@ class BlockletManager extends BaseBlockletManager {
769
843
  did,
770
844
  name,
771
845
  version,
772
- children: childrenMeta.map((x) => ({ name: x.name, version: x.version })),
846
+ children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
773
847
  });
774
848
 
775
849
  // new blocklet
776
850
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
777
- mergeMeta(meta, childrenMeta);
778
- newBlocklet.meta = meta;
779
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
851
+
852
+ newBlocklet.children = children;
780
853
  await validateBlocklet(newBlocklet);
781
854
 
782
855
  this.emit(BlockletEvents.statusChange, newBlocklet);
@@ -849,19 +922,19 @@ class BlockletManager extends BaseBlockletManager {
849
922
  logger.error('failed to delete blocklet process for dev', { error: err });
850
923
  }
851
924
 
852
- const childrenMeta = await getChildrenMeta(meta);
853
- mergeMeta(meta, childrenMeta);
925
+ const children = await this._getChildren(meta);
854
926
  const blocklet = await states.blocklet.addBlocklet({
855
927
  did,
856
928
  meta,
857
929
  source: BlockletSource.local,
858
930
  deployedFrom: folder,
859
931
  mode: BLOCKLET_MODES.DEVELOPMENT,
860
- childrenMeta,
932
+ children,
861
933
  });
862
934
  logger.info('add blocklet for dev', { did, version, meta });
863
935
 
864
- await this._downloadBlocklet(blocklet);
936
+ const oldBlocklet = { children: children.filter((x) => x.dynamic) }; // let downloader skip re-downloading dynamic blocklet
937
+ await this._downloadBlocklet(blocklet, oldBlocklet);
865
938
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
866
939
 
867
940
  // Add environments
@@ -1004,7 +1077,8 @@ class BlockletManager extends BaseBlockletManager {
1004
1077
  blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
1005
1078
 
1006
1079
  try {
1007
- const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1080
+ const app = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1081
+ const { appId } = app;
1008
1082
  blocklet.runtimeInfo = await getRuntimeInfo(appId);
1009
1083
  if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
1010
1084
  blocklet.status = statusMap[blocklet.runtimeInfo.status];
@@ -1278,7 +1352,7 @@ class BlockletManager extends BaseBlockletManager {
1278
1352
  }
1279
1353
  }
1280
1354
 
1281
- async _installFromRegistry({ did, registry }, context) {
1355
+ async _installFromStore({ did, registry }, context) {
1282
1356
  logger.debug('start install blocklet', { did });
1283
1357
  if (!isValidDid(did)) {
1284
1358
  throw new Error('Blocklet did is invalid');
@@ -1340,9 +1414,84 @@ class BlockletManager extends BaseBlockletManager {
1340
1414
  });
1341
1415
  }
1342
1416
 
1343
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1344
- logger.info('install blocklet', { from: 'upload file' });
1345
- // download
1417
+ async _installComponentFromUrl({ rootDid, mountPoint, url, context }) {
1418
+ const blocklet = await states.blocklet.getBlocklet(rootDid);
1419
+ if (!blocklet) {
1420
+ throw new Error('Root blocklet does not exist');
1421
+ }
1422
+
1423
+ const meta = await getBlockletMetaFromUrl(url);
1424
+
1425
+ if (meta.did === rootDid) {
1426
+ throw new Error('Cannot add self as a component');
1427
+ }
1428
+
1429
+ const newChildren = await parseChildren(blocklet.meta, {
1430
+ children: [
1431
+ {
1432
+ meta,
1433
+ mountPoint,
1434
+ sourceUrl: url,
1435
+ },
1436
+ ],
1437
+ dynamic: true,
1438
+ });
1439
+
1440
+ checkDuplicateComponents(blocklet.children, newChildren);
1441
+
1442
+ return this.updateChildren(
1443
+ {
1444
+ did: rootDid,
1445
+ children: [...blocklet.children, ...newChildren],
1446
+ },
1447
+ context
1448
+ );
1449
+ }
1450
+
1451
+ async _installFromCreate({ title, description }, context = {}) {
1452
+ logger.debug('create blocklet', { title, description });
1453
+
1454
+ await joi.string().label('title').max(20).required().validateAsync(title);
1455
+ await joi.string().label('description').max(80).required().validateAsync(description);
1456
+
1457
+ const { did, name } = await this._findNextCustomBlockletName();
1458
+ const rawMeta = {
1459
+ name,
1460
+ did,
1461
+ title,
1462
+ description,
1463
+ version: BLOCKLET_DEFAULT_VERSION,
1464
+ group: BlockletGroup.gateway,
1465
+ interfaces: [
1466
+ {
1467
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1468
+ name: BLOCKLET_INTERFACE_PUBLIC,
1469
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1470
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1471
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1472
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
1473
+ },
1474
+ ],
1475
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1476
+ };
1477
+ const meta = validateMeta(rawMeta);
1478
+
1479
+ await states.blocklet.addBlocklet({
1480
+ did: meta.did,
1481
+ meta,
1482
+ source: BlockletSource.custom,
1483
+ });
1484
+ await this._setConfigs(did);
1485
+
1486
+ // fake install bundle
1487
+ const bundleDir = getBundleDir(this.installDir, meta);
1488
+ fs.mkdirSync(bundleDir, { recursive: true });
1489
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1490
+
1491
+ return this._installBlocklet({ did, context });
1492
+ }
1493
+
1494
+ async _downloadFromUpload(file) {
1346
1495
  // const { filename, mimetype, encoding, createReadStream } = await file;
1347
1496
  const { filename, createReadStream } = await file;
1348
1497
  const cwd = path.join(this.dataDirs.tmp, 'download');
@@ -1366,6 +1515,13 @@ class BlockletManager extends BaseBlockletManager {
1366
1515
  writeStream.on('finish', resolve);
1367
1516
  });
1368
1517
 
1518
+ return { cwd, tarFile };
1519
+ }
1520
+
1521
+ async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1522
+ logger.info('install blocklet', { from: 'upload file' });
1523
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1524
+
1369
1525
  // diff deploy
1370
1526
  if (did && diffVersion) {
1371
1527
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -1386,14 +1542,12 @@ class BlockletManager extends BaseBlockletManager {
1386
1542
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1387
1543
  }
1388
1544
 
1389
- const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
1390
- const childrenMeta = await getChildrenMeta(meta);
1391
- mergeMeta(meta, childrenMeta);
1545
+ const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet.meta);
1392
1546
  const newBlocklet = await states.blocklet.getBlocklet(did);
1393
1547
  newBlocklet.meta = meta;
1394
1548
  newBlocklet.source = BlockletSource.upload;
1395
1549
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1396
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1550
+ newBlocklet.children = await this._getChildren(meta);
1397
1551
  await validateBlocklet(newBlocklet);
1398
1552
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
1399
1553
 
@@ -1408,19 +1562,18 @@ class BlockletManager extends BaseBlockletManager {
1408
1562
  const { meta } = await this._resolveDownload(cwd, tarFile);
1409
1563
  const oldBlocklet = await states.blocklet.getBlocklet(meta.did);
1410
1564
 
1565
+ // full deploy - upgrade
1411
1566
  if (oldBlocklet) {
1412
1567
  if (isInProgress(oldBlocklet.status)) {
1413
1568
  logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1414
1569
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1415
1570
  }
1416
1571
 
1417
- const childrenMeta = await getChildrenMeta(meta);
1418
- mergeMeta(meta, childrenMeta);
1419
1572
  const newBlocklet = await states.blocklet.getBlocklet(meta.did);
1420
1573
  newBlocklet.meta = meta;
1421
1574
  newBlocklet.source = BlockletSource.upload;
1422
1575
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1423
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1576
+ newBlocklet.children = await this._getChildren(meta);
1424
1577
 
1425
1578
  await validateBlocklet(newBlocklet);
1426
1579
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
@@ -1432,14 +1585,14 @@ class BlockletManager extends BaseBlockletManager {
1432
1585
  });
1433
1586
  }
1434
1587
 
1435
- const childrenMeta = await getChildrenMeta(meta);
1436
- mergeMeta(meta, childrenMeta);
1588
+ // full deploy - install
1589
+ const children = await this._getChildren(meta);
1437
1590
  const blocklet = await states.blocklet.addBlocklet({
1438
1591
  did: meta.did,
1439
1592
  meta,
1440
1593
  source: BlockletSource.upload,
1441
1594
  deployedFrom: `Upload by ${context.user.did}`,
1442
- childrenMeta,
1595
+ children,
1443
1596
  });
1444
1597
 
1445
1598
  await validateBlocklet(blocklet);
@@ -1447,13 +1600,92 @@ class BlockletManager extends BaseBlockletManager {
1447
1600
  await this._setConfigs(meta.did);
1448
1601
 
1449
1602
  // download
1450
- await this._downloadBlocklet(blocklet);
1603
+ await this._downloadBlocklet(
1604
+ blocklet,
1605
+ // let downloader skip re-downloading dynamic blocklet
1606
+ { children: children.filter((x) => x.dynamic) }
1607
+ );
1451
1608
  return this._installBlocklet({
1452
1609
  did: meta.did,
1453
1610
  context,
1454
1611
  });
1455
1612
  }
1456
1613
 
1614
+ async _installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context }) {
1615
+ logger.info('install blocklet', { from: 'upload file' });
1616
+ // download
1617
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1618
+
1619
+ const oldBlocklet = await states.blocklet.getBlocklet(rootDid);
1620
+ if (!oldBlocklet) {
1621
+ throw new Error('Root blocklet does not exist');
1622
+ }
1623
+
1624
+ if (isInProgress(oldBlocklet.status)) {
1625
+ logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1626
+ throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1627
+ }
1628
+
1629
+ let meta;
1630
+ // diff upload
1631
+ if (did && diffVersion) {
1632
+ const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
1633
+ if (!oldChild) {
1634
+ throw new Error(`Blocklet ${did} not found when diff deploying`);
1635
+ }
1636
+ if (oldChild.meta.version !== diffVersion) {
1637
+ logger.error('Diff deploy: Blocklet version changed', {
1638
+ preVersion: diffVersion,
1639
+ changedVersion: oldChild.meta.version,
1640
+ name: oldChild.meta.name,
1641
+ did: oldChild.meta.did,
1642
+ });
1643
+ throw new Error('Blocklet version changed when diff deploying');
1644
+ }
1645
+
1646
+ meta = (await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldChild.meta)).meta;
1647
+ } else {
1648
+ // full deploy
1649
+ meta = (await this._resolveDownload(cwd, tarFile)).meta;
1650
+ }
1651
+
1652
+ if (meta.did === rootDid) {
1653
+ // should not be here
1654
+ throw new Error('Cannot add self as a component');
1655
+ }
1656
+
1657
+ const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1658
+ const dynamicChildren = await this._getDynamicChildrenFromSettings(rootDid);
1659
+
1660
+ const newChild = {
1661
+ meta,
1662
+ mountPoint,
1663
+ source: BlockletSource.upload,
1664
+ deployedFrom: `Upload by ${context.user.did}`,
1665
+ sourceUrl: '',
1666
+ dynamic: true,
1667
+ };
1668
+ const index = dynamicChildren.findIndex((child) => child.meta.did === meta.did);
1669
+ if (index >= 0) {
1670
+ dynamicChildren.splice(index, 1, newChild);
1671
+ } else {
1672
+ dynamicChildren.push(newChild);
1673
+ }
1674
+
1675
+ const staticChildren = newBlocklet.children.filter((x) => !x.dynamic);
1676
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1677
+
1678
+ newBlocklet.children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
1679
+
1680
+ await validateBlocklet(newBlocklet);
1681
+
1682
+ return this._upgradeBlocklet({
1683
+ oldBlocklet,
1684
+ newBlocklet,
1685
+ context,
1686
+ });
1687
+ }
1688
+
1457
1689
  /**
1458
1690
  * add to download job queue
1459
1691
  * @param {string} did blocklet did
@@ -1488,7 +1720,14 @@ class BlockletManager extends BaseBlockletManager {
1488
1720
 
1489
1721
  async prune() {
1490
1722
  const blocklets = await states.blocklet.getBlocklets();
1491
- await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
1723
+ const settings = await states.blockletExtras.listSettings();
1724
+ await pruneBlockletBundle({
1725
+ installDir: this.dataDirs.blocklets,
1726
+ blocklets,
1727
+ blockletSettings: settings
1728
+ .filter((x) => x.settings.children && x.settings.children.length)
1729
+ .map((x) => x.settings),
1730
+ });
1492
1731
  }
1493
1732
 
1494
1733
  async getLatestBlockletVersion({ did, version }) {
@@ -1576,20 +1815,35 @@ class BlockletManager extends BaseBlockletManager {
1576
1815
  blocklets.forEach(run);
1577
1816
  }
1578
1817
 
1818
+ async _getChildren(meta) {
1819
+ const staticChildren = await parseChildren(meta);
1820
+ const dynamicChildren = await parseChildren(meta, {
1821
+ children: await states.blockletExtras.getSettings(meta.did, 'children', []),
1822
+ dynamic: true,
1823
+ });
1824
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1825
+
1826
+ return [...staticChildren, ...dynamicChildren];
1827
+ }
1828
+
1829
+ async _getDynamicChildrenFromSettings(did) {
1830
+ const children = await states.blockletExtras.getSettings(did, 'children', []);
1831
+ return children.map((x) => ({ ...x, dynamic: true }));
1832
+ }
1833
+
1579
1834
  async _install({ meta, source, deployedFrom, context, sync }) {
1580
1835
  validateBlockletMeta(meta, { ensureDist: true });
1581
1836
 
1582
1837
  const { name, did, version } = meta;
1583
1838
 
1584
- const childrenMeta = await getChildrenMeta(meta);
1585
- mergeMeta(meta, childrenMeta);
1839
+ const children = await this._getChildren(meta);
1586
1840
  try {
1587
1841
  const blocklet = await states.blocklet.addBlocklet({
1588
1842
  did: meta.did,
1589
1843
  meta,
1590
1844
  source,
1591
1845
  deployedFrom,
1592
- childrenMeta,
1846
+ children,
1593
1847
  });
1594
1848
 
1595
1849
  await validateBlocklet(blocklet);
@@ -1604,6 +1858,7 @@ class BlockletManager extends BaseBlockletManager {
1604
1858
  // download
1605
1859
  const downloadParams = {
1606
1860
  blocklet: { ...blocklet1 },
1861
+ oldBlocklet: { children: children.filter((x) => x.dynamic) }, // let downloader skip re-downloading dynamic blocklet
1607
1862
  context,
1608
1863
  postAction: 'install',
1609
1864
  };
@@ -1662,7 +1917,7 @@ class BlockletManager extends BaseBlockletManager {
1662
1917
  }
1663
1918
 
1664
1919
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
1665
- validateBlockletMeta(meta, { ensureDist: true });
1920
+ validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
1666
1921
 
1667
1922
  const { name, version, did } = meta;
1668
1923
 
@@ -1673,12 +1928,11 @@ class BlockletManager extends BaseBlockletManager {
1673
1928
  logger.info(`${action} blocklet`, { did, version });
1674
1929
 
1675
1930
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1676
- const childrenMeta = await getChildrenMeta(meta);
1677
- mergeMeta(meta, childrenMeta);
1931
+
1678
1932
  newBlocklet.meta = meta;
1679
1933
  newBlocklet.source = source;
1680
1934
  newBlocklet.deployedFrom = deployedFrom;
1681
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1935
+ newBlocklet.children = await this._getChildren(meta);
1682
1936
 
1683
1937
  await validateBlocklet(newBlocklet);
1684
1938
 
@@ -1781,7 +2035,7 @@ class BlockletManager extends BaseBlockletManager {
1781
2035
 
1782
2036
  await ensureBlockletExpanded(meta, downloadDir);
1783
2037
 
1784
- installDir = path.join(this.installDir, name, version);
2038
+ installDir = getBundleDir(this.installDir, meta);
1785
2039
  if (fs.existsSync(installDir)) {
1786
2040
  fs.removeSync(installDir);
1787
2041
  logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
@@ -1798,7 +2052,7 @@ class BlockletManager extends BaseBlockletManager {
1798
2052
  return { meta, installDir };
1799
2053
  }
1800
2054
 
1801
- async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
2055
+ async _resolveDiffDownload(cwd, tarFile, deleteSet, bundle) {
1802
2056
  logger.info('Resolve diff download', { tarFile, cwd });
1803
2057
  const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
1804
2058
  const diffDir = `${downloadDir}-diff`;
@@ -1811,7 +2065,7 @@ class BlockletManager extends BaseBlockletManager {
1811
2065
  throw error;
1812
2066
  }
1813
2067
  logger.info('Copy installDir to downloadDir', { installDir: this.installDir, downloadDir });
1814
- await fs.copy(path.join(this.installDir, blocklet.meta.name, blocklet.meta.version), downloadDir);
2068
+ await fs.copy(getBundleDir(this.installDir, bundle), downloadDir);
1815
2069
  try {
1816
2070
  // delete
1817
2071
  logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
@@ -1840,11 +2094,11 @@ class BlockletManager extends BaseBlockletManager {
1840
2094
 
1841
2095
  await ensureBlockletExpanded(meta, downloadDir);
1842
2096
 
1843
- const installDir = path.join(this.installDir, meta.name, meta.version);
1844
- logger.info('Move downloadDir to installDir', { downloadDir, installDir });
1845
- await fs.move(downloadDir, installDir, { overwrite: true });
2097
+ const bundleDir = getBundleDir(this.installDir, meta);
2098
+ logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
2099
+ await fs.move(downloadDir, bundleDir, { overwrite: true });
1846
2100
 
1847
- return { meta, installDir };
2101
+ return { meta, installDir: bundleDir };
1848
2102
  } catch (error) {
1849
2103
  fs.removeSync(downloadDir);
1850
2104
  fs.removeSync(diffDir);
@@ -1875,7 +2129,7 @@ class BlockletManager extends BaseBlockletManager {
1875
2129
  hooks.preInstall({
1876
2130
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1877
2131
  env: { ...nodeEnvironments },
1878
- appDir: blocklet.env.appDir,
2132
+ appDir: b.env.appDir,
1879
2133
  did, // root blocklet did
1880
2134
  notification: states.notification,
1881
2135
  context,
@@ -1891,7 +2145,7 @@ class BlockletManager extends BaseBlockletManager {
1891
2145
  hooks.postInstall({
1892
2146
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1893
2147
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1894
- appDir: blocklet.env.appDir,
2148
+ appDir: b.env.appDir,
1895
2149
  did, // root blocklet did
1896
2150
  notification: states.notification,
1897
2151
  context,
@@ -1975,7 +2229,7 @@ class BlockletManager extends BaseBlockletManager {
1975
2229
  hooks.preInstall({
1976
2230
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1977
2231
  env: { ...nodeEnvironments },
1978
- appDir: blocklet.env.appDir,
2232
+ appDir: b.env.appDir,
1979
2233
  did, // root blocklet did
1980
2234
  notification: states.notification,
1981
2235
  context,
@@ -2060,6 +2314,12 @@ class BlockletManager extends BaseBlockletManager {
2060
2314
  logger.error('emit upgrade notification failed', { name, version, error });
2061
2315
  }
2062
2316
 
2317
+ // Update dynamic component meta in blocklet settings
2318
+ const dynamicChildren = blocklet.children
2319
+ .filter((x) => x.dynamic)
2320
+ .map((x) => pick(x, ['meta', 'mountPoint', 'sourceUrl', 'source']));
2321
+ await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2322
+
2063
2323
  return blocklet;
2064
2324
  } catch (err) {
2065
2325
  const b = await this._rollback(action, did, oldBlocklet);
@@ -2266,12 +2526,18 @@ class BlockletManager extends BaseBlockletManager {
2266
2526
 
2267
2527
  for (const child of blocklet.children) {
2268
2528
  const oldChild = oldChildren[child.meta.did];
2269
- if (!oldChild || oldChild.meta.dist.integrity !== child.meta.dist.integrity) {
2529
+ if (
2530
+ !oldChild ||
2531
+ (![BlockletSource.upload, BlockletSource.local].includes(child.source) &&
2532
+ child.sourceUrl &&
2533
+ get(oldChild, 'meta.dist.integrity') !== get(child, 'meta.dist.integrity'))
2534
+ ) {
2270
2535
  metas.push(child.meta);
2271
2536
  }
2272
2537
  }
2273
2538
 
2274
2539
  try {
2540
+ logger.info('Download Blocklet', { name, did, bundles: metas.map((x) => get(x, 'dist.tarball')) });
2275
2541
  const tasks = [];
2276
2542
  for (const meta of metas) {
2277
2543
  const url = await this.registry.resolveTarballURL({
@@ -2413,6 +2679,20 @@ class BlockletManager extends BaseBlockletManager {
2413
2679
  await states.blockletExtras.delChildConfigs(did, child.meta.did);
2414
2680
  }
2415
2681
  }
2682
+
2683
+ async _findNextCustomBlockletName(leftTimes = 10) {
2684
+ if (leftTimes <= 0) {
2685
+ throw new Error('Generate custom blocklet did too many times');
2686
+ }
2687
+ const number = await states.node.increaseCustomBlockletNumber();
2688
+ const name = `custom-${number}`;
2689
+ const did = toBlockletDid(name);
2690
+ const blocklet = await states.blocklet.getBlocklet(did);
2691
+ if (blocklet) {
2692
+ return this._findNextCustomBlockletName(leftTimes - 1);
2693
+ }
2694
+ return { did, name };
2695
+ }
2416
2696
  }
2417
2697
 
2418
2698
  module.exports = BlockletManager;