@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260111-112953-aed5ff39
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/access-key-manager.js +104 -0
- package/lib/api/team/invitation-manager.js +461 -0
- package/lib/api/team/notification-manager.js +189 -0
- package/lib/api/team/oauth-manager.js +60 -0
- package/lib/api/team/org-crud-manager.js +202 -0
- package/lib/api/team/org-manager.js +56 -0
- package/lib/api/team/org-member-manager.js +403 -0
- package/lib/api/team/org-query-manager.js +126 -0
- package/lib/api/team/org-resource-manager.js +186 -0
- package/lib/api/team/passport-manager.js +670 -0
- package/lib/api/team/rbac-manager.js +335 -0
- package/lib/api/team/session-manager.js +540 -0
- package/lib/api/team/store-manager.js +198 -0
- package/lib/api/team/tag-manager.js +230 -0
- package/lib/api/team/user-auth-manager.js +132 -0
- package/lib/api/team/user-manager.js +78 -0
- package/lib/api/team/user-query-manager.js +299 -0
- package/lib/api/team/user-social-manager.js +354 -0
- package/lib/api/team/user-update-manager.js +224 -0
- package/lib/api/team/verify-code-manager.js +161 -0
- package/lib/api/team.js +439 -3287
- package/lib/blocklet/manager/disk/auth-manager.js +68 -0
- package/lib/blocklet/manager/disk/backup-manager.js +288 -0
- package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
- package/lib/blocklet/manager/disk/component-manager.js +83 -0
- package/lib/blocklet/manager/disk/config-manager.js +191 -0
- package/lib/blocklet/manager/disk/controller-manager.js +64 -0
- package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
- package/lib/blocklet/manager/disk/download-manager.js +96 -0
- package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
- package/lib/blocklet/manager/disk/federated-manager.js +651 -0
- package/lib/blocklet/manager/disk/hook-manager.js +124 -0
- package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
- package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
- package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
- package/lib/blocklet/manager/disk/install-manager.js +36 -0
- package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
- package/lib/blocklet/manager/disk/job-manager.js +467 -0
- package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
- package/lib/blocklet/manager/disk/notification-manager.js +343 -0
- package/lib/blocklet/manager/disk/query-manager.js +562 -0
- package/lib/blocklet/manager/disk/settings-manager.js +507 -0
- package/lib/blocklet/manager/disk/start-manager.js +611 -0
- package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
- package/lib/blocklet/manager/disk/update-manager.js +153 -0
- package/lib/blocklet/manager/disk.js +669 -5796
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
- package/lib/blocklet/manager/lock.js +18 -0
- package/lib/event/index.js +28 -24
- package/lib/util/blocklet/app-utils.js +192 -0
- package/lib/util/blocklet/blocklet-loader.js +258 -0
- package/lib/util/blocklet/config-manager.js +232 -0
- package/lib/util/blocklet/did-document.js +240 -0
- package/lib/util/blocklet/environment.js +555 -0
- package/lib/util/blocklet/health-check.js +449 -0
- package/lib/util/blocklet/install-utils.js +365 -0
- package/lib/util/blocklet/logo.js +57 -0
- package/lib/util/blocklet/meta-utils.js +269 -0
- package/lib/util/blocklet/port-manager.js +141 -0
- package/lib/util/blocklet/process-manager.js +504 -0
- package/lib/util/blocklet/runtime-info.js +105 -0
- package/lib/util/blocklet/validation.js +418 -0
- package/lib/util/blocklet.js +98 -3066
- package/lib/util/wallet-app-notification.js +40 -0
- package/package.json +22 -22
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/**
|
|
3
|
+
* Install Utilities Module
|
|
4
|
+
*
|
|
5
|
+
* Functions for blocklet installation, verification, and bundle management
|
|
6
|
+
* Extracted from blocklet.js for better modularity
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const tar = require('tar');
|
|
12
|
+
const get = require('lodash/get');
|
|
13
|
+
const streamToPromise = require('stream-to-promise');
|
|
14
|
+
const { Throttle } = require('stream-throttle');
|
|
15
|
+
const ssri = require('ssri');
|
|
16
|
+
const diff = require('deep-diff');
|
|
17
|
+
|
|
18
|
+
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:install-utils');
|
|
19
|
+
const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
|
|
20
|
+
const hashFiles = require('@abtnode/util/lib/hash-files');
|
|
21
|
+
const { BLOCKLET_INSTALL_TYPE } = require('@abtnode/constant');
|
|
22
|
+
|
|
23
|
+
const { BlockletStatus, BlockletSource, fromBlockletStatus } = require('@blocklet/constant');
|
|
24
|
+
const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get version scope string with hash for unique directory naming
|
|
28
|
+
* @param {object} meta - Blocklet meta
|
|
29
|
+
* @returns {string} Version scope string
|
|
30
|
+
*/
|
|
31
|
+
const getVersionScope = (meta) => {
|
|
32
|
+
if (meta.dist?.integrity) {
|
|
33
|
+
const safeHash = meta.dist.integrity
|
|
34
|
+
.replace('sha512-', '')
|
|
35
|
+
.slice(0, 8)
|
|
36
|
+
.replace(/[^a-zA-Z0-9]/g, '');
|
|
37
|
+
return `${meta.version}-${safeHash}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return meta.version;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Expand tarball to destination directory
|
|
45
|
+
* @param {object} options - Options
|
|
46
|
+
* @param {string} options.source - Source tarball path
|
|
47
|
+
* @param {string} options.dest - Destination directory
|
|
48
|
+
* @param {number} options.strip - Number of leading directories to strip (default: 1)
|
|
49
|
+
* @returns {Promise<string>} Destination path
|
|
50
|
+
*/
|
|
51
|
+
const expandTarball = async ({ source, dest, strip = 1 }) => {
|
|
52
|
+
logger.info('expand blocklet', { source, dest });
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(source)) {
|
|
55
|
+
throw new Error(`Blocklet tarball ${source} does not exist`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
59
|
+
|
|
60
|
+
await streamToPromise(
|
|
61
|
+
fs
|
|
62
|
+
.createReadStream(source)
|
|
63
|
+
.pipe(new Throttle({ rate: 1024 * 1024 * 20 })) // 20MB
|
|
64
|
+
.pipe(tar.x({ C: dest, strip }))
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return dest;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Verify file integrity using SSRI
|
|
72
|
+
* @param {object} options - Options
|
|
73
|
+
* @param {string} options.file - File path
|
|
74
|
+
* @param {string} options.integrity - Expected integrity hash
|
|
75
|
+
* @returns {Promise<boolean>} True if valid
|
|
76
|
+
*/
|
|
77
|
+
const verifyIntegrity = async ({ file, integrity: expected }) => {
|
|
78
|
+
const stream = fs.createReadStream(file);
|
|
79
|
+
const result = await ssri.checkStream(stream, ssri.parse(expected));
|
|
80
|
+
logger.debug('verify integrity result', { result });
|
|
81
|
+
stream.destroy();
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get all app directories from install directory
|
|
87
|
+
* @param {string} installDir - Installation directory
|
|
88
|
+
* @returns {Promise<Array<{ key: string, dir: string }>>} App directories
|
|
89
|
+
*/
|
|
90
|
+
const getAppDirs = async (installDir) => {
|
|
91
|
+
const appDirs = [];
|
|
92
|
+
|
|
93
|
+
const getNextLevel = (level, name) => {
|
|
94
|
+
if (level === 'root') {
|
|
95
|
+
if (name.startsWith('@')) {
|
|
96
|
+
return 'scope';
|
|
97
|
+
}
|
|
98
|
+
return 'name';
|
|
99
|
+
}
|
|
100
|
+
if (level === 'scope') {
|
|
101
|
+
return 'name';
|
|
102
|
+
}
|
|
103
|
+
if (level === 'name') {
|
|
104
|
+
return 'version';
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`Invalid level ${level}`);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const fillAppDirs = async (dir, level = 'root') => {
|
|
110
|
+
if (level === 'version') {
|
|
111
|
+
appDirs.push({
|
|
112
|
+
key: formatBackSlash(path.relative(installDir, dir)),
|
|
113
|
+
dir,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const nextDirs = [];
|
|
120
|
+
for (const x of await fs.promises.readdir(dir)) {
|
|
121
|
+
if (!fs.lstatSync(path.join(dir, x)).isDirectory()) {
|
|
122
|
+
logger.error('pruneBlockletBundle: invalid file in bundle storage', { dir, file: x });
|
|
123
|
+
// eslint-disable-next-line no-continue
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
nextDirs.push(x);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const x of nextDirs) {
|
|
130
|
+
await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await fillAppDirs(installDir, 'root');
|
|
135
|
+
|
|
136
|
+
return appDirs;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Prune unused blocklet bundles from install directory
|
|
141
|
+
* @param {object} options - Options
|
|
142
|
+
* @param {Array} options.blocklets - Current blocklets
|
|
143
|
+
* @param {string} options.installDir - Installation directory
|
|
144
|
+
* @param {Array} options.blockletSettings - Blocklet settings with children
|
|
145
|
+
*/
|
|
146
|
+
const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings }) => {
|
|
147
|
+
for (const blocklet of blocklets) {
|
|
148
|
+
if (
|
|
149
|
+
[
|
|
150
|
+
BlockletStatus.waiting,
|
|
151
|
+
BlockletStatus.installing,
|
|
152
|
+
BlockletStatus.upgrading,
|
|
153
|
+
BlockletStatus.downloading,
|
|
154
|
+
].includes(blocklet.status)
|
|
155
|
+
) {
|
|
156
|
+
logger.info('There are blocklet activities in progress, abort pruning', {
|
|
157
|
+
bundleName: blocklet.meta.bundleName,
|
|
158
|
+
status: fromBlockletStatus(blocklet.status),
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// blockletMap: { <[scope/]name/version>: true }
|
|
165
|
+
const blockletMap = {};
|
|
166
|
+
for (const blocklet of blocklets) {
|
|
167
|
+
forEachBlockletSync(blocklet, (component) => {
|
|
168
|
+
blockletMap[`${component.meta.bundleName}/${component.meta.version}`] = true;
|
|
169
|
+
blockletMap[`${component.meta.bundleName}/${getVersionScope(component.meta)}`] = true;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
for (const setting of blockletSettings) {
|
|
173
|
+
for (const child of setting.children || []) {
|
|
174
|
+
if (child.status !== BlockletStatus.deleted) {
|
|
175
|
+
forEachBlockletSync(child, (component) => {
|
|
176
|
+
blockletMap[`${component.meta.bundleName}/${component.meta.version}`] = true;
|
|
177
|
+
blockletMap[`${component.meta.bundleName}/${getVersionScope(component.meta)}`] = true;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// fill appDirs
|
|
184
|
+
let appDirs = [];
|
|
185
|
+
try {
|
|
186
|
+
appDirs = await getAppDirs(installDir);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.error('fill app dirs failed', { error });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const ensureBundleDirRemoved = async (dir) => {
|
|
192
|
+
const relativeDir = path.relative(installDir, dir);
|
|
193
|
+
const arr = relativeDir.split(path.sep).filter(Boolean);
|
|
194
|
+
const { length } = arr;
|
|
195
|
+
const bundleName = arr[length - 2];
|
|
196
|
+
const scopeName = length > 2 ? arr[length - 3] : '';
|
|
197
|
+
const bundleDir = path.join(installDir, scopeName, bundleName);
|
|
198
|
+
const isDirEmpty = (await fs.promises.readdir(bundleDir)).length === 0;
|
|
199
|
+
if (isDirEmpty) {
|
|
200
|
+
logger.info('Remove bundle folder', { bundleDir });
|
|
201
|
+
await fs.remove(bundleDir);
|
|
202
|
+
}
|
|
203
|
+
if (scopeName) {
|
|
204
|
+
const scopeDir = path.join(installDir, scopeName);
|
|
205
|
+
const isScopeEmpty = (await fs.promises.readdir(scopeDir)).length === 0;
|
|
206
|
+
if (isScopeEmpty) {
|
|
207
|
+
logger.info('Remove scope folder', { scopeDir });
|
|
208
|
+
await fs.remove(scopeDir);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// remove trash
|
|
214
|
+
for (const app of appDirs) {
|
|
215
|
+
if (!blockletMap[app.key]) {
|
|
216
|
+
logger.info('Remove app folder', { dir: app.dir });
|
|
217
|
+
await fs.remove(app.dir);
|
|
218
|
+
await ensureBundleDirRemoved(app.dir);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
logger.info('Blocklet source folder has been pruned');
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get install type from params
|
|
227
|
+
* @param {object} params - Install params
|
|
228
|
+
* @returns {string} BLOCKLET_INSTALL_TYPE
|
|
229
|
+
*/
|
|
230
|
+
const getTypeFromInstallParams = (params) => {
|
|
231
|
+
if (params.type) {
|
|
232
|
+
if (!Object.values(BLOCKLET_INSTALL_TYPE).includes(params.type)) {
|
|
233
|
+
throw new Error(`Can only install blocklet from ${Object.values(BLOCKLET_INSTALL_TYPE).join('/')}`);
|
|
234
|
+
}
|
|
235
|
+
return params.type;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (params.url) {
|
|
239
|
+
return BLOCKLET_INSTALL_TYPE.URL;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (params.file) {
|
|
243
|
+
throw new Error('install from upload is not supported');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (params.did) {
|
|
247
|
+
return BLOCKLET_INSTALL_TYPE.STORE;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (params.title && params.description) {
|
|
251
|
+
return BLOCKLET_INSTALL_TYPE.CREATE;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw new Error(`Can only install blocklet from ${Object.values(BLOCKLET_INSTALL_TYPE).join('/')}`);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get diff files between input files and source directory
|
|
259
|
+
* @param {Array} inputFiles - Input files with hash
|
|
260
|
+
* @param {string} sourceDir - Source directory
|
|
261
|
+
* @returns {Promise<{ addSet: Array, changeSet: Array, deleteSet: Array }>}
|
|
262
|
+
*/
|
|
263
|
+
const getDiffFiles = async (inputFiles, sourceDir) => {
|
|
264
|
+
if (!fs.existsSync(sourceDir)) {
|
|
265
|
+
throw new Error(`${sourceDir} does not exist`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const files = inputFiles.reduce((obj, item) => {
|
|
269
|
+
obj[item.file] = item.hash;
|
|
270
|
+
return obj;
|
|
271
|
+
}, {});
|
|
272
|
+
|
|
273
|
+
const { files: sourceFiles } = await hashFiles(sourceDir, {
|
|
274
|
+
filter: (x) => x.indexOf('node_modules') === -1,
|
|
275
|
+
concurrentHash: 1,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const addSet = [];
|
|
279
|
+
const changeSet = [];
|
|
280
|
+
const deleteSet = [];
|
|
281
|
+
|
|
282
|
+
const diffFiles = diff(sourceFiles, files);
|
|
283
|
+
if (diffFiles) {
|
|
284
|
+
diffFiles.forEach((item) => {
|
|
285
|
+
if (item.kind === 'D') {
|
|
286
|
+
deleteSet.push(item.path[0]);
|
|
287
|
+
}
|
|
288
|
+
if (item.kind === 'E') {
|
|
289
|
+
changeSet.push(item.path[0]);
|
|
290
|
+
}
|
|
291
|
+
if (item.kind === 'N') {
|
|
292
|
+
addSet.push(item.path[0]);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
addSet,
|
|
299
|
+
changeSet,
|
|
300
|
+
deleteSet,
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const checkCompatibleOnce = {};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check compatibility with old blocklets that use blocklet.yml
|
|
308
|
+
* @param {string} dir - Directory to check
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
const compatibleWithOldBlocklets = (dir) => {
|
|
312
|
+
if (checkCompatibleOnce[dir] !== undefined) {
|
|
313
|
+
return checkCompatibleOnce[dir];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
checkCompatibleOnce[dir] = !!fs.existsSync(path.join(dir, 'blocklet.yml'));
|
|
317
|
+
|
|
318
|
+
return checkCompatibleOnce[dir];
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get bundle directory for blocklet
|
|
323
|
+
* @param {string} installDir - Installation directory
|
|
324
|
+
* @param {object} meta - Blocklet meta
|
|
325
|
+
* @returns {string} Bundle directory path
|
|
326
|
+
*/
|
|
327
|
+
const getBundleDir = (installDir, meta) => {
|
|
328
|
+
const oldDir = path.join(installDir, meta.bundleName || meta.name, meta.version);
|
|
329
|
+
if (compatibleWithOldBlocklets(oldDir)) {
|
|
330
|
+
return oldDir;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return path.join(installDir, meta.bundleName || meta.name, getVersionScope(meta));
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Check if blocklet needs to be downloaded
|
|
338
|
+
* @param {object} blocklet - New blocklet
|
|
339
|
+
* @param {object} oldBlocklet - Existing blocklet
|
|
340
|
+
* @returns {boolean}
|
|
341
|
+
*/
|
|
342
|
+
const needBlockletDownload = (blocklet, oldBlocklet) => {
|
|
343
|
+
if ([BlockletSource.upload, BlockletSource.local, BlockletSource.custom].includes(blocklet.source)) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!get(oldBlocklet, 'meta.dist.integrity')) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity');
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
module.exports = {
|
|
355
|
+
getVersionScope,
|
|
356
|
+
expandTarball,
|
|
357
|
+
verifyIntegrity,
|
|
358
|
+
getAppDirs,
|
|
359
|
+
pruneBlockletBundle,
|
|
360
|
+
getTypeFromInstallParams,
|
|
361
|
+
getDiffFiles,
|
|
362
|
+
compatibleWithOldBlocklets,
|
|
363
|
+
getBundleDir,
|
|
364
|
+
needBlockletDownload,
|
|
365
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logo Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for blocklet logo generation and management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
const { isEthereumDid } = require('@arcblock/did');
|
|
11
|
+
const { toSvg: createDidLogo } = require('@arcblock/did-motif');
|
|
12
|
+
const { createBlockiesSvg } = require('@blocklet/meta/lib/blockies');
|
|
13
|
+
const { BlockletSource } = require('@blocklet/constant');
|
|
14
|
+
|
|
15
|
+
const { getBundleDir } = require('./install-utils');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Update blocklet fallback logo with DID-based SVG
|
|
19
|
+
* @param {object} blocklet - Blocklet object with env.dataDir and meta.did
|
|
20
|
+
*/
|
|
21
|
+
const updateBlockletFallbackLogo = async (blocklet) => {
|
|
22
|
+
if (isEthereumDid(blocklet.meta.did)) {
|
|
23
|
+
await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createBlockiesSvg(blocklet.meta.did));
|
|
24
|
+
} else {
|
|
25
|
+
await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensure app logo exists by copying from component if needed
|
|
31
|
+
* @param {object} blocklet - Blocklet object
|
|
32
|
+
* @param {string} blockletsDir - Blocklets directory path
|
|
33
|
+
*/
|
|
34
|
+
const ensureAppLogo = async (blocklet, blockletsDir) => {
|
|
35
|
+
if (!blocklet) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
blocklet.source === BlockletSource.custom &&
|
|
41
|
+
(blocklet.children || [])[0]?.meta?.logo &&
|
|
42
|
+
blocklet.children[0].env.appDir
|
|
43
|
+
) {
|
|
44
|
+
const fileName = blocklet.children[0].meta.logo;
|
|
45
|
+
const src = path.join(blocklet.children[0].env.appDir, fileName);
|
|
46
|
+
const dist = path.join(getBundleDir(blockletsDir, blocklet.meta), fileName);
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(src)) {
|
|
49
|
+
await fs.copy(src, dist);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
updateBlockletFallbackLogo,
|
|
56
|
+
ensureAppLogo,
|
|
57
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Utils Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for managing blocklet metadata, themes, and status utilities
|
|
5
|
+
* Extracted from blocklet.js for better modularity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
9
|
+
const mergeWith = require('lodash/mergeWith');
|
|
10
|
+
const semver = require('semver');
|
|
11
|
+
|
|
12
|
+
const { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK } = require('@blocklet/theme');
|
|
13
|
+
const { getComponentConfig } = require('@blocklet/resolver');
|
|
14
|
+
const { BlockletStatus, BlockletGroup, BlockletSource } = require('@blocklet/constant');
|
|
15
|
+
const {
|
|
16
|
+
forEachChildSync,
|
|
17
|
+
findWebInterface,
|
|
18
|
+
forEachComponentV2Sync,
|
|
19
|
+
isInProgress,
|
|
20
|
+
isRunning,
|
|
21
|
+
} = require('@blocklet/meta/lib/util');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format blocklet theme configuration
|
|
25
|
+
* @param {object} rawTheme - Raw theme configuration
|
|
26
|
+
* @returns {object} Formatted theme configuration
|
|
27
|
+
*/
|
|
28
|
+
const formatBlockletTheme = (rawTheme) => {
|
|
29
|
+
let themeConfig = {};
|
|
30
|
+
|
|
31
|
+
if (rawTheme) {
|
|
32
|
+
if (Array.isArray(rawTheme.concepts) && rawTheme.currentConceptId) {
|
|
33
|
+
const concept = rawTheme.concepts.find((x) => x.id === rawTheme.currentConceptId);
|
|
34
|
+
themeConfig = {
|
|
35
|
+
...concept.themeConfig,
|
|
36
|
+
prefer: concept.prefer,
|
|
37
|
+
name: concept.name,
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
// 兼容旧数据
|
|
41
|
+
themeConfig = {
|
|
42
|
+
light: rawTheme.light || {},
|
|
43
|
+
dark: rawTheme.dark || {},
|
|
44
|
+
common: rawTheme.common || {},
|
|
45
|
+
prefer: rawTheme.prefer || 'system',
|
|
46
|
+
name: rawTheme.name || 'Default',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = mergeWith(
|
|
52
|
+
// 至少提供 palette 色板值(客户端会使用)
|
|
53
|
+
cloneDeep({
|
|
54
|
+
light: { palette: BLOCKLET_THEME_LIGHT.palette },
|
|
55
|
+
dark: { palette: BLOCKLET_THEME_DARK.palette },
|
|
56
|
+
prefer: 'system',
|
|
57
|
+
}),
|
|
58
|
+
themeConfig,
|
|
59
|
+
// 数组值直接替换
|
|
60
|
+
(_, srcValue) => {
|
|
61
|
+
if (Array.isArray(srcValue)) {
|
|
62
|
+
return srcValue;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// 保留原始数据,用于 settings 保存
|
|
69
|
+
Object.defineProperty(result, 'raw', {
|
|
70
|
+
value: rawTheme,
|
|
71
|
+
enumerable: false,
|
|
72
|
+
writable: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* merge services
|
|
80
|
+
* from meta.children[].mountPoints[].services, meta.children[].services
|
|
81
|
+
* to childrenMeta[].interfaces[].services
|
|
82
|
+
*
|
|
83
|
+
* @param {array<child>|object{children:array}} source e.g. [<config>] or { children: [<config>] }
|
|
84
|
+
* @param {array<meta|{meta}>} childrenMeta e.g. [<meta>] or [{ meta: <meta> }]
|
|
85
|
+
*/
|
|
86
|
+
const mergeMeta = (source, childrenMeta = []) => {
|
|
87
|
+
// configMap
|
|
88
|
+
const configMap = {};
|
|
89
|
+
(Array.isArray(source) ? source : getComponentConfig(source) || []).forEach((x) => {
|
|
90
|
+
configMap[x.name] = x;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// merge service from config to child meta
|
|
94
|
+
childrenMeta.forEach((child) => {
|
|
95
|
+
const childMeta = child.meta || child;
|
|
96
|
+
const config = configMap[childMeta.name];
|
|
97
|
+
if (!config) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
(config.mountPoints || []).forEach((mountPoint) => {
|
|
102
|
+
if (!mountPoint.services) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const childInterface = childMeta.interfaces.find((y) => y.name === mountPoint.child.interfaceName);
|
|
107
|
+
if (childInterface) {
|
|
108
|
+
// merge
|
|
109
|
+
const services = childInterface.services || [];
|
|
110
|
+
mountPoint.services.forEach((x) => {
|
|
111
|
+
const index = services.findIndex((y) => y.name === x.name);
|
|
112
|
+
if (index >= 0) {
|
|
113
|
+
services.splice(index, 1, x);
|
|
114
|
+
} else {
|
|
115
|
+
services.push(x);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
childInterface.services = services;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (config.services) {
|
|
123
|
+
const childInterface = findWebInterface(childMeta);
|
|
124
|
+
if (childInterface) {
|
|
125
|
+
// merge
|
|
126
|
+
const services = childInterface.services || [];
|
|
127
|
+
config.services.forEach((x) => {
|
|
128
|
+
const index = services.findIndex((y) => y.name === x.name);
|
|
129
|
+
if (index >= 0) {
|
|
130
|
+
services.splice(index, 1, x);
|
|
131
|
+
} else {
|
|
132
|
+
services.push(x);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
childInterface.services = services;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get list of components that need updates based on version comparison
|
|
143
|
+
* @param {object} oldBlocklet - Old blocklet state
|
|
144
|
+
* @param {object} newBlocklet - New blocklet state
|
|
145
|
+
* @returns {Array} Array of {id, meta} for components needing update
|
|
146
|
+
*/
|
|
147
|
+
const getUpdateMetaList = (oldBlocklet = {}, newBlocklet = {}) => {
|
|
148
|
+
const oldMap = {};
|
|
149
|
+
forEachChildSync(oldBlocklet, (b, { id }) => {
|
|
150
|
+
if (b.bundleSource) {
|
|
151
|
+
oldMap[id] = b.meta.version;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const res = [];
|
|
156
|
+
|
|
157
|
+
forEachChildSync(newBlocklet, (b, { id }) => {
|
|
158
|
+
if ((b.bundleSource && semver.gt(b.meta.version, oldMap[id])) || process.env.TEST_UPDATE_ALL_BLOCKLET === 'true') {
|
|
159
|
+
res.push({ id, meta: b.meta });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return res;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get fixed bundle source from component
|
|
168
|
+
* @param {object} component - Component object
|
|
169
|
+
* @returns {object|null} Bundle source object or null
|
|
170
|
+
*/
|
|
171
|
+
const getFixedBundleSource = (component) => {
|
|
172
|
+
if (!component) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (component.bundleSource) {
|
|
177
|
+
return component.bundleSource;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const { source, deployedFrom, meta: { bundleName } = {} } = component;
|
|
181
|
+
|
|
182
|
+
if (!deployedFrom) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (source === BlockletSource.registry && bundleName) {
|
|
187
|
+
return {
|
|
188
|
+
store: deployedFrom,
|
|
189
|
+
name: bundleName,
|
|
190
|
+
version: 'latest',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (source === BlockletSource.url) {
|
|
195
|
+
return {
|
|
196
|
+
url: deployedFrom,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return null;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get computed status of a blocklet based on its components
|
|
205
|
+
* @param {object} blocklet - Blocklet object
|
|
206
|
+
* @returns {string} Computed status
|
|
207
|
+
*/
|
|
208
|
+
const getBlockletStatus = (blocklet) => {
|
|
209
|
+
const fallbackStatus = BlockletStatus.stopped;
|
|
210
|
+
|
|
211
|
+
if (!blocklet) {
|
|
212
|
+
return fallbackStatus;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!blocklet.children?.length) {
|
|
216
|
+
if (blocklet.meta?.group === BlockletGroup.gateway) {
|
|
217
|
+
return blocklet.status;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (blocklet.status === BlockletStatus.added) {
|
|
221
|
+
return BlockletStatus.added;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// for backward compatibility
|
|
225
|
+
if (!blocklet.structVersion) {
|
|
226
|
+
return blocklet.status;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return fallbackStatus;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let inProgressStatus;
|
|
233
|
+
let runningStatus;
|
|
234
|
+
let status;
|
|
235
|
+
|
|
236
|
+
forEachComponentV2Sync(blocklet, (component) => {
|
|
237
|
+
if (component.meta?.group === BlockletGroup.gateway) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (isInProgress(component.status)) {
|
|
242
|
+
if (!inProgressStatus) {
|
|
243
|
+
inProgressStatus = component.status;
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isRunning(component.status) || isRunning(component.greenStatus)) {
|
|
249
|
+
runningStatus = BlockletStatus.running;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (status === BlockletStatus.stopped) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
status = component.status;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return inProgressStatus || runningStatus || status;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
formatBlockletTheme,
|
|
265
|
+
mergeMeta,
|
|
266
|
+
getUpdateMetaList,
|
|
267
|
+
getFixedBundleSource,
|
|
268
|
+
getBlockletStatus,
|
|
269
|
+
};
|