@abtnode/core 1.8.69-beta-b0bb2d67 → 1.8.69-beta-76f8a46f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/team.js +2 -2
- package/lib/blocklet/downloader/resolve-download.js +31 -0
- package/lib/blocklet/manager/base.js +8 -16
- package/lib/blocklet/manager/disk.js +367 -1659
- package/lib/blocklet/manager/helper/{install-from-backup.js → install-application-from-backup.js} +26 -19
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +80 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +377 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/storage/backup/spaces.js +14 -14
- package/lib/index.js +7 -7
- package/lib/router/helper.js +5 -7
- package/lib/states/blocklet-extras.js +44 -0
- package/lib/states/blocklet.js +56 -4
- package/lib/states/node.js +1 -0
- package/lib/states/site.js +15 -6
- package/lib/team/manager.js +5 -0
- package/lib/util/blocklet.js +177 -132
- package/lib/util/get-domain-for-blocklet.js +5 -14
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +0 -5
- package/lib/util/store.js +44 -6
- package/lib/webhook/sender/wallet/index.js +3 -0
- package/package.json +26 -26
package/lib/blocklet/manager/helper/{install-from-backup.js → install-application-from-backup.js}
RENAMED
|
@@ -7,19 +7,23 @@ const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
|
7
7
|
|
|
8
8
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
9
9
|
|
|
10
|
-
const logger = require('@abtnode/logger')('@abtnode/core:install-
|
|
10
|
+
const logger = require('@abtnode/logger')('@abtnode/core:install-app-backup');
|
|
11
11
|
|
|
12
12
|
const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../util/blocklet');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* backup 目录结构
|
|
16
|
+
* /blocklets/<name1>version>
|
|
17
|
+
* /blocklets/<name2>version>
|
|
18
|
+
* /blocklets/<name3>version>
|
|
19
|
+
* /data
|
|
20
|
+
* /blocklet.json
|
|
21
|
+
* /blocklet_extras.json
|
|
22
|
+
* @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
|
|
23
|
+
* @param {{ url: string, appSk: string, moveDir: boolean, manager: import('../disk'), states: import('../../../states/index'), context: Record<string, string> }}
|
|
24
|
+
* @return {Promise<any>}
|
|
21
25
|
*/
|
|
22
|
-
|
|
26
|
+
const installApplicationFromBackup = async ({ url, appSk, moveDir, context = {}, states, manager } = {}) => {
|
|
23
27
|
// TODO: support more url schema feature (http, did-spaces)
|
|
24
28
|
if (!url.startsWith('file://')) {
|
|
25
29
|
throw new Error('url must starts with file://');
|
|
@@ -45,10 +49,19 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
45
49
|
'startedAt',
|
|
46
50
|
]);
|
|
47
51
|
|
|
52
|
+
if (!state.structVersion) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'Only application of structVersion 2 can be restored on this server. Please migrate you application and re-backup your application on your previous server.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { meta } = state;
|
|
59
|
+
const { did, name: appName } = meta;
|
|
60
|
+
|
|
48
61
|
const extra = omit(fs.readJSONSync(path.join(dir, 'blocklet-extras.json')), ['_id', 'createdAt', 'updatedAt']);
|
|
49
62
|
|
|
50
63
|
if (state.meta.did !== extra.did) {
|
|
51
|
-
throw new Error(
|
|
64
|
+
throw new Error(`did does not match in blocklet.json (${state.meta.did}) and blocklet_extra.json ${extra.did}`);
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
forEachBlockletSync(state, (component) => {
|
|
@@ -57,18 +70,13 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
57
70
|
delete component.environments;
|
|
58
71
|
});
|
|
59
72
|
|
|
60
|
-
const { meta } = state;
|
|
61
|
-
|
|
62
73
|
await validateBlocklet({ meta });
|
|
63
74
|
|
|
64
|
-
const { did, name: appName } = meta;
|
|
65
|
-
|
|
66
|
-
// FIXME: meta.did and meta.name should be dynamic created when installing multiple blocklet is supported
|
|
67
75
|
const existState = await states.blocklet.hasBlocklet(did);
|
|
68
76
|
|
|
69
77
|
if (existState) {
|
|
70
78
|
logger.error('blocklet is already exist', { did });
|
|
71
|
-
throw new Error(
|
|
79
|
+
throw new Error(`blocklet is already exist. did: ${did}`);
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
if (appSk) {
|
|
@@ -106,8 +114,6 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
106
114
|
const existExtra = await states.blockletExtras.find({ did });
|
|
107
115
|
if (existExtra) {
|
|
108
116
|
// 如果数据存在, 当前视为脏数据, 直接删除
|
|
109
|
-
// FIXME 另一个人的数据可能被删除. 修复方式: 动态生成 meta.did 或通过 blocklet sk 生成 meta.did
|
|
110
|
-
// FIXME 简单粗暴的删掉数据可能不是理想的方式,需要考虑旧数据存在时, 如何与新数据 merge
|
|
111
117
|
logger.error('old extra state exists and will be force removed', { existExtra });
|
|
112
118
|
await states.blockletExtras.remove({ did });
|
|
113
119
|
}
|
|
@@ -142,7 +148,6 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
142
148
|
// 从 store 下载 blocklet
|
|
143
149
|
await manager.blockletDownloader.download(state, { skipCheckIntegrity: true });
|
|
144
150
|
|
|
145
|
-
// FIXME same as copy extra
|
|
146
151
|
const dataDir = path.join(manager.dataDirs.data, appName);
|
|
147
152
|
if (fs.existsSync(dataDir)) {
|
|
148
153
|
logger.error('old data exists and will be force removed', { dataDir });
|
|
@@ -158,7 +163,7 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
158
163
|
logger.info(`data is ${moveDir ? 'moved' : 'copied'} successfully`);
|
|
159
164
|
}
|
|
160
165
|
} catch (error) {
|
|
161
|
-
logger.error('installFromBackup failed', { error });
|
|
166
|
+
logger.error('installFromBackup failed', { did, error });
|
|
162
167
|
|
|
163
168
|
await manager._rollback('install', did);
|
|
164
169
|
|
|
@@ -168,3 +173,5 @@ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } =
|
|
|
168
173
|
logger.info('start install blocklet', { did });
|
|
169
174
|
return manager._installBlocklet({ did, context });
|
|
170
175
|
};
|
|
176
|
+
|
|
177
|
+
module.exports = { installApplicationFromBackup };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const { toSvg: createDidLogo } =
|
|
5
|
+
process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
|
|
6
|
+
const { BlockletStatus, BLOCKLET_MODES, fromBlockletStatus, BlockletSource } = require('@blocklet/constant');
|
|
7
|
+
|
|
8
|
+
const logger = require('@abtnode/logger')('@abtnode/core:install-app-dev');
|
|
9
|
+
const { ensureMeta } = require('../../../util/blocklet');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {{
|
|
14
|
+
* manager: import('../disk'),
|
|
15
|
+
* states: import('../../../states/index')
|
|
16
|
+
* }}
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
const installApplicationFromDev = async ({ folder, meta, states, manager } = {}) => {
|
|
20
|
+
const { did, version } = meta;
|
|
21
|
+
|
|
22
|
+
const exist = await states.blocklet.getBlocklet(did);
|
|
23
|
+
if (exist) {
|
|
24
|
+
if (exist.mode === BLOCKLET_MODES.PRODUCTION) {
|
|
25
|
+
throw new Error('The blocklet of production mode already exists, please remove it before developing');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const status = fromBlockletStatus(exist.status);
|
|
29
|
+
if (['starting', 'running'].includes(status)) {
|
|
30
|
+
throw new Error(`The blocklet is already on ${status}, please stop it before developing`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
logger.info('remove blocklet for dev', { did, version });
|
|
34
|
+
|
|
35
|
+
await manager.delete({ did, keepLogsDir: false });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// delete process
|
|
39
|
+
try {
|
|
40
|
+
await manager.deleteProcess({ did });
|
|
41
|
+
logger.info('delete blocklet precess for dev', { did, version });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
44
|
+
logger.error('failed to delete blocklet process for dev', { error: err });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// create component
|
|
49
|
+
const component = {
|
|
50
|
+
meta: ensureMeta(meta),
|
|
51
|
+
mountPoint: '/',
|
|
52
|
+
source: BlockletSource.local,
|
|
53
|
+
deployedFrom: folder,
|
|
54
|
+
status: BlockletStatus.installed,
|
|
55
|
+
mode: BLOCKLET_MODES.DEVELOPMENT,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// create app
|
|
59
|
+
const added = await manager._addBlocklet({
|
|
60
|
+
component,
|
|
61
|
+
name: meta.name,
|
|
62
|
+
did: meta.did,
|
|
63
|
+
mode: BLOCKLET_MODES.DEVELOPMENT,
|
|
64
|
+
});
|
|
65
|
+
logger.info('add blocklet for dev', { did, version, meta });
|
|
66
|
+
|
|
67
|
+
await manager._downloadBlocklet(added);
|
|
68
|
+
|
|
69
|
+
// Add Config
|
|
70
|
+
await manager._setConfigsFromMeta(did);
|
|
71
|
+
|
|
72
|
+
// should ensure blocklet integrity
|
|
73
|
+
let blocklet = await manager.ensureBlocklet(did);
|
|
74
|
+
|
|
75
|
+
// pre install
|
|
76
|
+
await manager._runPreInstallHook(blocklet);
|
|
77
|
+
|
|
78
|
+
// Add environments
|
|
79
|
+
await manager._updateBlockletEnvironment(did);
|
|
80
|
+
blocklet = await manager.getBlocklet(did);
|
|
81
|
+
|
|
82
|
+
// post install
|
|
83
|
+
await manager._runPostInstallHook(blocklet);
|
|
84
|
+
|
|
85
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
|
|
86
|
+
|
|
87
|
+
blocklet = await manager.getBlocklet(did);
|
|
88
|
+
|
|
89
|
+
await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
|
|
90
|
+
|
|
91
|
+
return blocklet;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
module.exports = { installApplicationFromDev };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const joi = require('joi');
|
|
2
|
+
|
|
3
|
+
const { BLOCKLET_INSTALL_TYPE, NODE_MODES } = require('@abtnode/constant');
|
|
4
|
+
const { BlockletStatus, BlockletEvents, BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
5
|
+
|
|
6
|
+
const logger = require('@abtnode/logger')('@abtnode/core:install-app-general');
|
|
7
|
+
const getApplicationWallet = require('@blocklet/meta/lib/wallet');
|
|
8
|
+
|
|
9
|
+
const StoreUtil = require('../../../util/store');
|
|
10
|
+
const { getBlockletMetaFromUrl, ensureMeta, validateStore, validateInServerless } = require('../../../util/blocklet');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {{
|
|
15
|
+
* manager: import('../disk'),
|
|
16
|
+
* states: import('../../../states/index')
|
|
17
|
+
* }} param0
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
const installApplicationFromGeneral = async ({
|
|
21
|
+
type,
|
|
22
|
+
appSk,
|
|
23
|
+
sync,
|
|
24
|
+
delay,
|
|
25
|
+
controller,
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
did: bundleDid,
|
|
29
|
+
storeUrl,
|
|
30
|
+
url: inputUrl,
|
|
31
|
+
context = {},
|
|
32
|
+
states,
|
|
33
|
+
manager,
|
|
34
|
+
} = {}) => {
|
|
35
|
+
const nodeInfo = await states.node.read();
|
|
36
|
+
|
|
37
|
+
let componentSourceUrl;
|
|
38
|
+
if (type === BLOCKLET_INSTALL_TYPE.STORE) {
|
|
39
|
+
if (!storeUrl) {
|
|
40
|
+
throw new Error('store url should not be empty');
|
|
41
|
+
}
|
|
42
|
+
componentSourceUrl = StoreUtil.getBlockletMetaUrl({ did: bundleDid, storeUrl });
|
|
43
|
+
await validateStore(nodeInfo, componentSourceUrl);
|
|
44
|
+
} else if (type === BLOCKLET_INSTALL_TYPE.URL) {
|
|
45
|
+
componentSourceUrl = inputUrl;
|
|
46
|
+
await validateStore(nodeInfo, componentSourceUrl);
|
|
47
|
+
} else if (type === BLOCKLET_INSTALL_TYPE.CREATE) {
|
|
48
|
+
await joi.string().label('title').max(20).required().validateAsync(title);
|
|
49
|
+
await joi.string().label('description').max(80).required().validateAsync(description);
|
|
50
|
+
} else {
|
|
51
|
+
throw new Error(`Should not be here: unknown type ${type}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// check install count
|
|
55
|
+
if (controller?.nftId) {
|
|
56
|
+
// sometimes nedb will throw error if use states.blocklet.count({ ['controller.nftId']: controller.nftId })
|
|
57
|
+
const installedCount = await states.blockletExtras.count({ 'controller.nftId': controller.nftId });
|
|
58
|
+
|
|
59
|
+
if (installedCount >= (controller.appMaxCount || 1)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`You can only install ${controller.appMaxCount} blocklet with this credential: ${controller.nftId}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// app wallet
|
|
67
|
+
const wallet = getApplicationWallet(appSk);
|
|
68
|
+
const did = wallet.address;
|
|
69
|
+
const name = wallet.address;
|
|
70
|
+
|
|
71
|
+
// check exist
|
|
72
|
+
const exist = await states.blocklet.hasBlocklet(did);
|
|
73
|
+
if (exist) {
|
|
74
|
+
throw new Error(`blocklet ${did} already exists`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// remove dirty data
|
|
78
|
+
const oldExtraState = await states.blockletExtras.findOne({ did });
|
|
79
|
+
if (oldExtraState) {
|
|
80
|
+
logger.error('find dirty data in blocklet extra', { did });
|
|
81
|
+
await states.blockletExtras.remove({ did });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// create component
|
|
85
|
+
let component;
|
|
86
|
+
if (componentSourceUrl) {
|
|
87
|
+
const meta = await getBlockletMetaFromUrl(componentSourceUrl);
|
|
88
|
+
|
|
89
|
+
if (nodeInfo.mode === NODE_MODES.SERVERLESS) {
|
|
90
|
+
validateInServerless({ blockletMeta: meta });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
component = {
|
|
94
|
+
meta: ensureMeta(meta),
|
|
95
|
+
mountPoint: '/',
|
|
96
|
+
bundleSource: { url: componentSourceUrl },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// create app
|
|
101
|
+
const blocklet = await manager._addBlocklet({ component, name, did, title, description });
|
|
102
|
+
logger.info('blocklet added to database', { did: blocklet.meta.did });
|
|
103
|
+
|
|
104
|
+
// create config
|
|
105
|
+
try {
|
|
106
|
+
await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
|
|
107
|
+
await manager._setConfigsFromMeta(did);
|
|
108
|
+
await states.blockletExtras.setConfigs(did, [
|
|
109
|
+
{
|
|
110
|
+
key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
|
|
111
|
+
value: appSk,
|
|
112
|
+
secure: true,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
await manager._rollback('install', did, {});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!blocklet.children.length) {
|
|
120
|
+
return manager._installBlocklet({ did, context });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
|
|
125
|
+
manager.emit(BlockletEvents.added, blocklet1);
|
|
126
|
+
|
|
127
|
+
const action = 'install';
|
|
128
|
+
const downloadParams = {
|
|
129
|
+
blocklet: { ...blocklet1 },
|
|
130
|
+
context,
|
|
131
|
+
postAction: action,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// backup rollback data
|
|
135
|
+
await manager._rollbackCache.backup({ did, action });
|
|
136
|
+
|
|
137
|
+
if (sync) {
|
|
138
|
+
await manager._downloadAndInstall({ ...downloadParams, throwOnError: true });
|
|
139
|
+
return states.blocklet.getBlocklet(did);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
const ticket = manager.installQueue.push(
|
|
144
|
+
{
|
|
145
|
+
entity: 'blocklet',
|
|
146
|
+
action: 'download',
|
|
147
|
+
id: did,
|
|
148
|
+
...downloadParams,
|
|
149
|
+
},
|
|
150
|
+
did
|
|
151
|
+
);
|
|
152
|
+
ticket.on('failed', async (err) => {
|
|
153
|
+
const componentDid = component?.meta?.did;
|
|
154
|
+
logger.error('failed to install blocklet', { did, componentDid, error: err });
|
|
155
|
+
try {
|
|
156
|
+
await manager._rollback('install', did, {});
|
|
157
|
+
} catch (e) {
|
|
158
|
+
logger.error('failed to remove blocklet on install error', { did, componentDid, error: e });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
manager._createNotification(did, {
|
|
162
|
+
title: 'Blocklet Install Failed',
|
|
163
|
+
description: `Blocklet ${title || component?.meta?.title} install failed with error: ${
|
|
164
|
+
err.message || 'queue exception'
|
|
165
|
+
}`,
|
|
166
|
+
entityType: 'blocklet',
|
|
167
|
+
entityId: did,
|
|
168
|
+
severity: 'error',
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}, delay || 0);
|
|
172
|
+
|
|
173
|
+
return blocklet1;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const componentDid = component?.meta?.did;
|
|
176
|
+
logger.error('failed to install blocklet', { did, componentDid, error: err });
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await manager._rollback('install', did, {});
|
|
180
|
+
} catch (e) {
|
|
181
|
+
logger.error('failed to remove blocklet on install error', { did, componentDid, error: e });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
module.exports = { installApplicationFromGeneral };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const logger = require('@abtnode/logger')('@abtnode/core:install-component-dev');
|
|
2
|
+
|
|
3
|
+
const { BlockletStatus, BlockletSource, BLOCKLET_MODES, fromBlockletStatus } = require('@blocklet/constant');
|
|
4
|
+
const {
|
|
5
|
+
parseComponents,
|
|
6
|
+
filterDuplicateComponents,
|
|
7
|
+
ensureMeta,
|
|
8
|
+
checkVersionCompatibility,
|
|
9
|
+
validateBlocklet,
|
|
10
|
+
} = require('../../../util/blocklet');
|
|
11
|
+
const { formatName } = require('../../../util/get-domain-for-blocklet');
|
|
12
|
+
|
|
13
|
+
const installComponentFromDev = async ({ folder, meta, rootDid, mountPoint, manager, states }) => {
|
|
14
|
+
const { did, version } = meta;
|
|
15
|
+
|
|
16
|
+
const existRoot = await states.blocklet.getBlocklet(rootDid);
|
|
17
|
+
if (!existRoot) {
|
|
18
|
+
throw new Error('Root blocklet does not exist');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const exist = existRoot.children.find((x) => x.meta.did === meta.did);
|
|
22
|
+
if (exist) {
|
|
23
|
+
if (exist.mode === BLOCKLET_MODES.PRODUCTION) {
|
|
24
|
+
throw new Error('The blocklet component of production mode already exists, please remove it before developing');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const status = fromBlockletStatus(exist.status);
|
|
28
|
+
if (['starting', 'running'].includes(status)) {
|
|
29
|
+
throw new Error(`The blocklet component is already on ${status}, please stop it before developing`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logger.info('remove blocklet component for dev', { did, version });
|
|
33
|
+
|
|
34
|
+
await manager.deleteComponent({ did, rootDid });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const defaultPath = formatName(meta.name);
|
|
38
|
+
const component = {
|
|
39
|
+
meta: ensureMeta(meta),
|
|
40
|
+
mountPoint: mountPoint || `/${defaultPath}`,
|
|
41
|
+
source: BlockletSource.local,
|
|
42
|
+
deployedFrom: folder,
|
|
43
|
+
status: BlockletStatus.installed,
|
|
44
|
+
mode: BLOCKLET_MODES.DEVELOPMENT,
|
|
45
|
+
};
|
|
46
|
+
const { dynamicComponents } = await parseComponents(component);
|
|
47
|
+
dynamicComponents.unshift(component);
|
|
48
|
+
const children = filterDuplicateComponents(dynamicComponents, existRoot.children);
|
|
49
|
+
checkVersionCompatibility([...children, ...existRoot.children]);
|
|
50
|
+
await states.blocklet.addChildren(rootDid, children);
|
|
51
|
+
|
|
52
|
+
const newBlocklet = await states.blocklet.getBlocklet(rootDid);
|
|
53
|
+
await validateBlocklet(newBlocklet);
|
|
54
|
+
await manager._downloadBlocklet(newBlocklet);
|
|
55
|
+
await states.blocklet.setBlockletStatus(rootDid, BlockletStatus.stopped);
|
|
56
|
+
|
|
57
|
+
// Add Config
|
|
58
|
+
await manager._setConfigsFromMeta(rootDid);
|
|
59
|
+
|
|
60
|
+
// should ensure blocklet integrity
|
|
61
|
+
let blocklet = await manager.ensureBlocklet(rootDid);
|
|
62
|
+
|
|
63
|
+
// pre install
|
|
64
|
+
await manager._runPreInstallHook(blocklet);
|
|
65
|
+
|
|
66
|
+
// Add environments
|
|
67
|
+
await manager._updateBlockletEnvironment(rootDid);
|
|
68
|
+
blocklet = await manager.getBlocklet(rootDid);
|
|
69
|
+
|
|
70
|
+
// post install
|
|
71
|
+
await manager._runPostInstallHook(blocklet);
|
|
72
|
+
|
|
73
|
+
logger.info('add blocklet component for dev', { did, version, meta });
|
|
74
|
+
|
|
75
|
+
blocklet = await manager.getBlocklet(rootDid);
|
|
76
|
+
|
|
77
|
+
return blocklet;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports = { installComponentFromDev };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const logger = require('@abtnode/logger')('@abtnode/core:install-component-upload');
|
|
4
|
+
const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
|
|
5
|
+
|
|
6
|
+
const { BlockletSource, BlockletGroup, fromBlockletStatus } = require('@blocklet/constant');
|
|
7
|
+
const { isInProgress } = require('../../../util');
|
|
8
|
+
const {
|
|
9
|
+
parseComponents,
|
|
10
|
+
filterDuplicateComponents,
|
|
11
|
+
getDiffFiles,
|
|
12
|
+
getBundleDir,
|
|
13
|
+
ensureMeta,
|
|
14
|
+
checkStructVersion,
|
|
15
|
+
checkVersionCompatibility,
|
|
16
|
+
} = require('../../../util/blocklet');
|
|
17
|
+
const { resolveDownload, resolveDiffDownload, downloadFromUpload } = require('../../downloader/resolve-download');
|
|
18
|
+
|
|
19
|
+
const installComponentFromUpload = async ({
|
|
20
|
+
rootDid,
|
|
21
|
+
mountPoint,
|
|
22
|
+
file,
|
|
23
|
+
did,
|
|
24
|
+
diffVersion,
|
|
25
|
+
deleteSet,
|
|
26
|
+
context = {},
|
|
27
|
+
manager,
|
|
28
|
+
states,
|
|
29
|
+
}) => {
|
|
30
|
+
logger.info('install component', { from: 'upload file' });
|
|
31
|
+
|
|
32
|
+
const oldBlocklet = await manager._getBlockletForInstallation(rootDid);
|
|
33
|
+
if (!oldBlocklet) {
|
|
34
|
+
throw new Error('Root blocklet does not exist');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
checkStructVersion(oldBlocklet);
|
|
38
|
+
|
|
39
|
+
// download
|
|
40
|
+
const { tarFile } = await downloadFromUpload(file, { downloadDir: path.join(manager.dataDirs.tmp, 'download') });
|
|
41
|
+
|
|
42
|
+
if (isInProgress(oldBlocklet.status)) {
|
|
43
|
+
logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
|
|
44
|
+
throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let meta;
|
|
48
|
+
// diff upload
|
|
49
|
+
if (did && diffVersion) {
|
|
50
|
+
const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
|
|
51
|
+
if (!oldChild) {
|
|
52
|
+
throw new Error(`Blocklet ${did} not found when diff deploying`);
|
|
53
|
+
}
|
|
54
|
+
if (oldChild.meta.version !== diffVersion) {
|
|
55
|
+
logger.error('Diff deploy: Blocklet version changed', {
|
|
56
|
+
preVersion: diffVersion,
|
|
57
|
+
changedVersion: oldChild.meta.version,
|
|
58
|
+
name: oldChild.meta.name,
|
|
59
|
+
did: oldChild.meta.did,
|
|
60
|
+
});
|
|
61
|
+
throw new Error('Blocklet version changed when diff deploying');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
meta = (await resolveDiffDownload(tarFile, manager.installDir, { deleteSet, meta: oldChild.meta })).meta;
|
|
65
|
+
} else {
|
|
66
|
+
// full deploy
|
|
67
|
+
meta = (await resolveDownload(tarFile, manager.installDir)).meta;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (meta.did === rootDid) {
|
|
71
|
+
// should not be here
|
|
72
|
+
throw new Error('Cannot add self as a component');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (meta.group === BlockletGroup.gateway) {
|
|
76
|
+
throw new Error('Cannot add gateway component');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newBlocklet = await states.blocklet.getBlocklet(rootDid);
|
|
80
|
+
|
|
81
|
+
const newChild = {
|
|
82
|
+
meta: ensureMeta(meta),
|
|
83
|
+
mountPoint,
|
|
84
|
+
source: BlockletSource.upload,
|
|
85
|
+
deployedFrom: `Upload by ${context.user.fullName}`,
|
|
86
|
+
bundleSource: null,
|
|
87
|
+
};
|
|
88
|
+
const index = newBlocklet.children.findIndex((child) => child.meta.did === meta.did);
|
|
89
|
+
if (index >= 0) {
|
|
90
|
+
// if upgrade, do not update mountPoint and title
|
|
91
|
+
newChild.mountPoint = newBlocklet.children[index].mountPoint;
|
|
92
|
+
newChild.meta.title = newBlocklet.children[index].meta.title;
|
|
93
|
+
newBlocklet.children.splice(index, 1, newChild);
|
|
94
|
+
} else {
|
|
95
|
+
newBlocklet.children.push(newChild);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { dynamicComponents } = await parseComponents(newChild);
|
|
99
|
+
const newChildren = filterDuplicateComponents(dynamicComponents, newBlocklet.children);
|
|
100
|
+
|
|
101
|
+
newBlocklet.children.push(...newChildren);
|
|
102
|
+
|
|
103
|
+
checkVersionCompatibility(newBlocklet.children);
|
|
104
|
+
|
|
105
|
+
// backup rollback data
|
|
106
|
+
const action = 'upgrade';
|
|
107
|
+
await manager._rollbackCache.backup({ did: newBlocklet.meta.did, action, oldBlocklet });
|
|
108
|
+
|
|
109
|
+
await manager._downloadAndInstall({
|
|
110
|
+
blocklet: newBlocklet,
|
|
111
|
+
oldBlocklet,
|
|
112
|
+
context: { ...context, forceStartProcessIds: [getComponentProcessId(newChild, [newBlocklet])] },
|
|
113
|
+
throwOnError: true,
|
|
114
|
+
postAction: action,
|
|
115
|
+
skipCheckStatusBeforeDownload: true,
|
|
116
|
+
selectedComponentDids: newChildren.map((x) => x.meta.did),
|
|
117
|
+
});
|
|
118
|
+
return manager.getBlocklet(newBlocklet.meta.did);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const diff = async ({ did, hashFiles: clientFiles, rootDid: inputRootDid, states }) => {
|
|
122
|
+
if (!did) {
|
|
123
|
+
throw new Error('did is empty');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!clientFiles || !clientFiles.length) {
|
|
127
|
+
throw new Error('hashFiles is empty');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rootDid = inputRootDid || did;
|
|
131
|
+
const childDid = inputRootDid ? did : '';
|
|
132
|
+
|
|
133
|
+
if (childDid === rootDid) {
|
|
134
|
+
throw new Error('Cannot add self as a component');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
logger.info('Get blocklet diff', { rootDid, childDid, clientFilesNumber: clientFiles.length });
|
|
138
|
+
|
|
139
|
+
const rootBlocklet = await states.blocklet.getBlocklet(rootDid);
|
|
140
|
+
if (childDid && !rootBlocklet) {
|
|
141
|
+
throw new Error('Root blocklet does not exist');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const state = childDid ? await (rootBlocklet.children || []).find((x) => x.meta.did === childDid) : rootBlocklet;
|
|
145
|
+
|
|
146
|
+
if (!state) {
|
|
147
|
+
return {
|
|
148
|
+
hasBlocklet: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (state.source === BlockletSource.local) {
|
|
153
|
+
throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { version } = state.meta;
|
|
157
|
+
const bundleDir = getBundleDir(this.installDir, state.meta);
|
|
158
|
+
const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
|
|
159
|
+
|
|
160
|
+
logger.info('Diff files', {
|
|
161
|
+
name: state.meta.name,
|
|
162
|
+
did: state.meta.did,
|
|
163
|
+
version: state.meta.version,
|
|
164
|
+
addNum: addSet.length,
|
|
165
|
+
changeNum: changeSet.length,
|
|
166
|
+
deleteNum: deleteSet.length,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
hasBlocklet: true,
|
|
171
|
+
version,
|
|
172
|
+
addSet,
|
|
173
|
+
changeSet,
|
|
174
|
+
deleteSet,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
installComponentFromUpload,
|
|
180
|
+
diff,
|
|
181
|
+
};
|