@abtnode/core 1.6.13 → 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.
- package/lib/blocklet/manager/disk.js +397 -99
- package/lib/event.js +1 -1
- package/lib/index.js +3 -10
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/router/helper.js +27 -13
- 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 +18 -2
- package/lib/util/blocklet.js +276 -68
- package/lib/util/default-node-config.js +1 -1
- 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/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,
|
|
@@ -74,17 +79,19 @@ const PRIVATE_NODE_ENVS = [
|
|
|
74
79
|
// 'NEDB_MULTI_PORT', // FIXME: 排查 abtnode 对外提供的 SDK(比如 @abtnode/queue), SDK 中不要自动使用 NEDB_MULTI_PORT 环境变量
|
|
75
80
|
'ABT_NODE_UPDATER_PORT',
|
|
76
81
|
'ABT_NODE_SESSION_TTL',
|
|
77
|
-
'ABT_NODE_MODE',
|
|
78
82
|
'ABT_NODE_ROUTER_PROVIDER',
|
|
79
83
|
'ABT_NODE_DATA_DIR',
|
|
80
84
|
'ABT_NODE_TOKEN_SECRET',
|
|
81
85
|
'ABT_NODE_SK',
|
|
82
86
|
'ABT_NODE_SESSION_SECRET',
|
|
83
|
-
'ABT_NODE_NAME',
|
|
84
|
-
'ABT_NODE_DESCRIPTION',
|
|
85
87
|
'ABT_NODE_BASE_URL',
|
|
86
88
|
'ABT_NODE_LOG_LEVEL',
|
|
87
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',
|
|
88
95
|
];
|
|
89
96
|
|
|
90
97
|
/**
|
|
@@ -115,19 +122,9 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
115
122
|
logsDir = path.join(dataDirs.logs, rootBlocklet.meta.name);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
if (ensure) {
|
|
119
|
-
try {
|
|
120
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
121
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
122
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
123
|
-
} catch (err) {
|
|
124
|
-
logger.error('make blocklet dir failed', { error: err });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
125
|
// get app dirs
|
|
129
126
|
|
|
130
|
-
const {
|
|
127
|
+
const { main, group } = blocklet.meta;
|
|
131
128
|
|
|
132
129
|
const startFromDevEntry =
|
|
133
130
|
blocklet.mode === BLOCKLET_MODES.DEVELOPMENT && blocklet.meta.scripts && blocklet.meta.scripts.dev;
|
|
@@ -142,13 +139,24 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
142
139
|
if (blocklet.source === BlockletSource.local) {
|
|
143
140
|
appDir = blocklet.deployedFrom;
|
|
144
141
|
} else {
|
|
145
|
-
appDir =
|
|
142
|
+
appDir = getBundleDir(dataDirs.blocklets, blocklet.meta);
|
|
146
143
|
}
|
|
147
144
|
|
|
148
145
|
if (!appDir) {
|
|
149
146
|
throw new Error('Can not determine blocklet directory, maybe invalid deployment from local blocklets');
|
|
150
147
|
}
|
|
151
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
|
+
|
|
152
160
|
mainDir = appDir;
|
|
153
161
|
|
|
154
162
|
if (!startFromDevEntry && !isBeforeInstalled(rootBlocklet.status)) {
|
|
@@ -243,12 +251,16 @@ const getRootSystemEnvironments = (blocklet, nodeInfo) => {
|
|
|
243
251
|
const appName = title || name || result.name;
|
|
244
252
|
const appDescription = description || result.description;
|
|
245
253
|
|
|
254
|
+
// FIXME: we should use https here when possible, eg, when did-gateway is available
|
|
255
|
+
const appUrl = `http://${getDidDomainForBlocklet({ appId, didDomain: nodeInfo.didDomain })}`;
|
|
256
|
+
|
|
246
257
|
return {
|
|
247
258
|
BLOCKLET_DID: did,
|
|
248
259
|
BLOCKLET_APP_SK: appSk,
|
|
249
260
|
BLOCKLET_APP_ID: appId,
|
|
250
261
|
BLOCKLET_APP_NAME: appName,
|
|
251
262
|
BLOCKLET_APP_DESCRIPTION: appDescription,
|
|
263
|
+
BLOCKLET_APP_URL: appUrl,
|
|
252
264
|
};
|
|
253
265
|
};
|
|
254
266
|
|
|
@@ -428,11 +440,13 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
428
440
|
const options = {
|
|
429
441
|
namespace: 'blocklets',
|
|
430
442
|
name: appId,
|
|
431
|
-
|
|
443
|
+
cwd: appCwd,
|
|
432
444
|
time: true,
|
|
433
445
|
output: path.join(logsDir, 'output.log'),
|
|
434
446
|
error: path.join(logsDir, 'error.log'),
|
|
435
|
-
|
|
447
|
+
wait_ready: process.env.NODE_ENV !== 'test',
|
|
448
|
+
listen_timeout: 5000,
|
|
449
|
+
max_memory_restart: `${maxMemoryRestart}M`,
|
|
436
450
|
max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
|
|
437
451
|
env: {
|
|
438
452
|
...env,
|
|
@@ -460,17 +474,17 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
460
474
|
const engine = getEngine(blockletEngineInfo.interpreter);
|
|
461
475
|
options.interpreter = engine.interpreter === 'node' ? '' : engine.interpreter;
|
|
462
476
|
options.interpreterArgs = engine.args || '';
|
|
463
|
-
|
|
464
477
|
options.script = blockletEngineInfo.script || appMain;
|
|
465
|
-
|
|
466
|
-
logger.debug('start.blocklet.engine.info', { blockletEngineInfo });
|
|
467
|
-
logger.debug('start.blocklet.max_memory_restart', { maxMemoryRestart });
|
|
468
478
|
}
|
|
469
479
|
|
|
470
480
|
await pm2.startAsync(options);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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 });
|
|
474
488
|
},
|
|
475
489
|
{ parallel: true }
|
|
476
490
|
);
|
|
@@ -533,6 +547,10 @@ const getBlockletStatusFromProcess = async (blocklet) => {
|
|
|
533
547
|
|
|
534
548
|
const list = await Promise.all(tasks);
|
|
535
549
|
|
|
550
|
+
if (!list.length) {
|
|
551
|
+
return blocklet.status;
|
|
552
|
+
}
|
|
553
|
+
|
|
536
554
|
return getRootBlockletStatus(list);
|
|
537
555
|
};
|
|
538
556
|
|
|
@@ -611,26 +629,76 @@ const reloadProcess = (appId) =>
|
|
|
611
629
|
});
|
|
612
630
|
});
|
|
613
631
|
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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}`);
|
|
630
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
|
+
});
|
|
631
697
|
}
|
|
632
698
|
|
|
633
|
-
|
|
699
|
+
mergeMeta(configs, results);
|
|
700
|
+
|
|
701
|
+
return results;
|
|
634
702
|
};
|
|
635
703
|
|
|
636
704
|
const validateBlocklet = (blocklet) =>
|
|
@@ -728,7 +796,7 @@ const verifyIntegrity = async ({ file, integrity: expected }) => {
|
|
|
728
796
|
return true;
|
|
729
797
|
};
|
|
730
798
|
|
|
731
|
-
const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
799
|
+
const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings }) => {
|
|
732
800
|
for (const blocklet of blocklets) {
|
|
733
801
|
if (
|
|
734
802
|
[
|
|
@@ -746,53 +814,104 @@ const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
|
746
814
|
}
|
|
747
815
|
}
|
|
748
816
|
|
|
749
|
-
// blockletMap: { <name/version>: true }
|
|
750
|
-
const blockletMap =
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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;
|
|
754
823
|
}
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
757
830
|
|
|
758
|
-
// appDirs: [{ key: <name/version>, dir: appDir }]
|
|
831
|
+
// appDirs: [{ key: <[scope/]name/version>, dir: appDir }]
|
|
759
832
|
const appDirs = [];
|
|
760
833
|
|
|
761
834
|
// fill appDirs
|
|
762
835
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
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
|
+
|
|
765
860
|
appDirs.push({
|
|
766
861
|
key: path.relative(installDir, dir),
|
|
767
862
|
dir,
|
|
768
863
|
});
|
|
864
|
+
|
|
769
865
|
return;
|
|
770
866
|
}
|
|
867
|
+
|
|
771
868
|
const nextDirs = [];
|
|
772
|
-
for (const x of await
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
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;
|
|
778
874
|
}
|
|
779
|
-
nextDirs.push(
|
|
875
|
+
nextDirs.push(x);
|
|
780
876
|
}
|
|
781
877
|
|
|
782
878
|
for (const x of nextDirs) {
|
|
783
|
-
await fillAppDirs(x);
|
|
879
|
+
await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
|
|
784
880
|
}
|
|
785
881
|
};
|
|
786
|
-
await fillAppDirs(installDir);
|
|
882
|
+
await fillAppDirs(installDir, 'root');
|
|
787
883
|
} catch (error) {
|
|
788
884
|
logger.error('fill app dirs failed', { error });
|
|
789
885
|
}
|
|
790
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
|
+
|
|
791
909
|
// remove trash
|
|
792
910
|
for (const app of appDirs) {
|
|
793
911
|
if (!blockletMap[app.key]) {
|
|
794
|
-
logger.info('Remove
|
|
912
|
+
logger.info('Remove app folder', { dir: app.dir });
|
|
795
913
|
await fs.remove(app.dir);
|
|
914
|
+
await ensureBundleDirRemoved(app.dir);
|
|
796
915
|
}
|
|
797
916
|
}
|
|
798
917
|
|
|
@@ -830,15 +949,25 @@ const getRuntimeInfo = async (appId) => {
|
|
|
830
949
|
};
|
|
831
950
|
};
|
|
832
951
|
|
|
833
|
-
|
|
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 = []) => {
|
|
834
962
|
// configMap
|
|
835
963
|
const configMap = {};
|
|
836
|
-
(
|
|
964
|
+
(Array.isArray(source) ? source : source.children || []).forEach((x) => {
|
|
837
965
|
configMap[x.name] = x;
|
|
838
966
|
});
|
|
839
967
|
|
|
840
968
|
// merge service from config to child meta
|
|
841
|
-
childrenMeta.forEach((
|
|
969
|
+
childrenMeta.forEach((child) => {
|
|
970
|
+
const childMeta = child.meta || child;
|
|
842
971
|
const config = configMap[childMeta.name];
|
|
843
972
|
if (!config) {
|
|
844
973
|
return;
|
|
@@ -898,10 +1027,84 @@ const getUpdateMetaList = (oldMetas = [], newMetas = []) => {
|
|
|
898
1027
|
return newMetas.filter(({ version, did }) => did && version !== oldMap[did]);
|
|
899
1028
|
};
|
|
900
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
|
+
|
|
901
1104
|
module.exports = {
|
|
902
1105
|
forEachBlocklet,
|
|
903
1106
|
getBlockletMetaFromUrl,
|
|
904
|
-
|
|
1107
|
+
parseChildren,
|
|
905
1108
|
getBlockletDirs,
|
|
906
1109
|
getRootSystemEnvironments,
|
|
907
1110
|
getSystemEnvironments,
|
|
@@ -927,4 +1130,9 @@ module.exports = {
|
|
|
927
1130
|
mergeMeta,
|
|
928
1131
|
fixAndVerifyBlockletMeta,
|
|
929
1132
|
getUpdateMetaList,
|
|
1133
|
+
getSourceFromInstallParams,
|
|
1134
|
+
findWebInterface,
|
|
1135
|
+
checkDuplicateComponents,
|
|
1136
|
+
getDiffFiles,
|
|
1137
|
+
getBundleDir,
|
|
930
1138
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
|
|
2
|
+
|
|
3
|
+
const getBlockletDomainGroupName = (did) => `${did}${BLOCKLET_SITE_GROUP_SUFFIX}`;
|
|
4
|
+
|
|
5
|
+
const getDidFromDomainGroupName = (name) => {
|
|
6
|
+
const did = name.replace(BLOCKLET_SITE_GROUP_SUFFIX, '');
|
|
7
|
+
return did;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
getBlockletDomainGroupName,
|
|
12
|
+
getDidFromDomainGroupName,
|
|
13
|
+
};
|
package/lib/validators/node.js
CHANGED
|
@@ -9,14 +9,23 @@ const nodeInfoSchema = Joi.object({
|
|
|
9
9
|
description: Joi.string()
|
|
10
10
|
.required()
|
|
11
11
|
.messages({ zh: { 'string.empty': '描述不能为空' }, en: { 'string.empty': 'Description cannot be empty' } }),
|
|
12
|
+
registerUrl: Joi.string()
|
|
13
|
+
.uri({ scheme: [/https?/] })
|
|
14
|
+
.label('register url')
|
|
15
|
+
.allow('')
|
|
16
|
+
.optional()
|
|
17
|
+
.messages({
|
|
18
|
+
zh: { 'string.uriCustomScheme': '应用启动器必须是合法的 URL' },
|
|
19
|
+
en: { 'string.uriCustomScheme': 'Blocklet Launcher must be a valid URL' },
|
|
20
|
+
}),
|
|
12
21
|
webWalletUrl: Joi.string()
|
|
13
22
|
.uri({ scheme: [/https?/] })
|
|
14
23
|
.label('web wallet url')
|
|
15
24
|
.allow('')
|
|
16
25
|
.optional()
|
|
17
26
|
.messages({
|
|
18
|
-
zh: { 'string.uriCustomScheme': 'Web
|
|
19
|
-
en: { 'string.uriCustomScheme': 'Web
|
|
27
|
+
zh: { 'string.uriCustomScheme': 'Web Wallet 必须是合法的 URL' },
|
|
28
|
+
en: { 'string.uriCustomScheme': 'Web Wallet must be a valid URL' },
|
|
20
29
|
}),
|
|
21
30
|
autoUpgrade: Joi.boolean(),
|
|
22
31
|
enableWelcomePage: Joi.boolean(),
|
|
@@ -31,16 +40,6 @@ const nodeInfoSchema = Joi.object({
|
|
|
31
40
|
'number.max': 'Disk usage alert threshold cannot be higher than 99%',
|
|
32
41
|
},
|
|
33
42
|
}),
|
|
34
|
-
// removed in 1.5.1
|
|
35
|
-
registerUrl: Joi.string()
|
|
36
|
-
.uri({ scheme: [/https?/] })
|
|
37
|
-
.label('register url')
|
|
38
|
-
.allow('')
|
|
39
|
-
.optional()
|
|
40
|
-
.messages({
|
|
41
|
-
zh: { 'string.uriCustomScheme': '注册地址必须是合法的 URL' },
|
|
42
|
-
en: { 'string.uriCustomScheme': 'Registry URL must be a valid URL' },
|
|
43
|
-
}),
|
|
44
43
|
}).options({ stripUnknown: true });
|
|
45
44
|
|
|
46
45
|
module.exports = {
|
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
const JOI = require('joi');
|
|
3
3
|
const { getMultipleLangParams } = require('./util');
|
|
4
4
|
|
|
5
|
-
const nameSchema = JOI.string()
|
|
5
|
+
const nameSchema = JOI.string()
|
|
6
|
+
.trim()
|
|
7
|
+
.max(64)
|
|
8
|
+
.custom((name) => {
|
|
9
|
+
const arr = name.split('_');
|
|
10
|
+
const formatTip = 'The format of permission name should be "{action}_{resource}", e.g. query_data';
|
|
11
|
+
if (arr.length > 2) {
|
|
12
|
+
throw new Error(`Too much "_". ${formatTip}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (arr.length < 2) {
|
|
16
|
+
throw new Error(formatTip);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return name;
|
|
20
|
+
});
|
|
6
21
|
const descriptionSchema = JOI.string().trim().max(600);
|
|
7
22
|
|
|
8
23
|
const createPermissionSchema = JOI.object({
|