plugin-cluster-manager 1.1.15 → 1.1.17
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/dist/client/index.js +1 -1
- package/dist/client-v2/376.cd1d86e85a50088e.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/locale/en-US.json +16 -6
- package/dist/locale/vi-VN.json +16 -6
- package/dist/locale/zh-CN.json +16 -6
- package/dist/server/actions/cluster-nodes.js +44 -11
- package/dist/server/actions/doctor.js +73 -7
- package/dist/server/actions/event-queue-monitor.js +33 -3
- package/dist/server/actions/orchestrator.js +48 -32
- package/dist/server/actions/queue-mappings.js +1 -0
- package/dist/server/actions/tasks.js +8 -8
- package/dist/server/adapters/redis-event-queue-adapter.js +188 -0
- package/dist/server/adapters/redis-node-registry.js +44 -10
- package/dist/server/collections/orchestrator-stacks.js +6 -0
- package/dist/server/collections/worker-queue-mappings.js +1 -1
- package/dist/server/orchestrator/PackageManager.js +47 -12
- package/dist/server/plugin.js +37 -6
- package/dist/server/queue-scanner.js +54 -34
- package/dist/server/utils/node.js +3 -7
- package/dist/server/utils/redis.js +37 -9
- package/dist/shared/worker-processes.js +233 -0
- package/package.json +1 -1
- package/src/client/ClusterNodes.tsx +76 -10
- package/src/client/ContainerOrchestrator.tsx +146 -8
- package/src/client/QueueAssignment.tsx +10 -2
- package/src/locale/en-US.json +16 -6
- package/src/locale/vi-VN.json +16 -6
- package/src/locale/zh-CN.json +16 -6
- package/src/server/__tests__/worker-processes.test.ts +42 -0
- package/src/server/actions/cluster-nodes.ts +43 -8
- package/src/server/actions/doctor.ts +77 -0
- package/src/server/actions/event-queue-monitor.ts +34 -3
- package/src/server/actions/orchestrator.ts +58 -38
- package/src/server/actions/queue-mappings.ts +1 -0
- package/src/server/actions/tasks.ts +142 -142
- package/src/server/adapters/redis-event-queue-adapter.ts +189 -0
- package/src/server/adapters/redis-node-registry.ts +44 -4
- package/src/server/collections/orchestrator-stacks.ts +6 -0
- package/src/server/collections/worker-queue-mappings.ts +3 -3
- package/src/server/orchestrator/PackageManager.ts +48 -11
- package/src/server/orchestrator/types.ts +5 -4
- package/src/server/plugin.ts +40 -6
- package/src/server/queue-scanner.ts +65 -51
- package/src/server/utils/node.ts +3 -10
- package/src/server/utils/redis.ts +39 -4
- package/src/shared/worker-processes.ts +216 -0
- package/dist/client-v2/914.c0bce51908fd81d7.js +0 -10
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QueueScanner
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 1. EventQueue events (registered via app.eventQueue.subscribe)
|
|
6
|
-
* 2. Redis List-based queues (convention *:plugin-*:queue)
|
|
7
|
-
*
|
|
8
|
-
* Used by the Queue Assignment UI to let admins map queues to worker stacks.
|
|
2
|
+
* QueueScanner discovers process keys and queue aliases that can be assigned
|
|
3
|
+
* to worker stacks. The worker deployment should receive process keys in
|
|
4
|
+
* WORKER_MODE; physical EventQueue/Redis names are exposed as aliases only.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
import type { Application } from '@nocobase/server';
|
|
8
|
+
import {
|
|
9
|
+
getWorkerProcessDefinition,
|
|
10
|
+
resolveWorkerProcessName,
|
|
11
|
+
WORKER_PROCESS_DEFINITIONS,
|
|
12
|
+
} from '../shared/worker-processes';
|
|
12
13
|
import { getRedisClient } from './utils/redis';
|
|
13
14
|
|
|
14
15
|
export type QueueItem = {
|
|
@@ -17,39 +18,27 @@ export type QueueItem = {
|
|
|
17
18
|
description: string;
|
|
18
19
|
type: 'event-queue' | 'redis-list';
|
|
19
20
|
pending: number | null;
|
|
21
|
+
workerProcessName?: string;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
const
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
'build-guide:process': {
|
|
40
|
-
label: 'Build Guide',
|
|
41
|
-
description: 'Build user guide pages (plugin-build-guide-block)',
|
|
42
|
-
},
|
|
43
|
-
'build-ui-template:process': {
|
|
44
|
-
label: 'Build UI Template',
|
|
45
|
-
description: 'Build UI template pages (plugin-build-ui-template)',
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/** Redis key patterns for List-based queues (same as event-queue-monitor.ts) */
|
|
50
|
-
const REDIS_QUEUE_PATTERNS = ['*:plugin-git-manager:review:queue', '*:plugin-build-guide-block:build:queue'];
|
|
24
|
+
const REDIS_QUEUE_PATTERNS = [
|
|
25
|
+
'*:plugin-git-manager:review:queue',
|
|
26
|
+
'*:plugin-build-guide-block:build:queue',
|
|
27
|
+
'*:plugin-build-visualization-block:build:queue',
|
|
28
|
+
'file-preview-auth.ocr.queue',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function describeRedisQueueKey(key: string): { label: string; description: string; workerProcessName?: string } {
|
|
32
|
+
const workerProcessName = resolveWorkerProcessName(key);
|
|
33
|
+
const definition = getWorkerProcessDefinition(workerProcessName);
|
|
34
|
+
if (definition) {
|
|
35
|
+
return {
|
|
36
|
+
label: definition.label,
|
|
37
|
+
description: definition.description,
|
|
38
|
+
workerProcessName: definition.name,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
51
41
|
|
|
52
|
-
function describeRedisQueueKey(key: string): { label: string; description: string } {
|
|
53
42
|
const parts = String(key).split(':');
|
|
54
43
|
const plugin = parts[parts.length - 3] || 'unknown';
|
|
55
44
|
const queue = parts[parts.length - 2] || key;
|
|
@@ -59,33 +48,54 @@ function describeRedisQueueKey(key: string): { label: string; description: strin
|
|
|
59
48
|
};
|
|
60
49
|
}
|
|
61
50
|
|
|
62
|
-
/**
|
|
63
|
-
* Discover all queues from EventQueue subscribers.
|
|
64
|
-
*/
|
|
65
51
|
function scanEventQueue(app: Application): QueueItem[] {
|
|
66
52
|
const eq = (app as any).eventQueue;
|
|
67
|
-
if (!eq
|
|
53
|
+
if (!eq?.events) return [];
|
|
68
54
|
|
|
69
55
|
const events: Map<string, { concurrency?: number; interval?: number; shared?: boolean }> = eq.events;
|
|
70
56
|
const items: QueueItem[] = [];
|
|
71
57
|
|
|
72
58
|
for (const [channel] of events.entries()) {
|
|
73
|
-
const
|
|
59
|
+
const workerProcessName = resolveWorkerProcessName(channel);
|
|
60
|
+
const known = getWorkerProcessDefinition(workerProcessName);
|
|
74
61
|
items.push({
|
|
75
62
|
name: channel,
|
|
76
63
|
label: known?.label ?? channel,
|
|
77
64
|
description: known?.description ?? `EventQueue channel: ${channel}`,
|
|
78
65
|
type: 'event-queue',
|
|
79
66
|
pending: null,
|
|
67
|
+
workerProcessName: known?.name,
|
|
80
68
|
});
|
|
81
69
|
}
|
|
82
70
|
|
|
83
71
|
return items;
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
function scanKnownWorkerModes(app: Application): QueueItem[] {
|
|
75
|
+
const pluginManager = (app as unknown as { pm?: { get?: (name: string) => unknown } }).pm;
|
|
76
|
+
if (!pluginManager?.get) return [];
|
|
77
|
+
|
|
78
|
+
const hasPlugin = (name: string) => {
|
|
79
|
+
try {
|
|
80
|
+
return Boolean(pluginManager.get?.(name));
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return WORKER_PROCESS_DEFINITIONS.filter(
|
|
87
|
+
(definition) =>
|
|
88
|
+
definition.common && !definition.sandbox && (!definition.pluginName || hasPlugin(definition.pluginName)),
|
|
89
|
+
).map((definition) => ({
|
|
90
|
+
name: definition.name,
|
|
91
|
+
label: definition.label,
|
|
92
|
+
description: definition.description,
|
|
93
|
+
type: definition.kind === 'redis-list' ? 'redis-list' : ('event-queue' as const),
|
|
94
|
+
pending: null,
|
|
95
|
+
workerProcessName: definition.name,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
async function scanRedisQueues(app: Application): Promise<QueueItem[]> {
|
|
90
100
|
const redis = getRedisClient(app);
|
|
91
101
|
if (!redis) {
|
|
@@ -97,8 +107,8 @@ async function scanRedisQueues(app: Application): Promise<QueueItem[]> {
|
|
|
97
107
|
|
|
98
108
|
for (const pattern of REDIS_QUEUE_PATTERNS) {
|
|
99
109
|
try {
|
|
100
|
-
const
|
|
101
|
-
const keyList: string[] =
|
|
110
|
+
const result = await redis.sendCommand(['SCAN', '0', 'MATCH', pattern, 'COUNT', '200']);
|
|
111
|
+
const keyList: string[] = Array.isArray(result?.[1]) ? result[1] : [];
|
|
102
112
|
|
|
103
113
|
for (const key of keyList) {
|
|
104
114
|
if (seen.has(key)) continue;
|
|
@@ -118,6 +128,7 @@ async function scanRedisQueues(app: Application): Promise<QueueItem[]> {
|
|
|
118
128
|
description: desc.description,
|
|
119
129
|
type: 'redis-list',
|
|
120
130
|
pending,
|
|
131
|
+
workerProcessName: desc.workerProcessName,
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
} catch {
|
|
@@ -128,14 +139,11 @@ async function scanRedisQueues(app: Application): Promise<QueueItem[]> {
|
|
|
128
139
|
return items;
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
/**
|
|
132
|
-
* Full queue scan — merges EventQueue + Redis results.
|
|
133
|
-
*/
|
|
134
142
|
export async function scanQueues(app: Application): Promise<{ queues: QueueItem[]; total: number }> {
|
|
135
143
|
const eventQueues = scanEventQueue(app);
|
|
144
|
+
const knownWorkerModes = scanKnownWorkerModes(app);
|
|
136
145
|
const redisQueues = await scanRedisQueues(app);
|
|
137
146
|
|
|
138
|
-
// Deduplicate: if a queue name appears in both sources, prefer EventQueue
|
|
139
147
|
const seenNames = new Set<string>();
|
|
140
148
|
const merged: QueueItem[] = [];
|
|
141
149
|
|
|
@@ -143,6 +151,12 @@ export async function scanQueues(app: Application): Promise<{ queues: QueueItem[
|
|
|
143
151
|
merged.push(q);
|
|
144
152
|
seenNames.add(q.name);
|
|
145
153
|
}
|
|
154
|
+
for (const q of knownWorkerModes) {
|
|
155
|
+
if (!seenNames.has(q.name)) {
|
|
156
|
+
merged.push(q);
|
|
157
|
+
seenNames.add(q.name);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
146
160
|
for (const q of redisQueues) {
|
|
147
161
|
if (!seenNames.has(q.name)) {
|
|
148
162
|
merged.push(q);
|
package/src/server/utils/node.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
|
+
import { isWorkerOnlyMode, normalizeWorkerMode } from '../../shared/worker-processes';
|
|
2
3
|
|
|
3
4
|
export type NodeRole = 'app' | 'worker' | 'sandbox';
|
|
4
5
|
|
|
@@ -15,15 +16,7 @@ export type NodeRole = 'app' | 'worker' | 'sandbox';
|
|
|
15
16
|
* HTTP, so it is treated as an app node.
|
|
16
17
|
*/
|
|
17
18
|
export function isWorkerMode(workerMode?: string): boolean {
|
|
18
|
-
|
|
19
|
-
if (!mode || mode === 'main' || mode === 'app') return false;
|
|
20
|
-
if (mode === '-') return false;
|
|
21
|
-
const topics = mode
|
|
22
|
-
.split(',')
|
|
23
|
-
.map((t) => t.trim())
|
|
24
|
-
.filter(Boolean);
|
|
25
|
-
if (topics.includes('!')) return false;
|
|
26
|
-
return true;
|
|
19
|
+
return isWorkerOnlyMode(workerMode ?? process.env.WORKER_MODE);
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
/**
|
|
@@ -55,7 +48,7 @@ export function getLocalRole(): NodeRole {
|
|
|
55
48
|
*/
|
|
56
49
|
export function getLocalNodeId(app: any): string {
|
|
57
50
|
const port = process.env.APP_PORT || 'unknown';
|
|
58
|
-
const mode = process.env.WORKER_MODE || 'main';
|
|
51
|
+
const mode = normalizeWorkerMode(process.env.WORKER_MODE) || 'main';
|
|
59
52
|
const appName = process.env.APP_NAME || app?.name || 'main';
|
|
60
53
|
return `${appName}_${mode}_${os.hostname()}_${port}_${process.pid}`;
|
|
61
54
|
}
|
|
@@ -3,15 +3,52 @@ import { createClient } from 'redis';
|
|
|
3
3
|
|
|
4
4
|
let globalRedisClient: any = null;
|
|
5
5
|
|
|
6
|
+
const CLUSTER_REDIS_CONNECTION = 'cluster-manager:registry';
|
|
7
|
+
|
|
8
|
+
const CLUSTER_REDIS_ENV_CANDIDATES = [
|
|
9
|
+
'NODE_REGISTRY_REDIS_URL',
|
|
10
|
+
'CLUSTER_MANAGER_REDIS_URL',
|
|
11
|
+
'REDIS_URL',
|
|
12
|
+
'CACHE_REDIS_URL',
|
|
13
|
+
'PUBSUB_ADAPTER_REDIS_URL',
|
|
14
|
+
'QUEUE_ADAPTER_REDIS_URL',
|
|
15
|
+
'LOCK_ADAPTER_REDIS_URL',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function getClusterRedisUrl() {
|
|
19
|
+
for (const key of CLUSTER_REDIS_ENV_CANDIDATES) {
|
|
20
|
+
const value = process.env[key];
|
|
21
|
+
if (value?.trim()) {
|
|
22
|
+
return value.trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isClusterRedisConfigured(app?: any) {
|
|
29
|
+
if (getClusterRedisUrl()) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const manager = app?.redisConnectionManager;
|
|
34
|
+
return Boolean(manager?.getConnection?.());
|
|
35
|
+
}
|
|
36
|
+
|
|
6
37
|
export function getRedisClient(app?: any) {
|
|
7
38
|
if (globalRedisClient) return globalRedisClient;
|
|
8
39
|
|
|
40
|
+
const url = getClusterRedisUrl();
|
|
41
|
+
|
|
9
42
|
if (app?.redisConnectionManager) {
|
|
43
|
+
if (url) {
|
|
44
|
+
const conn = app.redisConnectionManager.getConnection(CLUSTER_REDIS_CONNECTION, { connectionString: url });
|
|
45
|
+
if (conn) return conn;
|
|
46
|
+
}
|
|
47
|
+
|
|
10
48
|
const conn = app.redisConnectionManager.getConnection();
|
|
11
49
|
if (conn) return conn;
|
|
12
50
|
}
|
|
13
51
|
|
|
14
|
-
const url = process.env.REDIS_URL || process.env.CACHE_REDIS_URL || process.env.PUBSUB_ADAPTER_REDIS_URL;
|
|
15
52
|
if (!url) return null;
|
|
16
53
|
|
|
17
54
|
globalRedisClient = createClient({ url });
|
|
@@ -53,9 +90,7 @@ export async function scanKeys(redis: any, pattern: string, batchSize = 200): Pr
|
|
|
53
90
|
let cursor = '0';
|
|
54
91
|
|
|
55
92
|
do {
|
|
56
|
-
const result = await redis.sendCommand([
|
|
57
|
-
'SCAN', cursor, 'MATCH', pattern, 'COUNT', String(batchSize),
|
|
58
|
-
]);
|
|
93
|
+
const result = await redis.sendCommand(['SCAN', cursor, 'MATCH', pattern, 'COUNT', String(batchSize)]);
|
|
59
94
|
|
|
60
95
|
// result is [nextCursor, [...keys]]
|
|
61
96
|
cursor = String(result[0]);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
export type WorkerProcessKind = 'event-queue' | 'redis-list' | 'pubsub';
|
|
2
|
+
|
|
3
|
+
export type WorkerProcessDefinition = {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
kind: WorkerProcessKind;
|
|
8
|
+
aliases?: string[];
|
|
9
|
+
redisKeySuffixes?: string[];
|
|
10
|
+
pluginName?: string;
|
|
11
|
+
sandbox?: boolean;
|
|
12
|
+
common?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const WORKER_PROCESS_DEFINITIONS: WorkerProcessDefinition[] = [
|
|
16
|
+
{
|
|
17
|
+
name: 'workflow:process',
|
|
18
|
+
label: 'Workflow',
|
|
19
|
+
description: 'Process workflow executions',
|
|
20
|
+
kind: 'event-queue',
|
|
21
|
+
aliases: ['workflow.pendingExecution', '@nocobase/plugin-workflow.pendingExecution'],
|
|
22
|
+
common: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'async-task:process',
|
|
26
|
+
label: 'Async Tasks',
|
|
27
|
+
description: 'Execute async tasks',
|
|
28
|
+
kind: 'event-queue',
|
|
29
|
+
aliases: ['async-task-manager.task', '@nocobase/plugin-async-task-manager.task'],
|
|
30
|
+
common: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'notification:send',
|
|
34
|
+
label: 'Notifications',
|
|
35
|
+
description: 'Send notification messages',
|
|
36
|
+
kind: 'event-queue',
|
|
37
|
+
aliases: ['notification-manager.send', '@nocobase/plugin-notification-manager.send'],
|
|
38
|
+
common: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'knowledge-base:document-vectorize',
|
|
42
|
+
label: 'Document Vectorization',
|
|
43
|
+
description: 'Vectorize knowledge base documents',
|
|
44
|
+
kind: 'event-queue',
|
|
45
|
+
aliases: ['knowledge-base:document-vectorize'],
|
|
46
|
+
pluginName: 'plugin-knowledge-base',
|
|
47
|
+
common: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'git-review:process',
|
|
51
|
+
label: 'Git Review',
|
|
52
|
+
description: 'AI code review jobs',
|
|
53
|
+
kind: 'redis-list',
|
|
54
|
+
aliases: ['plugin-git-manager.review'],
|
|
55
|
+
redisKeySuffixes: [':plugin-git-manager:review:queue'],
|
|
56
|
+
pluginName: 'plugin-git-manager',
|
|
57
|
+
common: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'build-guide:process',
|
|
61
|
+
label: 'Build Guide',
|
|
62
|
+
description: 'Build user guide pages',
|
|
63
|
+
kind: 'redis-list',
|
|
64
|
+
aliases: ['plugin-build-guide-block.build'],
|
|
65
|
+
redisKeySuffixes: [':plugin-build-guide-block:build:queue'],
|
|
66
|
+
pluginName: 'plugin-build-guide-block',
|
|
67
|
+
common: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'build-visualization:process',
|
|
71
|
+
label: 'Build Visualization',
|
|
72
|
+
description: 'Build visualization blocks',
|
|
73
|
+
kind: 'redis-list',
|
|
74
|
+
aliases: ['plugin-build-visualization-block.build'],
|
|
75
|
+
redisKeySuffixes: [':plugin-build-visualization-block:build:queue'],
|
|
76
|
+
pluginName: 'plugin-build-visualization-block',
|
|
77
|
+
common: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'build-ui-template:process',
|
|
81
|
+
label: 'Build UI Template',
|
|
82
|
+
description: 'Build UI template pages',
|
|
83
|
+
kind: 'event-queue',
|
|
84
|
+
aliases: ['plugin-build-ui-template.build'],
|
|
85
|
+
pluginName: 'plugin-build-ui-template',
|
|
86
|
+
common: true,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'file-preview-auth:ocr',
|
|
90
|
+
label: 'File Preview OCR',
|
|
91
|
+
description: 'Run OCR extraction for authenticated file previews',
|
|
92
|
+
kind: 'redis-list',
|
|
93
|
+
aliases: ['file-preview-auth.ocr.queue'],
|
|
94
|
+
redisKeySuffixes: ['file-preview-auth.ocr.queue'],
|
|
95
|
+
pluginName: 'plugin-file-preview-auth',
|
|
96
|
+
common: true,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'skill-hub:sandbox',
|
|
100
|
+
label: 'Skill Sandbox',
|
|
101
|
+
description: 'Execute Skill Hub sandbox tasks',
|
|
102
|
+
kind: 'pubsub',
|
|
103
|
+
aliases: ['skill-hub.task'],
|
|
104
|
+
pluginName: 'plugin-agent-orchestrator',
|
|
105
|
+
sandbox: true,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const PROCESS_BY_NAME = new Map(WORKER_PROCESS_DEFINITIONS.map((definition) => [definition.name, definition]));
|
|
110
|
+
|
|
111
|
+
const ALIAS_TO_PROCESS = new Map<string, string>();
|
|
112
|
+
for (const definition of WORKER_PROCESS_DEFINITIONS) {
|
|
113
|
+
ALIAS_TO_PROCESS.set(definition.name, definition.name);
|
|
114
|
+
for (const alias of definition.aliases || []) {
|
|
115
|
+
ALIAS_TO_PROCESS.set(alias, definition.name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function splitWorkerModeTokens(value?: string | string[]): string[] {
|
|
120
|
+
const rawTokens = Array.isArray(value) ? value : String(value || '').split(',');
|
|
121
|
+
return rawTokens.map((token) => String(token).trim()).filter(Boolean);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function stripKnownPrefixes(token: string): string[] {
|
|
125
|
+
const candidates = [token];
|
|
126
|
+
|
|
127
|
+
const dotIndex = token.indexOf('.');
|
|
128
|
+
if (dotIndex > 0) {
|
|
129
|
+
candidates.push(token.slice(dotIndex + 1));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const colonIndex = token.indexOf(':');
|
|
133
|
+
if (colonIndex > 0) {
|
|
134
|
+
candidates.push(token.slice(colonIndex + 1));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return Array.from(new Set(candidates));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveWorkerProcessName(token: string): string {
|
|
141
|
+
const value = String(token || '').trim();
|
|
142
|
+
if (!value || value === '*' || value === '!' || value === '-') {
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const directProcessName = ALIAS_TO_PROCESS.get(value);
|
|
147
|
+
if (directProcessName) {
|
|
148
|
+
return directProcessName;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const candidate of stripKnownPrefixes(value)) {
|
|
152
|
+
const processName = ALIAS_TO_PROCESS.get(candidate);
|
|
153
|
+
if (processName) {
|
|
154
|
+
return processName;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const definition of WORKER_PROCESS_DEFINITIONS) {
|
|
159
|
+
if ((definition.redisKeySuffixes || []).some((suffix) => value.endsWith(suffix))) {
|
|
160
|
+
return definition.name;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function normalizeWorkerMode(value?: string | string[]): string | undefined {
|
|
168
|
+
const resolved = splitWorkerModeTokens(value).map(resolveWorkerProcessName);
|
|
169
|
+
if (resolved.includes('*')) {
|
|
170
|
+
return '*';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const unique = Array.from(new Set(resolved.filter(Boolean)));
|
|
174
|
+
return unique.length ? unique.join(',') : undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function workerModeTokens(value?: string | string[]): string[] {
|
|
178
|
+
const normalized = normalizeWorkerMode(value);
|
|
179
|
+
return normalized ? normalized.split(',') : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function workerModeServesProcess(workerMode: string | undefined, processNameOrAlias: string): boolean {
|
|
183
|
+
const mode = normalizeWorkerMode(workerMode);
|
|
184
|
+
if (!mode) return true;
|
|
185
|
+
if (mode === 'main' || mode === 'app') return true;
|
|
186
|
+
if (mode === '-') return false;
|
|
187
|
+
if (mode === '*') return true;
|
|
188
|
+
|
|
189
|
+
const processName = resolveWorkerProcessName(processNameOrAlias);
|
|
190
|
+
return mode.split(',').includes(processName);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function isWorkerOnlyMode(workerMode?: string): boolean {
|
|
194
|
+
const mode = normalizeWorkerMode(workerMode);
|
|
195
|
+
if (!mode || mode === 'main' || mode === 'app' || mode === '-') return false;
|
|
196
|
+
return !mode.split(',').includes('!');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function isAppServingMode(workerMode?: string): boolean {
|
|
200
|
+
const mode = normalizeWorkerMode(workerMode);
|
|
201
|
+
if (!mode || mode === 'main' || mode === 'app') return true;
|
|
202
|
+
return mode.split(',').includes('!');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getWorkerProcessDefinition(nameOrAlias: string): WorkerProcessDefinition | undefined {
|
|
206
|
+
return PROCESS_BY_NAME.get(resolveWorkerProcessName(nameOrAlias));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getCommonWorkerProcesses(): WorkerProcessDefinition[] {
|
|
210
|
+
return WORKER_PROCESS_DEFINITIONS.filter((definition) => definition.common && !definition.sandbox);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function getWorkerProcessByChannel(channel: string): WorkerProcessDefinition | undefined {
|
|
214
|
+
const processName = resolveWorkerProcessName(channel);
|
|
215
|
+
return PROCESS_BY_NAME.get(processName);
|
|
216
|
+
}
|