@abtnode/core 1.8.69-beta-54faead3 → 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.
@@ -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-from-backup');
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
- * @param {{
17
- * manager: import('../disk'),
18
- * states: import('../../../states/index')
19
- * }} param0
20
- * @returns
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
- module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } = {}) => {
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('did does not match in blocklet.json and blocklet_extra.json');
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('blocklet is already exist');
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
+ };