@abtnode/core 1.17.8-beta-20260108-224855-28496abb → 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.
Files changed (66) hide show
  1. package/lib/api/team/access-key-manager.js +104 -0
  2. package/lib/api/team/invitation-manager.js +461 -0
  3. package/lib/api/team/notification-manager.js +189 -0
  4. package/lib/api/team/oauth-manager.js +60 -0
  5. package/lib/api/team/org-crud-manager.js +202 -0
  6. package/lib/api/team/org-manager.js +56 -0
  7. package/lib/api/team/org-member-manager.js +403 -0
  8. package/lib/api/team/org-query-manager.js +126 -0
  9. package/lib/api/team/org-resource-manager.js +186 -0
  10. package/lib/api/team/passport-manager.js +670 -0
  11. package/lib/api/team/rbac-manager.js +335 -0
  12. package/lib/api/team/session-manager.js +540 -0
  13. package/lib/api/team/store-manager.js +198 -0
  14. package/lib/api/team/tag-manager.js +230 -0
  15. package/lib/api/team/user-auth-manager.js +132 -0
  16. package/lib/api/team/user-manager.js +78 -0
  17. package/lib/api/team/user-query-manager.js +299 -0
  18. package/lib/api/team/user-social-manager.js +354 -0
  19. package/lib/api/team/user-update-manager.js +224 -0
  20. package/lib/api/team/verify-code-manager.js +161 -0
  21. package/lib/api/team.js +439 -3287
  22. package/lib/blocklet/manager/disk/auth-manager.js +68 -0
  23. package/lib/blocklet/manager/disk/backup-manager.js +288 -0
  24. package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
  25. package/lib/blocklet/manager/disk/component-manager.js +83 -0
  26. package/lib/blocklet/manager/disk/config-manager.js +191 -0
  27. package/lib/blocklet/manager/disk/controller-manager.js +64 -0
  28. package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
  29. package/lib/blocklet/manager/disk/download-manager.js +96 -0
  30. package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
  31. package/lib/blocklet/manager/disk/federated-manager.js +651 -0
  32. package/lib/blocklet/manager/disk/hook-manager.js +124 -0
  33. package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
  34. package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
  35. package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
  36. package/lib/blocklet/manager/disk/install-manager.js +36 -0
  37. package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
  38. package/lib/blocklet/manager/disk/job-manager.js +467 -0
  39. package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
  40. package/lib/blocklet/manager/disk/notification-manager.js +343 -0
  41. package/lib/blocklet/manager/disk/query-manager.js +562 -0
  42. package/lib/blocklet/manager/disk/settings-manager.js +507 -0
  43. package/lib/blocklet/manager/disk/start-manager.js +611 -0
  44. package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
  45. package/lib/blocklet/manager/disk/update-manager.js +153 -0
  46. package/lib/blocklet/manager/disk.js +669 -5796
  47. package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
  48. package/lib/blocklet/manager/lock.js +18 -0
  49. package/lib/event/index.js +28 -24
  50. package/lib/router/helper.js +5 -1
  51. package/lib/util/blocklet/app-utils.js +192 -0
  52. package/lib/util/blocklet/blocklet-loader.js +258 -0
  53. package/lib/util/blocklet/config-manager.js +232 -0
  54. package/lib/util/blocklet/did-document.js +240 -0
  55. package/lib/util/blocklet/environment.js +555 -0
  56. package/lib/util/blocklet/health-check.js +449 -0
  57. package/lib/util/blocklet/install-utils.js +365 -0
  58. package/lib/util/blocklet/logo.js +57 -0
  59. package/lib/util/blocklet/meta-utils.js +269 -0
  60. package/lib/util/blocklet/port-manager.js +141 -0
  61. package/lib/util/blocklet/process-manager.js +504 -0
  62. package/lib/util/blocklet/runtime-info.js +105 -0
  63. package/lib/util/blocklet/validation.js +418 -0
  64. package/lib/util/blocklet.js +98 -3066
  65. package/lib/util/wallet-app-notification.js +40 -0
  66. package/package.json +22 -22
@@ -0,0 +1,141 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /**
3
+ * Port Manager Module
4
+ *
5
+ * Functions for managing blocklet port allocation and conflict resolution
6
+ * Extracted from blocklet.js for better modularity
7
+ */
8
+
9
+ const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:port-manager');
10
+ const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
11
+ const { isPortTaken } = require('@abtnode/util/lib/port');
12
+ const { forEachComponentV2 } = require('@blocklet/meta/lib/util');
13
+
14
+ const { shouldSkipComponent } = require('./process-manager');
15
+
16
+ // Lock for port assignment to prevent race conditions in multi-process environment
17
+ const portAssignLock = new DBCache(() => ({
18
+ ...getAbtNodeRedisAndSQLiteUrl(),
19
+ prefix: 'blocklet-port-assign-lock',
20
+ ttl: 1000 * 30, // 30 seconds timeout
21
+ }));
22
+
23
+ /**
24
+ * Ensure ports shape matches between ports and greenPorts
25
+ * @param {object} _states - States (unused)
26
+ * @param {object} portsA - Source ports
27
+ * @param {object} portsB - Target ports to fill
28
+ */
29
+ const ensurePortsShape = (_states, portsA, portsB) => {
30
+ if (!portsA || Object.keys(portsA).length === 0) {
31
+ return;
32
+ }
33
+ if (Object.keys(portsB).length === 0) {
34
+ for (const key of Object.keys(portsA)) {
35
+ portsB[key] = portsA[key];
36
+ }
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Ensure app ports are not occupied, refresh if needed
42
+ * @param {object} options - Options
43
+ * @param {object} options.blocklet - Blocklet object
44
+ * @param {Array} options.componentDids - Component DIDs to check
45
+ * @param {object} options.states - State managers
46
+ * @param {object} options.manager - Blocklet manager
47
+ * @param {boolean} options.isGreen - Whether checking green ports
48
+ * @returns {Promise<object>} Updated blocklet
49
+ */
50
+ const ensureAppPortsNotOccupied = async ({ blocklet, componentDids: inputDids, states, manager, isGreen = false }) => {
51
+ const { did } = blocklet.meta;
52
+ const lockName = `port-check-${did}`;
53
+
54
+ // ⚠️ 关键修复:使用 DBCache 锁确保端口分配的原子性
55
+ // 在多进程环境下,防止多个进程同时检查同一个端口
56
+ await portAssignLock.acquire(lockName);
57
+
58
+ try {
59
+ const occupiedDids = new Set();
60
+
61
+ await forEachComponentV2(blocklet, async (b) => {
62
+ try {
63
+ if (shouldSkipComponent(b.meta.did, inputDids)) return;
64
+
65
+ if (!b.greenPorts) {
66
+ occupiedDids.add(b.meta.did);
67
+ b.greenPorts = {};
68
+ }
69
+ const { ports = {}, greenPorts } = b;
70
+ ensurePortsShape(states, ports, greenPorts);
71
+
72
+ const targetPorts = isGreen ? greenPorts : ports;
73
+
74
+ let currentOccupied = false;
75
+ for (const port of Object.values(targetPorts)) {
76
+ currentOccupied = await isPortTaken(port);
77
+ if (currentOccupied) {
78
+ break;
79
+ }
80
+ }
81
+
82
+ if (currentOccupied) {
83
+ occupiedDids.add(b.meta.did);
84
+ }
85
+ } catch (error) {
86
+ logger.error('Failed to check ports occupied', { error, blockletDid: b.meta.did, isGreen });
87
+ }
88
+ });
89
+
90
+ if (occupiedDids.size === 0) {
91
+ logger.info('No occupied ports detected, no refresh needed', { did, isGreen });
92
+ return blocklet;
93
+ }
94
+
95
+ const componentDids = Array.from(occupiedDids);
96
+ const {
97
+ refreshed,
98
+ componentDids: actuallyRefreshedDids,
99
+ isInitialAssignment,
100
+ } = await states.blocklet.refreshBlockletPorts(did, componentDids, isGreen);
101
+
102
+ // 只有真正刷新了端口才打印日志和更新环境
103
+ if (refreshed && actuallyRefreshedDids.length > 0) {
104
+ // 区分首次分配和冲突刷新,使用不同的日志信息
105
+ if (isInitialAssignment) {
106
+ logger.info('Assigned green ports for blue-green deployment', {
107
+ did,
108
+ componentDids: actuallyRefreshedDids,
109
+ isGreen,
110
+ });
111
+ } else {
112
+ logger.info('Refreshed component ports due to conflict', {
113
+ did,
114
+ componentDids: actuallyRefreshedDids,
115
+ isGreen,
116
+ });
117
+ }
118
+
119
+ await manager._updateBlockletEnvironment(did);
120
+ const newBlocklet = await manager.ensureBlocklet(did);
121
+
122
+ return newBlocklet;
123
+ }
124
+
125
+ logger.info('Ports were detected as occupied but not actually occupied during refresh, no refresh needed', {
126
+ did,
127
+ componentDids,
128
+ isGreen,
129
+ });
130
+
131
+ return blocklet;
132
+ } finally {
133
+ await portAssignLock.releaseLock(lockName);
134
+ }
135
+ };
136
+
137
+ module.exports = {
138
+ portAssignLock,
139
+ ensurePortsShape,
140
+ ensureAppPortsNotOccupied,
141
+ };
@@ -0,0 +1,504 @@
1
+ /* eslint-disable camelcase */
2
+ /* eslint-disable no-await-in-loop */
3
+
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const get = require('lodash/get');
7
+ const omit = require('lodash/omit');
8
+ const shelljs = require('shelljs');
9
+ const fs = require('fs-extra');
10
+ const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:process-manager');
11
+ const pm2 = require('@abtnode/util/lib/pm2/async-pm2');
12
+ const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
13
+ const { getSecurityNodeOptions } = require('@abtnode/util/lib/security');
14
+ const { killProcessOccupiedPorts } = require('@abtnode/util/lib/port');
15
+ const fetchPm2 = require('@abtnode/util/lib/pm2/fetch-pm2');
16
+ const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
17
+ const { BLOCKLET_MAX_MEM_LIMIT_IN_MB } = require('@abtnode/constant');
18
+ const {
19
+ BlockletStatus,
20
+ BlockletGroup,
21
+ BLOCKLET_MODES,
22
+ BLOCKLET_DEFAULT_PORT_NAME,
23
+ STATIC_SERVER_ENGINE_DID,
24
+ } = require('@blocklet/constant');
25
+ const { forEachBlocklet, hasStartEngine, isExternalBlocklet } = require('@blocklet/meta/lib/util');
26
+ const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
27
+ const { validateBlockletEntry } = require('@blocklet/meta/lib/entry');
28
+
29
+ const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
30
+
31
+ const parseDockerOptionsFromPm2 = require('../docker/parse-docker-options-from-pm2');
32
+ const dockerRemoveByName = require('../docker/docker-remove-by-name');
33
+ const { createDockerNetwork } = require('../docker/docker-network');
34
+ const parseDockerName = require('../docker/parse-docker-name');
35
+ const { ensureBun } = require('../ensure-bun');
36
+ const { installExternalDependencies } = require('../install-external-dependencies');
37
+
38
+ const SCRIPT_ENGINES_WHITE_LIST = ['npm', 'npx', 'pnpm', 'yarn'];
39
+
40
+ const startLock = new DBCache(() => ({
41
+ ...getAbtNodeRedisAndSQLiteUrl(),
42
+ prefix: 'blocklet-start-locks2',
43
+ ttl: 1000 * 60 * 3,
44
+ }));
45
+
46
+ const statusMap = {
47
+ online: BlockletStatus.running,
48
+ launching: BlockletStatus.starting,
49
+ errored: BlockletStatus.error,
50
+ stopping: BlockletStatus.stopping,
51
+ stopped: BlockletStatus.stopped,
52
+ 'waiting restart': BlockletStatus.restarting,
53
+ };
54
+
55
+ const noop = () => {
56
+ //
57
+ };
58
+ const noopAsync = async () => {
59
+ //
60
+ };
61
+
62
+ const isUsefulError = (err) =>
63
+ err &&
64
+ err.message !== 'process or namespace not found' &&
65
+ !/id unknown/.test(err.message) &&
66
+ !/^Process \d+ not found$/.test(err.message);
67
+
68
+ /**
69
+ * @param {*} processId
70
+ * @returns {BlockletStatus}
71
+ */
72
+ const getProcessState = async (processId) => {
73
+ // eslint-disable-next-line no-use-before-define
74
+ const info = await getProcessInfo(processId);
75
+ if (!statusMap[info.pm2_env.status]) {
76
+ logger.error('Cannot find the blocklet status for pm2 status mapping', {
77
+ pm2Status: info.pm2_env.status,
78
+ });
79
+
80
+ return BlockletStatus.error;
81
+ }
82
+
83
+ return statusMap[info.pm2_env.status];
84
+ };
85
+
86
+ const getProcessInfo = (processId, { throwOnNotExist = true, timeout = 10_000 } = {}) =>
87
+ getPm2ProcessInfo(processId, { printError: logger.error.bind(logger), throwOnNotExist, timeout });
88
+
89
+ const deleteProcess = (processId) => {
90
+ return new Promise((resolve, reject) => {
91
+ pm2.delete(processId, async (err) => {
92
+ if (isUsefulError(err)) {
93
+ logger.error('blocklet process delete failed', { processId, error: err });
94
+ return reject(err);
95
+ }
96
+ await dockerRemoveByName(processId);
97
+ return resolve(processId);
98
+ });
99
+ });
100
+ };
101
+
102
+ const reloadProcess = (processId) =>
103
+ new Promise((resolve, reject) => {
104
+ pm2.reload(processId, (err) => {
105
+ if (err) {
106
+ if (isUsefulError(err)) {
107
+ logger.error('blocklet reload failed', { processId, error: err });
108
+ }
109
+ return reject(err);
110
+ }
111
+ return resolve(processId);
112
+ });
113
+ });
114
+
115
+ const shouldSkipComponent = (componentDid, whiteList) => {
116
+ if (!whiteList || !Array.isArray(whiteList)) {
117
+ return false;
118
+ }
119
+
120
+ const arr = whiteList.filter(Boolean);
121
+
122
+ if (!arr.length) {
123
+ return false;
124
+ }
125
+
126
+ return !arr.includes(componentDid);
127
+ };
128
+
129
+ /**
130
+ * Start all precesses of a blocklet
131
+ * @param {*} blocklet should contain env props
132
+ * @param {object} options Start options
133
+ * @param {object} options.getComponentStartEngine Function to get component start engine
134
+ * @param {object} options.getRuntimeEnvironments Function to get runtime environments
135
+ */
136
+ const startBlockletProcess = async (
137
+ blocklet,
138
+ {
139
+ preFlight = noop,
140
+ preStart = noop,
141
+ postStart = noopAsync,
142
+ nodeEnvironments,
143
+ nodeInfo,
144
+ e2eMode,
145
+ skippedProcessIds = [],
146
+ componentDids,
147
+ configSynchronizer,
148
+ onlyStart = false,
149
+ isGreen = false,
150
+ getComponentStartEngine,
151
+ getRuntimeEnvironments,
152
+ } = {}
153
+ ) => {
154
+ if (!blocklet) {
155
+ throw new Error('blocklet should not be empty');
156
+ }
157
+
158
+ const dockerNetworkName = parseDockerName(blocklet?.meta?.did, 'docker-network');
159
+ await createDockerNetwork(dockerNetworkName);
160
+
161
+ blocklet.children.forEach((component) => {
162
+ if (!componentDids.includes(component.meta.did)) {
163
+ return;
164
+ }
165
+ for (const envItem of component.environments) {
166
+ const envKey = envItem.key || envItem.name;
167
+ if (envKey !== 'BLOCKLET_PORT') {
168
+ continue;
169
+ }
170
+ if (isGreen) {
171
+ if (component.greenPorts?.BLOCKLET_PORT) {
172
+ envItem.value = component.greenPorts.BLOCKLET_PORT;
173
+ }
174
+ } else if (component.ports?.BLOCKLET_PORT) {
175
+ envItem.value = component.ports.BLOCKLET_PORT;
176
+ }
177
+ }
178
+ });
179
+
180
+ const startBlockletTask = async (b, { ancestors }) => {
181
+ // 需要在在这里传入字符串类型,否则进程中如法转化成 Date 对象
182
+ const now = `${new Date()}`;
183
+ if (b.meta.group === BlockletGroup.gateway) {
184
+ return;
185
+ }
186
+
187
+ if (!hasStartEngine(b.meta)) {
188
+ return;
189
+ }
190
+
191
+ const { processId, logsDir, appDir } = b.env;
192
+
193
+ if (skippedProcessIds.includes(processId)) {
194
+ logger.info('skip start skipped process', { processId });
195
+ return;
196
+ }
197
+
198
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
199
+ logger.info('skip start process not selected', { processId });
200
+ return;
201
+ }
202
+
203
+ if (b.mode !== BLOCKLET_MODES.DEVELOPMENT) {
204
+ validateBlockletEntry(appDir, b.meta);
205
+ }
206
+
207
+ const { cwd, script, args, environmentObj, interpreter, interpreterArgs } = getComponentStartEngine(b, {
208
+ e2eMode,
209
+ });
210
+ if (!script) {
211
+ logger.info('skip start process without script', { processId });
212
+ return;
213
+ }
214
+
215
+ // get env
216
+ const env = getRuntimeEnvironments(b, nodeEnvironments, ancestors, isGreen);
217
+ const startedAt = Date.now();
218
+
219
+ await installExternalDependencies({ appDir: env?.BLOCKLET_APP_DIR, nodeInfo });
220
+ await preFlight(b, { env: { ...env } });
221
+ await preStart(b, { env: { ...env } });
222
+
223
+ // kill process if port is occupied
224
+ try {
225
+ const { ports, greenPorts } = b;
226
+ await killProcessOccupiedPorts({
227
+ ports: isGreen ? greenPorts : ports,
228
+ pm2ProcessId: processId,
229
+ printError: logger.error.bind(logger),
230
+ });
231
+ } catch (error) {
232
+ logger.error('Failed to killProcessOccupiedPorts', { error });
233
+ }
234
+
235
+ // start process
236
+ const maxMemoryRestart = get(nodeInfo, 'runtimeConfig.blockletMaxMemoryLimit', BLOCKLET_MAX_MEM_LIMIT_IN_MB);
237
+ const processIdName = isGreen ? `${processId}-green` : processId;
238
+ /**
239
+ * @type {pm2.StartOptions}
240
+ */
241
+ const options = {
242
+ namespace: 'blocklets',
243
+ name: processIdName,
244
+ cwd,
245
+ log_date_format: '(YYYY-MM-DD HH:mm:ss)',
246
+ output: path.join(logsDir, 'output.log'),
247
+ error: path.join(logsDir, 'error.log'),
248
+ // wait_ready: process.env.NODE_ENV !== 'test',
249
+ wait_ready: false,
250
+ listen_timeout: 3000,
251
+ max_memory_restart: `${maxMemoryRestart}M`,
252
+ max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
253
+ min_uptime: 10_000,
254
+ exp_backoff_restart_delay: 300,
255
+ env: omit(
256
+ {
257
+ ...environmentObj,
258
+ ...env,
259
+ NODE_ENV: 'production',
260
+ BLOCKLET_START_AT: now,
261
+ NODE_OPTIONS: await getSecurityNodeOptions(b, nodeInfo.enableFileSystemIsolation),
262
+ },
263
+ // should only inject appSk and appPsk to the blocklet environment when unsafe mode enabled
264
+ ['1', 1].includes(env.UNSAFE_MODE) ? [] : ['BLOCKLET_APP_SK', 'BLOCKLET_APP_PSK']
265
+ ),
266
+ script,
267
+ args,
268
+ interpreter,
269
+ interpreterArgs,
270
+ };
271
+
272
+ const clusterMode = get(b.meta, 'capabilities.clusterMode', false);
273
+ if (clusterMode && b.mode !== BLOCKLET_MODES.DEVELOPMENT) {
274
+ const clusterSize = Number(blocklet.configObj.BLOCKLET_CLUSTER_SIZE) || +process.env.ABT_NODE_MAX_CLUSTER_SIZE;
275
+ options.execMode = 'cluster';
276
+ options.mergeLogs = true;
277
+ options.instances = Math.max(Math.min(os.cpus().length, clusterSize), 1);
278
+ options.env.BLOCKLET_CLUSTER_SIZE = options.instances;
279
+ if (options.instances !== clusterSize) {
280
+ logger.warn(`Fallback cluster size to ${options.instances} for ${processId}, ignore custom ${clusterSize}`);
281
+ }
282
+ } else {
283
+ delete options.env.BLOCKLET_CLUSTER_SIZE;
284
+ }
285
+
286
+ if (b.mode === BLOCKLET_MODES.DEVELOPMENT) {
287
+ options.env.NODE_ENV = e2eMode ? 'e2e' : 'development';
288
+ options.env.IS_E2E = e2eMode ? '1' : undefined;
289
+ options.env.BROWSER = 'none';
290
+ options.env.PORT = options.env[BLOCKLET_DEFAULT_PORT_NAME];
291
+
292
+ if (process.platform === 'win32') {
293
+ const [cmd, ...argList] = options.script.split(' ').filter(Boolean);
294
+
295
+ if (!SCRIPT_ENGINES_WHITE_LIST.includes(cmd)) {
296
+ throw new Error(`${cmd} script is not supported, ${SCRIPT_ENGINES_WHITE_LIST.join(', ')} are supported`);
297
+ }
298
+
299
+ const { stdout: nodejsBinPath } = shelljs.which('node');
300
+
301
+ const cmdPath = path.join(path.dirname(nodejsBinPath), 'node_modules', cmd);
302
+
303
+ const pkg = JSON.parse(fs.readFileSync(path.join(cmdPath, 'package.json'), 'utf8'));
304
+ const cmdBinPath = pkg.bin[cmd];
305
+
306
+ options.script = path.resolve(cmdPath, cmdBinPath);
307
+ options.args = [...argList].join(' ');
308
+ }
309
+ }
310
+
311
+ await configSynchronizer.syncComponentConfig(b.meta.did, blocklet.meta.did, {
312
+ serverSk: nodeEnvironments.ABT_NODE_SK,
313
+ });
314
+
315
+ if (options.interpreter === 'bun') {
316
+ options.exec_interpreter = await ensureBun();
317
+ options.exec_mode = 'fork';
318
+ delete options.instances;
319
+ delete options.mergeLogs;
320
+ }
321
+
322
+ let nextOptions;
323
+ if (b.mode === BLOCKLET_MODES.DEVELOPMENT) {
324
+ nextOptions = options;
325
+ } else {
326
+ nextOptions = await parseDockerOptionsFromPm2({
327
+ options,
328
+ nodeInfo,
329
+ isExternal: isExternalBlocklet(b),
330
+ meta: b.meta,
331
+ ports: isGreen ? b.greenPorts : b.ports,
332
+ onlyStart,
333
+ dockerNetworkName,
334
+ rootBlocklet: blocklet,
335
+ });
336
+ }
337
+
338
+ await fetchPm2({ ...nextOptions, pmx: false }, nodeEnvironments.ABT_NODE_SK);
339
+
340
+ const status = await getProcessState(processIdName);
341
+ if (status === BlockletStatus.error) {
342
+ throw new Error(`process ${processIdName} is not running within 3 seconds`);
343
+ }
344
+ logger.info('done start blocklet', { processId: processIdName, status, time: Date.now() - startedAt });
345
+
346
+ if (nextOptions.env.connectInternalDockerNetwork) {
347
+ try {
348
+ await promiseSpawn(nextOptions.env.connectInternalDockerNetwork, { mute: true });
349
+ } catch (err) {
350
+ logger.warn('blocklet connect internal docker network failed', { processId: processIdName, error: err });
351
+ }
352
+ }
353
+
354
+ // run hook
355
+ postStart(b, { env }).catch((err) => {
356
+ logger.error('blocklet post start failed', { processId: processIdName, error: err });
357
+ });
358
+ };
359
+
360
+ await forEachBlocklet(
361
+ blocklet,
362
+ /**
363
+ *
364
+ * @param {import('@blocklet/server-js').BlockletState} b
365
+ * @param {*} param1
366
+ * @returns
367
+ */
368
+ async (b, { ancestors }) => {
369
+ const lockName = `${blocklet.meta.did}-${b.meta.did}`;
370
+
371
+ // 如果锁存在,则跳过执行
372
+ if (!(await startLock.hasExpired(lockName))) {
373
+ return;
374
+ }
375
+ await startLock.acquire(lockName);
376
+
377
+ try {
378
+ await startBlockletTask(b, { ancestors });
379
+ } finally {
380
+ startLock.releaseLock(lockName);
381
+ }
382
+ },
383
+ { parallel: true, concurrencyLimit: 3 }
384
+ );
385
+ };
386
+
387
+ /**
388
+ * Stop all precesses of a blocklet
389
+ * @param {*} blocklet should contain env props
390
+ */
391
+ const stopBlockletProcess = (
392
+ blocklet,
393
+ { preStop = noop, skippedProcessIds = [], componentDids, isGreen = false, isStopGreenAndBlue = false } = {}
394
+ ) => {
395
+ // eslint-disable-next-line no-use-before-define
396
+ return deleteBlockletProcess(blocklet, {
397
+ preDelete: preStop,
398
+ skippedProcessIds,
399
+ componentDids,
400
+ isGreen,
401
+ isStopGreenAndBlue,
402
+ });
403
+ };
404
+
405
+ /**
406
+ * Delete all precesses of a blocklet
407
+ * @param {*} blocklet should contain env props
408
+ */
409
+ const deleteBlockletProcess = async (
410
+ blocklet,
411
+ { preDelete = noop, skippedProcessIds = [], componentDids, isGreen = false, isStopGreenAndBlue = false } = {}
412
+ ) => {
413
+ await forEachBlocklet(
414
+ blocklet,
415
+ async (b, { ancestors }) => {
416
+ // NOTICE: 如果不判断 group, 在 github action 中测试 disk.spec.js 时会报错, 但是在 mac 中跑测试不会报错
417
+ if (b.meta?.group === BlockletGroup.gateway) {
418
+ return;
419
+ }
420
+
421
+ if (skippedProcessIds.includes(b.env.processId)) {
422
+ logger.info(`skip delete skipped process ${b.env.processId}`);
423
+ return;
424
+ }
425
+
426
+ if (shouldSkipComponent(b.meta?.did, componentDids)) {
427
+ logger.info(`skip delete process not selected: ${b.meta.did}`, { processId: b.env.processId });
428
+ return;
429
+ }
430
+
431
+ if (!hasStartEngine(b.meta)) {
432
+ return;
433
+ }
434
+
435
+ // Skip deleting static-server engine processes since they were never started
436
+ if (b.meta?.group === 'static') {
437
+ return;
438
+ }
439
+ const engine = getBlockletEngine(b.meta);
440
+ if (engine.interpreter === 'blocklet' && engine.source?.name === STATIC_SERVER_ENGINE_DID) {
441
+ return;
442
+ }
443
+
444
+ await preDelete(b, { ancestors });
445
+ if (isStopGreenAndBlue) {
446
+ await deleteProcess(`${b.env.processId}-green`);
447
+ await deleteProcess(b.env.processId);
448
+ return;
449
+ }
450
+ const processId = isGreen ? `${b.env.processId}-green` : b.env.processId;
451
+ await deleteProcess(processId);
452
+ },
453
+ { parallel: true }
454
+ );
455
+ };
456
+
457
+ /**
458
+ * Reload all precesses of a blocklet
459
+ * @param {*} blocklet should contain env props
460
+ */
461
+ const reloadBlockletProcess = (blocklet, { componentDids } = {}) =>
462
+ forEachBlocklet(
463
+ blocklet,
464
+ async (b) => {
465
+ if (b.meta.group === BlockletGroup.gateway) {
466
+ return;
467
+ }
468
+
469
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
470
+ logger.info('skip reload process', { processId: b.env.processId });
471
+ return;
472
+ }
473
+
474
+ // Skip reloading static-server engine processes since they were never started
475
+ if (b.meta?.group === 'static') {
476
+ return;
477
+ }
478
+ const engine = getBlockletEngine(b.meta);
479
+ if (engine.interpreter === 'blocklet' && engine.source?.name === STATIC_SERVER_ENGINE_DID) {
480
+ return;
481
+ }
482
+
483
+ await reloadProcess(b.env.processId);
484
+ logger.info('done reload process', { processId: b.env.processId });
485
+ },
486
+ { parallel: false }
487
+ );
488
+
489
+ module.exports = {
490
+ startBlockletProcess,
491
+ stopBlockletProcess,
492
+ deleteBlockletProcess,
493
+ reloadBlockletProcess,
494
+ getProcessState,
495
+ getProcessInfo,
496
+ deleteProcess,
497
+ reloadProcess,
498
+ shouldSkipComponent,
499
+ startLock,
500
+ statusMap,
501
+ noop,
502
+ noopAsync,
503
+ isUsefulError,
504
+ };