@abtnode/core 1.17.3-beta-20251123-232619-53258789 → 1.17.3-beta-20251126-121502-d0926972

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/cert.js CHANGED
@@ -118,7 +118,16 @@ class Cert extends EventEmitter {
118
118
  }
119
119
 
120
120
  update(data) {
121
- return this.manager.update(data.id, { name: data.name, public: data.public });
121
+ if (data.certificate && data.privateKey) {
122
+ Cert.fixCertificate(data);
123
+ }
124
+
125
+ return this.manager.update(data.id, {
126
+ name: data.name,
127
+ public: data.public,
128
+ certificate: data.certificate,
129
+ privateKey: data.privateKey,
130
+ });
122
131
  }
123
132
 
124
133
  async remove({ id }) {
@@ -25,7 +25,7 @@ const {
25
25
  BLOCKLET_DEFAULT_PORT_NAME,
26
26
  BlockletGroup,
27
27
  } = require('@blocklet/constant');
28
- const { refreshPorts } = require('@abtnode/util/lib/port');
28
+ const { isPortTaken } = require('@abtnode/util/lib/port');
29
29
  const { verifyVault } = require('@blocklet/meta/lib/security');
30
30
  const { APP_STRUCT_VERSION } = require('@abtnode/constant');
31
31
 
@@ -42,6 +42,97 @@ const lock = new DBCache(() => ({
42
42
  ...getAbtNodeRedisAndSQLiteUrl(),
43
43
  }));
44
44
 
45
+ // Lock for port assignment to prevent race conditions in multi-process environment
46
+ const portAssignLock = new DBCache(() => ({
47
+ prefix: 'blocklet-port-assign-lock',
48
+ ttl: 1000 * 30, // 30 seconds timeout
49
+ ...getAbtNodeRedisAndSQLiteUrl(),
50
+ }));
51
+
52
+ /**
53
+ * 统一的端口分配方法
54
+ * 无论是首次分配还是刷新端口,都使用这个方法
55
+ * @param {Object} options
56
+ * @param {Array<string>} options.wantedPorts - 需要分配的端口名称列表
57
+ * @param {Object} options.blackListPorts - 黑名单端口(已占用的端口)
58
+ * @param {Array<number>} options.portRange - 端口范围,默认 [10000, 31000]
59
+ * @param {number} options.defaultPort - 回退时的起始端口
60
+ * @returns {Promise<Object>} 分配的端口对象 { portName: portNumber }
61
+ */
62
+ async function assignPortsWithLock({
63
+ wantedPorts,
64
+ blackListPorts = [],
65
+ portRange = [10000, 31000],
66
+ defaultPort = 5555,
67
+ }) {
68
+ const lockName = 'blocklet-port-assign';
69
+
70
+ // ⚠️ 关键修复:使用 DBCache 锁确保端口分配的原子性
71
+ // 在多进程环境下,防止多个进程同时分配端口导致冲突
72
+ await portAssignLock.acquire(lockName);
73
+
74
+ try {
75
+ const assignedPorts = {};
76
+ const usedPorts = [...blackListPorts];
77
+
78
+ for (let i = 0; i < wantedPorts.length; i++) {
79
+ const wantedPort = wantedPorts[i];
80
+
81
+ let found = false;
82
+ let assignedPort = null;
83
+ let attempts = 0;
84
+ const maxAttempts = 100;
85
+
86
+ while (!found && attempts < maxAttempts) {
87
+ // 在 [10000, 31000] 范围内随机选择端口
88
+ const randomPort = Math.floor(Math.random() * (portRange[1] - portRange[0])) + portRange[0];
89
+
90
+ if (!usedPorts.includes(randomPort)) {
91
+ // eslint-disable-next-line no-await-in-loop
92
+ const isTaken = await isPortTaken(randomPort);
93
+ if (!isTaken) {
94
+ assignedPort = randomPort;
95
+ usedPorts.push(assignedPort);
96
+ found = true;
97
+ }
98
+ }
99
+ attempts++;
100
+ }
101
+
102
+ // 如果随机方式失败,回退到累加方式
103
+ if (!found) {
104
+ logger.warn('Random port assignment failed, falling back to sequential assignment', {
105
+ wantedPort,
106
+ attempts,
107
+ });
108
+ let port = defaultPort + 1;
109
+ // eslint-disable-next-line no-await-in-loop
110
+ let fallbackPort = await detectPort(Number(port));
111
+ while (usedPorts.includes(fallbackPort)) {
112
+ port = fallbackPort + 1;
113
+ while (usedPorts.includes(port)) {
114
+ port++;
115
+ }
116
+ // eslint-disable-next-line no-await-in-loop
117
+ fallbackPort = await detectPort(Number(port));
118
+ }
119
+ assignedPort = fallbackPort;
120
+ usedPorts.push(assignedPort);
121
+ }
122
+
123
+ // set wantedPort to assignedPort
124
+ assignedPorts[wantedPort] = assignedPort;
125
+ }
126
+
127
+ return assignedPorts;
128
+ } catch (err) {
129
+ logger.error('Failed to assign ports with lock', { error: err });
130
+ throw err;
131
+ } finally {
132
+ await portAssignLock.releaseLock(lockName);
133
+ }
134
+ }
135
+
45
136
  const isHex = (str) => /^0x[0-9a-f]+$/i.test(str);
46
137
  const getMaxPort = (ports = {}) => Math.max(...Object.values(ports).map(Number));
47
138
 
@@ -436,31 +527,29 @@ class BlockletState extends BaseState {
436
527
  wantedPorts.push(BLOCKLET_DEFAULT_PORT_NAME);
437
528
  }
438
529
 
439
- const assignedPorts = Object.assign({}, alreadyAssigned);
440
- let port = Math.max(Number(this.defaultPort), defaultPort) + 1;
441
-
442
- for (let i = 0; i < wantedPorts.length; i++) {
443
- const wantedPort = wantedPorts[i];
444
-
445
- if (typeof alreadyAssigned[wantedPort] === 'undefined') {
446
- // find assignedPort not exist in occupiedInternalPorts
447
- let assignedPort = await detectPort(Number(port));
448
- while (occupiedInternalPorts.has(assignedPort)) {
449
- port = assignedPort + 1;
450
- while (occupiedInternalPorts.has(port)) {
451
- port++;
452
- }
453
- assignedPort = await detectPort(Number(port));
454
- }
455
-
456
- // set wantedPort to assignedPort
457
- assignedPorts[wantedPort] = assignedPort;
530
+ // 过滤出需要分配的端口(排除已分配的)
531
+ const portsToAssign = wantedPorts.filter((port) => typeof alreadyAssigned[port] === 'undefined');
458
532
 
459
- port = assignedPort + 1;
460
- }
533
+ if (portsToAssign.length === 0) {
534
+ return alreadyAssigned;
461
535
  }
462
536
 
463
- return assignedPorts;
537
+ const blackListPorts = [
538
+ ...Array.from(occupiedExternalPorts.keys()),
539
+ ...Array.from(occupiedInternalPorts.keys()),
540
+ ...Object.values(alreadyAssigned),
541
+ ];
542
+
543
+ // 使用统一的端口分配方法(包含锁机制)
544
+ const newPorts = await assignPortsWithLock({
545
+ wantedPorts: portsToAssign,
546
+ blackListPorts,
547
+ portRange: [10000, 31000],
548
+ defaultPort: Math.max(Number(this.defaultPort), defaultPort),
549
+ });
550
+
551
+ // 合并已分配的端口和新分配的端口
552
+ return Object.assign({}, alreadyAssigned, newPorts);
464
553
  } catch (err) {
465
554
  logger.error('Failed to assign port to blocklet', { error: err });
466
555
  throw err;
@@ -469,6 +558,7 @@ class BlockletState extends BaseState {
469
558
 
470
559
  /**
471
560
  * refresh ports for blocklet if occupied during starting workflow
561
+ * @returns {Promise<{refreshed: boolean, componentDids: string[], isInitialAssignment?: boolean}>} 返回是否真正刷新了端口,isInitialAssignment 表示是否是首次分配(非冲突刷新)
472
562
  */
473
563
  async refreshBlockletPorts(did, componentDids = [], isGreen = false) {
474
564
  const blocklet = await this.getBlocklet(did);
@@ -477,22 +567,79 @@ class BlockletState extends BaseState {
477
567
  }
478
568
 
479
569
  const { occupiedExternalPorts, occupiedInternalPorts } = await this._getOccupiedPorts();
570
+ const blackListPorts = [...Array.from(occupiedExternalPorts.keys()), ...Array.from(occupiedInternalPorts.keys())];
571
+
572
+ const actuallyRefreshedDids = [];
573
+ let hasInitialAssignment = false; // 标记是否有首次分配(非冲突刷新)
480
574
 
481
575
  await forEachComponentV2(blocklet, async (component) => {
482
576
  if (!shouldSkipComponent(component.meta.did, componentDids)) {
483
577
  let oldPorts = component[isGreen ? 'greenPorts' : 'ports'];
484
- if (!oldPorts || Object.keys(oldPorts).length === 0) {
578
+ const hasOldPorts = oldPorts && Object.keys(oldPorts).length > 0;
579
+
580
+ // 如果是 green 环境且 greenPorts 为空,需要分配新端口(基于 ports 的端口名)
581
+ // 这是首次分配,不是冲突刷新
582
+ if (isGreen && !hasOldPorts) {
583
+ // 从 ports 获取端口名,分配新的 green 端口
584
+ const basePorts = component.ports || {};
585
+ if (Object.keys(basePorts).length > 0) {
586
+ const wantedPorts = Object.keys(basePorts);
587
+ const newPorts = await assignPortsWithLock({
588
+ wantedPorts,
589
+ blackListPorts: [...blackListPorts, ...Object.values(basePorts)],
590
+ portRange: [10000, 31000],
591
+ defaultPort: this.defaultPort,
592
+ });
593
+
594
+ component.greenPorts = newPorts;
595
+ actuallyRefreshedDids.push(component.meta.did);
596
+ hasInitialAssignment = true; // 标记为首次分配
597
+ }
598
+ return;
599
+ }
600
+
601
+ // 如果 oldPorts 为空,回退到 ports
602
+ if (!hasOldPorts) {
485
603
  oldPorts = component.ports;
486
604
  }
487
- component[isGreen ? 'greenPorts' : 'ports'] = await refreshPorts(oldPorts, {
488
- blackList: [...occupiedExternalPorts.keys(), ...occupiedInternalPorts.keys()],
489
- });
605
+
606
+ // 检查旧端口是否真的被占用
607
+ let needReassign = false;
608
+ for (const port of Object.values(oldPorts)) {
609
+ const isTaken = await isPortTaken(port);
610
+ if (isTaken) {
611
+ needReassign = true;
612
+ break;
613
+ }
614
+ }
615
+
616
+ // 只有旧端口被占用时才分配新端口
617
+ if (needReassign) {
618
+ const wantedPorts = Object.keys(oldPorts);
619
+ const newPorts = await assignPortsWithLock({
620
+ wantedPorts,
621
+ blackListPorts: [...blackListPorts, ...Object.values(oldPorts)],
622
+ portRange: [10000, 31000],
623
+ defaultPort: this.defaultPort,
624
+ });
625
+
626
+ component[isGreen ? 'greenPorts' : 'ports'] = newPorts;
627
+ actuallyRefreshedDids.push(component.meta.did);
628
+ }
490
629
  }
491
630
  });
492
631
 
493
- return this.updateBlocklet(did, {
494
- children: blocklet.children,
495
- });
632
+ if (actuallyRefreshedDids.length > 0) {
633
+ await this.updateBlocklet(did, {
634
+ children: blocklet.children,
635
+ });
636
+ }
637
+
638
+ return {
639
+ refreshed: actuallyRefreshedDids.length > 0,
640
+ componentDids: actuallyRefreshedDids,
641
+ isInitialAssignment: hasInitialAssignment, // 返回是否是首次分配
642
+ };
496
643
  }
497
644
 
498
645
  async getServices() {
@@ -136,6 +136,92 @@ const parseDockerName = require('./docker/parse-docker-name');
136
136
  const { createDockerNetwork } = require('./docker/docker-network');
137
137
  const { ensureBun } = require('./ensure-bun');
138
138
 
139
+ /**
140
+ * Get actual listening port from Docker container or process
141
+ * @param {string} processId - PM2 process ID
142
+ * @param {object} blocklet - Blocklet object with meta and env
143
+ * @returns {Promise<number|null>} Actual port number or null if not found
144
+ */
145
+ const getActualListeningPort = async (processId, blocklet) => {
146
+ try {
147
+ // eslint-disable-next-line no-use-before-define
148
+ const info = await getProcessInfo(processId, { timeout: 3_000 });
149
+ const dockerName = info.pm2_env?.env?.dockerName;
150
+
151
+ if (dockerName) {
152
+ // For Docker containers, get actual port from docker inspect
153
+ try {
154
+ // Get port mapping from docker inspect
155
+ const inspectCmd = `docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{$p}} {{end}}' ${dockerName}`;
156
+ const portMappings = await promiseSpawn(inspectCmd, { mute: true });
157
+
158
+ if (portMappings) {
159
+ const ports = portMappings.trim().split(/\s+/).filter(Boolean);
160
+ for (const portMapping of ports) {
161
+ const port = parseInt(portMapping.split('/')[0], 10);
162
+ if (port && !Number.isNaN(port)) {
163
+ try {
164
+ const portCmd = `docker port ${dockerName} ${portMapping}`;
165
+ const hostPortOutput = await promiseSpawn(portCmd, { mute: true });
166
+ const match = hostPortOutput.match(/:(\d+)$/);
167
+ if (match) {
168
+ const actualPort = parseInt(match[1], 10);
169
+ if (actualPort && !Number.isNaN(actualPort)) {
170
+ logger.info('Got actual Docker port from container', {
171
+ processId,
172
+ dockerName,
173
+ containerPort: port,
174
+ hostPort: actualPort,
175
+ });
176
+ return actualPort;
177
+ }
178
+ }
179
+ } catch (err) {
180
+ logger.debug('Failed to get port from docker port command', { error: err.message });
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ // Fallback: try to get from NetworkSettings.Ports directly
187
+ const inspectPortsCmd = `docker inspect --format='{{json .NetworkSettings.Ports}}' ${dockerName}`;
188
+ const portsJson = await promiseSpawn(inspectPortsCmd, { mute: true });
189
+ if (portsJson) {
190
+ const ports = JSON.parse(portsJson);
191
+ // Find the primary port (usually BLOCKLET_PORT)
192
+ const webInterface = (blocklet?.meta?.interfaces || []).find(
193
+ (x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB || x.type === BLOCKLET_INTERFACE_TYPE_DOCKER
194
+ );
195
+ const expectedContainerPort = webInterface?.containerPort || webInterface?.port;
196
+
197
+ if (expectedContainerPort) {
198
+ const portKey = `${expectedContainerPort}/tcp`;
199
+ if (ports[portKey] && ports[portKey][0]) {
200
+ const hostPort = parseInt(ports[portKey][0].HostPort, 10);
201
+ if (hostPort && !Number.isNaN(hostPort)) {
202
+ logger.info('Got actual Docker port from NetworkSettings', {
203
+ processId,
204
+ dockerName,
205
+ containerPort: expectedContainerPort,
206
+ hostPort,
207
+ });
208
+ return hostPort;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ } catch (error) {
214
+ logger.debug('Failed to get Docker port mapping', { error: error.message, processId, dockerName });
215
+ }
216
+ }
217
+
218
+ return null;
219
+ } catch (error) {
220
+ logger.debug('Failed to get actual listening port', { error: error.message, processId });
221
+ return null;
222
+ }
223
+ };
224
+
139
225
  /**
140
226
  * get blocklet engine info, default is node
141
227
  * @param {object} meta blocklet meta
@@ -149,6 +235,13 @@ const startLock = new DBCache(() => ({
149
235
  ttl: 1000 * 60 * 3,
150
236
  }));
151
237
 
238
+ // Lock for port assignment to prevent race conditions in multi-process environment
239
+ const portAssignLock = new DBCache(() => ({
240
+ ...getAbtNodeRedisAndSQLiteUrl(),
241
+ prefix: 'blocklet-port-assign-lock',
242
+ ttl: 1000 * 30, // 30 seconds timeout
243
+ }));
244
+
152
245
  const blockletCache = new DBCache(() => ({
153
246
  prefix: 'blocklet-state',
154
247
  ttl: BLOCKLET_CACHE_TTL,
@@ -1122,18 +1215,46 @@ const _checkProcessHealthy = async (
1122
1215
  throw new Error('process not start within 10s');
1123
1216
  }
1124
1217
 
1218
+ // ⚠️ 关键修复:优先从进程实际监听的端口获取
1219
+ // 对于 Docker 容器,从 Docker 端口映射获取实际端口
1220
+ // 这样可以避免端口刷新后,健康检查使用错误的端口
1221
+ const actualPort = await getActualListeningPort(processId, blocklet);
1222
+
1223
+ // 端口优先级:实际端口 > pm2 环境变量端口 > 数据库中的端口
1125
1224
  const port =
1225
+ actualPort ||
1126
1226
  envPort ||
1127
1227
  findInterfacePortByName({ meta, ports: isGreen ? greenPorts : ports }, (webInterface || dockerInterface).name);
1228
+
1128
1229
  if (logToTerminal) {
1129
1230
  // eslint-disable-next-line no-console
1130
- console.log(
1131
- `Checking endpoint healthy for ${meta.title}, port: ${port}, minConsecutiveTime: ${
1231
+ logger.info(
1232
+ // eslint-disable-next-line no-nested-ternary
1233
+ `Checking endpoint healthy for ${meta.title}, port: ${port}${actualPort ? ' (actual)' : envPort ? ' (from pm2 env)' : ' (from db)'}, minConsecutiveTime: ${
1132
1234
  minConsecutiveTime / 1000
1133
1235
  }s, timeout: ${timeout / 1000}s`
1134
1236
  );
1135
1237
  }
1136
1238
 
1239
+ if (
1240
+ actualPort &&
1241
+ actualPort !== envPort &&
1242
+ actualPort !==
1243
+ (isGreen
1244
+ ? greenPorts?.[webInterface?.port || dockerInterface?.port]
1245
+ : ports?.[webInterface?.port || dockerInterface?.port])
1246
+ ) {
1247
+ logger.info('Port mismatch detected, using actual port for health check', {
1248
+ processId,
1249
+ appDid,
1250
+ actualPort,
1251
+ envPort,
1252
+ dbPort: isGreen
1253
+ ? greenPorts?.[webInterface?.port || dockerInterface?.port]
1254
+ : ports?.[webInterface?.port || dockerInterface?.port],
1255
+ });
1256
+ }
1257
+
1137
1258
  try {
1138
1259
  await ensureEndpointHealthy({
1139
1260
  port,
@@ -2440,50 +2561,89 @@ const ensurePortsShape = (_states, portsA, portsB) => {
2440
2561
 
2441
2562
  const ensureAppPortsNotOccupied = async ({ blocklet, componentDids: inputDids, states, manager, isGreen = false }) => {
2442
2563
  const { did } = blocklet.meta;
2443
- const occupiedDids = new Set();
2564
+ const lockName = `port-check-${did}`;
2444
2565
 
2445
- await forEachComponentV2(blocklet, async (b) => {
2446
- try {
2447
- if (shouldSkipComponent(b.meta.did, inputDids)) return;
2566
+ // ⚠️ 关键修复:使用 DBCache 锁确保端口分配的原子性
2567
+ // 在多进程环境下,防止多个进程同时检查同一个端口
2568
+ await portAssignLock.acquire(lockName);
2448
2569
 
2449
- if (!b.greenPorts) {
2450
- occupiedDids.add(b.meta.did);
2451
- b.greenPorts = {};
2452
- }
2453
- const { ports = {}, greenPorts } = b;
2454
- ensurePortsShape(states, ports, greenPorts);
2570
+ try {
2571
+ const occupiedDids = new Set();
2572
+
2573
+ await forEachComponentV2(blocklet, async (b) => {
2574
+ try {
2575
+ if (shouldSkipComponent(b.meta.did, inputDids)) return;
2576
+
2577
+ if (!b.greenPorts) {
2578
+ occupiedDids.add(b.meta.did);
2579
+ b.greenPorts = {};
2580
+ }
2581
+ const { ports = {}, greenPorts } = b;
2582
+ ensurePortsShape(states, ports, greenPorts);
2583
+
2584
+ const targetPorts = isGreen ? greenPorts : ports;
2455
2585
 
2456
- const targetPorts = isGreen ? greenPorts : ports;
2586
+ let currentOccupied = false;
2587
+ for (const port of Object.values(targetPorts)) {
2588
+ currentOccupied = await isPortTaken(port);
2589
+ if (currentOccupied) {
2590
+ break;
2591
+ }
2592
+ }
2457
2593
 
2458
- let currentOccupied = false;
2459
- for (const port of Object.values(targetPorts)) {
2460
- currentOccupied = await isPortTaken(port);
2461
2594
  if (currentOccupied) {
2462
- break;
2595
+ occupiedDids.add(b.meta.did);
2463
2596
  }
2597
+ } catch (error) {
2598
+ logger.error('Failed to check ports occupied', { error, blockletDid: b.meta.did, isGreen });
2464
2599
  }
2600
+ });
2465
2601
 
2466
- if (currentOccupied) {
2467
- occupiedDids.add(b.meta.did);
2602
+ if (occupiedDids.size === 0) {
2603
+ logger.info('No occupied ports detected, no refresh needed', { did, isGreen });
2604
+ return blocklet;
2605
+ }
2606
+
2607
+ const componentDids = Array.from(occupiedDids);
2608
+ const {
2609
+ refreshed,
2610
+ componentDids: actuallyRefreshedDids,
2611
+ isInitialAssignment,
2612
+ } = await states.blocklet.refreshBlockletPorts(did, componentDids, isGreen);
2613
+
2614
+ // 只有真正刷新了端口才打印日志和更新环境
2615
+ if (refreshed && actuallyRefreshedDids.length > 0) {
2616
+ // 区分首次分配和冲突刷新,使用不同的日志信息
2617
+ if (isInitialAssignment) {
2618
+ logger.info('Assigned green ports for blue-green deployment', {
2619
+ did,
2620
+ componentDids: actuallyRefreshedDids,
2621
+ isGreen,
2622
+ });
2623
+ } else {
2624
+ logger.info('Refreshed component ports due to conflict', {
2625
+ did,
2626
+ componentDids: actuallyRefreshedDids,
2627
+ isGreen,
2628
+ });
2468
2629
  }
2469
- } catch (error) {
2470
- logger.error('Failed to check ports occupied', { error, blockletDid: b.meta.did, isGreen });
2471
- }
2472
- });
2473
2630
 
2474
- if (occupiedDids.size === 0) {
2475
- logger.info('No occupied ports detected, no refresh needed');
2476
- return blocklet;
2477
- }
2631
+ await manager._updateBlockletEnvironment(did);
2632
+ const newBlocklet = await manager.ensureBlocklet(did);
2478
2633
 
2479
- const componentDids = Array.from(occupiedDids);
2480
- await states.blocklet.refreshBlockletPorts(did, componentDids, isGreen);
2481
- logger.info('Refreshed component ports due to conflict', { did, componentDids });
2634
+ return newBlocklet;
2635
+ }
2482
2636
 
2483
- await manager._updateBlockletEnvironment(did);
2484
- const newBlocklet = await manager.ensureBlocklet(did);
2637
+ logger.info('Ports were detected as occupied but not actually occupied during refresh, no refresh needed', {
2638
+ did,
2639
+ componentDids,
2640
+ isGreen,
2641
+ });
2485
2642
 
2486
- return newBlocklet;
2643
+ return blocklet;
2644
+ } finally {
2645
+ await portAssignLock.releaseLock(lockName);
2646
+ }
2487
2647
  };
2488
2648
 
2489
2649
  const getComponentNamesWithVersion = (app = {}, componentDids = []) => {
@@ -62,6 +62,7 @@ async function _ensureDockerPostgres(dataDir, name = 'abtnode-postgres', port =
62
62
  }
63
63
 
64
64
  const postgresVersion = process.env.ABT_NODE_POSTGRES_VERSION || '17.5';
65
+ const postgresMaxConnections = process.env.ABT_NODE_POSTGRES_MAX_CONNECTIONS || '500';
65
66
 
66
67
  const runCmd = [
67
68
  'docker run -d',
@@ -74,7 +75,7 @@ async function _ensureDockerPostgres(dataDir, name = 'abtnode-postgres', port =
74
75
  '-e POSTGRES_USER=postgres',
75
76
  '-e POSTGRES_DB=postgres',
76
77
  `postgres:${postgresVersion}`,
77
- '-c max_connections=200',
78
+ `-c max_connections=${postgresMaxConnections}`,
78
79
  ].join(' ');
79
80
 
80
81
  const url = `postgresql://postgres:postgres@localhost:${port}/postgres`;
@@ -33,7 +33,7 @@ async function installExternalDependencies({ appDir, forceInstall = false, nodeI
33
33
  }
34
34
 
35
35
  if (!(await isSameOs(appDir, isUseDocker))) {
36
- fs.removeSync(path.join(appDir, 'node_modules'));
36
+ fs.removeSync(path.join(appDir, 'node_modules'), { recursive: true });
37
37
  }
38
38
 
39
39
  const isNeedInstall = forceInstall || externals.some((dependency) => !isDependencyInstalled(appDir, dependency));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.3-beta-20251123-232619-53258789",
6
+ "version": "1.17.3-beta-20251126-121502-d0926972",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -17,46 +17,46 @@
17
17
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
18
18
  "license": "Apache-2.0",
19
19
  "dependencies": {
20
- "@abtnode/analytics": "1.17.3-beta-20251123-232619-53258789",
21
- "@abtnode/auth": "1.17.3-beta-20251123-232619-53258789",
22
- "@abtnode/certificate-manager": "1.17.3-beta-20251123-232619-53258789",
23
- "@abtnode/constant": "1.17.3-beta-20251123-232619-53258789",
24
- "@abtnode/cron": "1.17.3-beta-20251123-232619-53258789",
25
- "@abtnode/db-cache": "1.17.3-beta-20251123-232619-53258789",
26
- "@abtnode/docker-utils": "1.17.3-beta-20251123-232619-53258789",
27
- "@abtnode/logger": "1.17.3-beta-20251123-232619-53258789",
28
- "@abtnode/models": "1.17.3-beta-20251123-232619-53258789",
29
- "@abtnode/queue": "1.17.3-beta-20251123-232619-53258789",
30
- "@abtnode/rbac": "1.17.3-beta-20251123-232619-53258789",
31
- "@abtnode/router-provider": "1.17.3-beta-20251123-232619-53258789",
32
- "@abtnode/static-server": "1.17.3-beta-20251123-232619-53258789",
33
- "@abtnode/timemachine": "1.17.3-beta-20251123-232619-53258789",
34
- "@abtnode/util": "1.17.3-beta-20251123-232619-53258789",
35
- "@aigne/aigne-hub": "^0.10.9",
36
- "@arcblock/did": "^1.27.7",
37
- "@arcblock/did-connect-js": "^1.27.7",
38
- "@arcblock/did-ext": "^1.27.7",
20
+ "@abtnode/analytics": "1.17.3-beta-20251126-121502-d0926972",
21
+ "@abtnode/auth": "1.17.3-beta-20251126-121502-d0926972",
22
+ "@abtnode/certificate-manager": "1.17.3-beta-20251126-121502-d0926972",
23
+ "@abtnode/constant": "1.17.3-beta-20251126-121502-d0926972",
24
+ "@abtnode/cron": "1.17.3-beta-20251126-121502-d0926972",
25
+ "@abtnode/db-cache": "1.17.3-beta-20251126-121502-d0926972",
26
+ "@abtnode/docker-utils": "1.17.3-beta-20251126-121502-d0926972",
27
+ "@abtnode/logger": "1.17.3-beta-20251126-121502-d0926972",
28
+ "@abtnode/models": "1.17.3-beta-20251126-121502-d0926972",
29
+ "@abtnode/queue": "1.17.3-beta-20251126-121502-d0926972",
30
+ "@abtnode/rbac": "1.17.3-beta-20251126-121502-d0926972",
31
+ "@abtnode/router-provider": "1.17.3-beta-20251126-121502-d0926972",
32
+ "@abtnode/static-server": "1.17.3-beta-20251126-121502-d0926972",
33
+ "@abtnode/timemachine": "1.17.3-beta-20251126-121502-d0926972",
34
+ "@abtnode/util": "1.17.3-beta-20251126-121502-d0926972",
35
+ "@aigne/aigne-hub": "^0.10.10",
36
+ "@arcblock/did": "^1.27.12",
37
+ "@arcblock/did-connect-js": "^1.27.12",
38
+ "@arcblock/did-ext": "^1.27.12",
39
39
  "@arcblock/did-motif": "^1.1.14",
40
- "@arcblock/did-util": "^1.27.7",
41
- "@arcblock/event-hub": "^1.27.7",
42
- "@arcblock/jwt": "^1.27.7",
40
+ "@arcblock/did-util": "^1.27.12",
41
+ "@arcblock/event-hub": "^1.27.12",
42
+ "@arcblock/jwt": "^1.27.12",
43
43
  "@arcblock/pm2-events": "^0.0.5",
44
- "@arcblock/validator": "^1.27.7",
45
- "@arcblock/vc": "^1.27.7",
46
- "@blocklet/constant": "1.17.3-beta-20251123-232619-53258789",
47
- "@blocklet/did-space-js": "^1.2.4",
48
- "@blocklet/env": "1.17.3-beta-20251123-232619-53258789",
44
+ "@arcblock/validator": "^1.27.12",
45
+ "@arcblock/vc": "^1.27.12",
46
+ "@blocklet/constant": "1.17.3-beta-20251126-121502-d0926972",
47
+ "@blocklet/did-space-js": "^1.2.6",
48
+ "@blocklet/env": "1.17.3-beta-20251126-121502-d0926972",
49
49
  "@blocklet/error": "^0.3.3",
50
- "@blocklet/meta": "1.17.3-beta-20251123-232619-53258789",
51
- "@blocklet/resolver": "1.17.3-beta-20251123-232619-53258789",
52
- "@blocklet/sdk": "1.17.3-beta-20251123-232619-53258789",
53
- "@blocklet/server-js": "1.17.3-beta-20251123-232619-53258789",
54
- "@blocklet/store": "1.17.3-beta-20251123-232619-53258789",
55
- "@blocklet/theme": "^3.2.6",
50
+ "@blocklet/meta": "1.17.3-beta-20251126-121502-d0926972",
51
+ "@blocklet/resolver": "1.17.3-beta-20251126-121502-d0926972",
52
+ "@blocklet/sdk": "1.17.3-beta-20251126-121502-d0926972",
53
+ "@blocklet/server-js": "1.17.3-beta-20251126-121502-d0926972",
54
+ "@blocklet/store": "1.17.3-beta-20251126-121502-d0926972",
55
+ "@blocklet/theme": "^3.2.10",
56
56
  "@fidm/x509": "^1.2.1",
57
- "@ocap/mcrypto": "^1.27.7",
58
- "@ocap/util": "^1.27.7",
59
- "@ocap/wallet": "^1.27.7",
57
+ "@ocap/mcrypto": "^1.27.12",
58
+ "@ocap/util": "^1.27.12",
59
+ "@ocap/wallet": "^1.27.12",
60
60
  "@slack/webhook": "^7.0.6",
61
61
  "archiver": "^7.0.1",
62
62
  "axios": "^1.7.9",
@@ -116,5 +116,5 @@
116
116
  "express": "^4.18.2",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "d37497a49c982e82ba60249ed06c1b73f340b893"
119
+ "gitHead": "7039cacaad2a14a9573371e24e57cbbd6b6525c8"
120
120
  }