@angular-modernizer/worker 0.1.0
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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/analysis-worker.d.ts +26 -0
- package/dist/analysis-worker.d.ts.map +1 -0
- package/dist/analysis-worker.js +332 -0
- package/dist/analysis-worker.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
- package/src/analysis-worker.ts +408 -0
- package/src/index.ts +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christian Weiss<CWOIDA>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @angular-modernizer/worker
|
|
2
|
+
|
|
3
|
+
Worker thread script for parallel analysis in the Angular Modernization Platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package contains the analysis worker script that runs in Node.js worker threads for parallel code analysis. It depends on all plugin packages and is therefore the last package built in the monorepo.
|
|
8
|
+
|
|
9
|
+
## Why a Separate Package?
|
|
10
|
+
|
|
11
|
+
Worker threads run in isolated V8 contexts and need access to all analysis plugins. Since plugins depend on `core`, and `core` contains the `WorkerPool`, placing the worker script in `core` would create a circular dependency.
|
|
12
|
+
|
|
13
|
+
This package resolves that by:
|
|
14
|
+
|
|
15
|
+
1. Depending on all plugin packages (builds last)
|
|
16
|
+
2. Exporting a helper function to get the worker script path
|
|
17
|
+
3. Being used by both programmatic API users and the MCP adapter
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { getWorkerScriptPath } from '@angular-modernizer/worker';
|
|
23
|
+
import { WorkerPool } from '@angular-modernizer/core';
|
|
24
|
+
|
|
25
|
+
const pool = new WorkerPool({
|
|
26
|
+
tsConfigPath: '/path/to/tsconfig.json',
|
|
27
|
+
workerScriptPath: getWorkerScriptPath(),
|
|
28
|
+
maxWorkers: 4,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await pool.executeTask({
|
|
32
|
+
id: 'task-1',
|
|
33
|
+
filePath: '/path/to/file.ts',
|
|
34
|
+
pluginIds: ['architecture', 'solid'],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await pool.terminate();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Mode Selection
|
|
41
|
+
|
|
42
|
+
- Less than 100 files: Promise mode (worker overhead exceeds benefit)
|
|
43
|
+
- 100-500 files: Adaptive mode (auto-selects based on available memory)
|
|
44
|
+
- More than 500 files: Worker mode (5-10x speedup)
|
|
45
|
+
|
|
46
|
+
Memory pressure: at 85% heap usage, the pool reduces worker count or pauses new tasks.
|
|
47
|
+
|
|
48
|
+
## Build Order
|
|
49
|
+
|
|
50
|
+
This package builds last in the monorepo:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
1. core
|
|
54
|
+
2. plugin-system
|
|
55
|
+
3. api
|
|
56
|
+
4. plugin-analyzer, plugin-solid, plugin-angular, plugin-standalone, plugin-architecture, orchestration
|
|
57
|
+
5. worker (this package)
|
|
58
|
+
6. adapter-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Exports
|
|
62
|
+
|
|
63
|
+
- `getWorkerScriptPath()` - Returns the absolute path to the compiled worker script
|
|
64
|
+
- Re-exports `WorkerTask`, `WorkerResult` types from `@angular-modernizer/core`
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
All workspace packages must be built before using workers. Run `pnpm build` from the monorepo root.
|
|
69
|
+
|
|
70
|
+
## Testing
|
|
71
|
+
|
|
72
|
+
Worker tests are skipped by default to keep CI fast. Set `ENABLE_WORKER_TESTS=true` to run the full worker pool suite:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ENABLE_WORKER_TESTS=true pnpm --filter @angular-modernizer/worker test
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Implementation Notes
|
|
79
|
+
|
|
80
|
+
Workers require `ContextFactory.createAnalysisContext()` — not a bare `{ sourceFile, project, config }` object. Rules that access `context.api` will throw when given a bare object.
|
|
81
|
+
|
|
82
|
+
Data crossing the thread boundary must be JSON-serializable. Functions, class instances, circular references, and Symbols cannot be serialized and will cause `DataCloneError`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker - Analysis worker script for worker thread execution
|
|
3
|
+
*
|
|
4
|
+
* Each worker:
|
|
5
|
+
* - Creates isolated Project instance (ts-morph) - OPTIMIZED: lazy loading
|
|
6
|
+
* - Initializes PublicApi and plugins
|
|
7
|
+
* - Receives tasks via parentPort messages
|
|
8
|
+
* - Executes analysis rules
|
|
9
|
+
* - Returns results to main thread
|
|
10
|
+
*
|
|
11
|
+
* Communication Protocol:
|
|
12
|
+
* Main → Worker: WorkerTask { taskId, filePath, ruleIds, pluginIds, config }
|
|
13
|
+
* Worker → Main: WorkerResult { taskId, results, duration, error? }
|
|
14
|
+
*
|
|
15
|
+
* OPTIMIZATION: Workers now use lazy file loading instead of loading the entire
|
|
16
|
+
* project from tsconfig. This significantly reduces initialization time from
|
|
17
|
+
* ~2000ms to ~100ms per worker.
|
|
18
|
+
*
|
|
19
|
+
* This file is in @angular-modernizer/worker package which depends on ALL plugins.
|
|
20
|
+
* This solves the circular dependency issue - worker builds LAST after all plugins.
|
|
21
|
+
*
|
|
22
|
+
* Build Requirement: All workspace packages must be built before workers can run.
|
|
23
|
+
* Run `pnpm build` from the monorepo root to compile all packages.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=analysis-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-worker.d.ts","sourceRoot":"","sources":["../src/analysis-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker - Analysis worker script for worker thread execution
|
|
3
|
+
*
|
|
4
|
+
* Each worker:
|
|
5
|
+
* - Creates isolated Project instance (ts-morph) - OPTIMIZED: lazy loading
|
|
6
|
+
* - Initializes PublicApi and plugins
|
|
7
|
+
* - Receives tasks via parentPort messages
|
|
8
|
+
* - Executes analysis rules
|
|
9
|
+
* - Returns results to main thread
|
|
10
|
+
*
|
|
11
|
+
* Communication Protocol:
|
|
12
|
+
* Main → Worker: WorkerTask { taskId, filePath, ruleIds, pluginIds, config }
|
|
13
|
+
* Worker → Main: WorkerResult { taskId, results, duration, error? }
|
|
14
|
+
*
|
|
15
|
+
* OPTIMIZATION: Workers now use lazy file loading instead of loading the entire
|
|
16
|
+
* project from tsconfig. This significantly reduces initialization time from
|
|
17
|
+
* ~2000ms to ~100ms per worker.
|
|
18
|
+
*
|
|
19
|
+
* This file is in @angular-modernizer/worker package which depends on ALL plugins.
|
|
20
|
+
* This solves the circular dependency issue - worker builds LAST after all plugins.
|
|
21
|
+
*
|
|
22
|
+
* Build Requirement: All workspace packages must be built before workers can run.
|
|
23
|
+
* Run `pnpm build` from the monorepo root to compile all packages.
|
|
24
|
+
*/
|
|
25
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
26
|
+
import { Project } from 'ts-morph';
|
|
27
|
+
import { createPublicApi } from '@angular-modernizer/api';
|
|
28
|
+
import { ContextFactory } from '@angular-modernizer/plugin-system';
|
|
29
|
+
import { ArchitecturePlugin } from '@angular-modernizer/plugin-architecture';
|
|
30
|
+
import { SolidPlugin } from '@angular-modernizer/plugin-solid';
|
|
31
|
+
import { AngularPlugin } from '@angular-modernizer/plugin-angular';
|
|
32
|
+
import { AnalyzerPlugin } from '@angular-modernizer/plugin-analyzer';
|
|
33
|
+
import { StandalonePlugin } from '@angular-modernizer/plugin-standalone';
|
|
34
|
+
/**
|
|
35
|
+
* Worker state (isolated per worker)
|
|
36
|
+
*/
|
|
37
|
+
let project = null;
|
|
38
|
+
let workerId;
|
|
39
|
+
let publicApi = null;
|
|
40
|
+
const pluginRulesCache = new Map();
|
|
41
|
+
/**
|
|
42
|
+
* Initialize worker with Project and plugins.
|
|
43
|
+
* Loads full project from tsconfig because analysis rules need cross-file context
|
|
44
|
+
* (inheritance chains, import analysis, etc.)
|
|
45
|
+
*/
|
|
46
|
+
function initializeWorker() {
|
|
47
|
+
const initStart = Date.now();
|
|
48
|
+
const data = workerData;
|
|
49
|
+
workerId = data.workerId;
|
|
50
|
+
log(`Worker ${workerId} starting initialization...`);
|
|
51
|
+
// Create isolated Project instance - load all files for cross-file analysis
|
|
52
|
+
project = new Project({
|
|
53
|
+
tsConfigFilePath: data.tsConfigPath,
|
|
54
|
+
skipAddingFilesFromTsConfig: false, // Load all files for cross-file analysis
|
|
55
|
+
});
|
|
56
|
+
const fileCount = project.getSourceFiles().length;
|
|
57
|
+
const projectLoadTime = Date.now() - initStart;
|
|
58
|
+
log(`Worker ${workerId} loaded project with ${fileCount} files in ${projectLoadTime}ms`);
|
|
59
|
+
// Create PublicApi instance for this worker
|
|
60
|
+
publicApi = createPublicApi(project);
|
|
61
|
+
const totalInitTime = Date.now() - initStart;
|
|
62
|
+
log(`Worker ${workerId} fully initialized in ${totalInitTime}ms`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load plugin rules based on plugin IDs.
|
|
66
|
+
* Rules are cached per plugin ID to avoid re-instantiation.
|
|
67
|
+
*/
|
|
68
|
+
function loadPluginRules(pluginIds) {
|
|
69
|
+
const allRules = [];
|
|
70
|
+
for (const pluginId of pluginIds) {
|
|
71
|
+
// Check cache first
|
|
72
|
+
if (pluginRulesCache.has(pluginId)) {
|
|
73
|
+
const cachedRules = pluginRulesCache.get(pluginId);
|
|
74
|
+
allRules.push(...cachedRules);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Load plugin and get its rules
|
|
78
|
+
let rules = [];
|
|
79
|
+
switch (pluginId) {
|
|
80
|
+
case 'architecture':
|
|
81
|
+
case '@angular-modernizer/plugin-architecture': {
|
|
82
|
+
try {
|
|
83
|
+
const plugin = new ArchitecturePlugin();
|
|
84
|
+
rules = plugin.getAnalysisRules();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
log(`Warning: Failed to load ArchitecturePlugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'solid':
|
|
92
|
+
case '@angular-modernizer/plugin-solid': {
|
|
93
|
+
try {
|
|
94
|
+
const plugin = new SolidPlugin();
|
|
95
|
+
rules = plugin.getAnalysisRules();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
log(`Warning: Failed to load SolidPlugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'angular':
|
|
103
|
+
case '@angular-modernizer/plugin-angular': {
|
|
104
|
+
try {
|
|
105
|
+
const plugin = new AngularPlugin();
|
|
106
|
+
rules = plugin.getAnalysisRules();
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
log(`Warning: Failed to load AngularPlugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'analyzer':
|
|
114
|
+
case '@angular-modernizer/plugin-analyzer': {
|
|
115
|
+
try {
|
|
116
|
+
const plugin = new AnalyzerPlugin();
|
|
117
|
+
rules = plugin.getAnalysisRules();
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
log(`Warning: Failed to load AnalyzerPlugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'standalone':
|
|
125
|
+
case '@angular-modernizer/plugin-standalone': {
|
|
126
|
+
try {
|
|
127
|
+
const plugin = new StandalonePlugin();
|
|
128
|
+
// StandalonePlugin has transform rules, not analysis rules
|
|
129
|
+
// Use getTransformRules if available, otherwise empty array
|
|
130
|
+
rules =
|
|
131
|
+
plugin.getAnalysisRules?.() ?? [];
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
log(`Warning: Failed to load StandalonePlugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
log(`Warning: Unknown plugin ID: ${pluginId}`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Cache the rules
|
|
143
|
+
pluginRulesCache.set(pluginId, rules);
|
|
144
|
+
allRules.push(...rules);
|
|
145
|
+
}
|
|
146
|
+
return allRules;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Filter rules by rule IDs if specified.
|
|
150
|
+
*/
|
|
151
|
+
function filterRulesByIds(rules, ruleIds) {
|
|
152
|
+
if (!ruleIds || ruleIds.length === 0) {
|
|
153
|
+
return rules;
|
|
154
|
+
}
|
|
155
|
+
return rules.filter((rule) => ruleIds.includes(rule.id));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Convert plugin-system AnalysisResult to orchestration AnalysisResult.
|
|
159
|
+
* The orchestration type requires a severity field.
|
|
160
|
+
*/
|
|
161
|
+
function convertToOrchestrationResult(pluginResult) {
|
|
162
|
+
return {
|
|
163
|
+
ruleId: pluginResult.ruleId,
|
|
164
|
+
filePath: pluginResult.filePath,
|
|
165
|
+
message: pluginResult.message,
|
|
166
|
+
severity: 'warning', // Default severity (plugin results don't have this field)
|
|
167
|
+
line: pluginResult.line,
|
|
168
|
+
column: pluginResult.column,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Execute analysis task.
|
|
173
|
+
*/
|
|
174
|
+
async function executeTask(task) {
|
|
175
|
+
const startTime = Date.now();
|
|
176
|
+
log(`Starting task ${task.id} for file: ${task.filePath}`);
|
|
177
|
+
try {
|
|
178
|
+
if (!project) {
|
|
179
|
+
throw new Error('Worker not initialized');
|
|
180
|
+
}
|
|
181
|
+
if (!publicApi) {
|
|
182
|
+
throw new Error('PublicApi not initialized');
|
|
183
|
+
}
|
|
184
|
+
// Get the source file from the pre-loaded project
|
|
185
|
+
log(`Getting source file for ${task.filePath}`);
|
|
186
|
+
const sourceFile = project.getSourceFile(task.filePath);
|
|
187
|
+
if (!sourceFile) {
|
|
188
|
+
throw new Error(`Source file not found in project: ${task.filePath}`);
|
|
189
|
+
}
|
|
190
|
+
// Load plugin rules based on task.pluginIds
|
|
191
|
+
log(`Loading plugin rules for plugins: ${task.pluginIds.join(', ') || 'none'}`);
|
|
192
|
+
const allRules = loadPluginRules(task.pluginIds || []);
|
|
193
|
+
// Filter rules by ruleIds if specified
|
|
194
|
+
const rules = filterRulesByIds(allRules, task.ruleIds);
|
|
195
|
+
log(`Executing ${rules.length} rules for ${task.filePath}`);
|
|
196
|
+
if (rules.length === 0) {
|
|
197
|
+
log(`Warning: No rules to execute for task ${task.id}`);
|
|
198
|
+
return {
|
|
199
|
+
taskId: task.id,
|
|
200
|
+
results: [],
|
|
201
|
+
duration: Date.now() - startTime,
|
|
202
|
+
workerId,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Create analysis context
|
|
206
|
+
const context = ContextFactory.createAnalysisContext({
|
|
207
|
+
sourceFile,
|
|
208
|
+
project,
|
|
209
|
+
api: publicApi,
|
|
210
|
+
config: task.config ?? {},
|
|
211
|
+
});
|
|
212
|
+
// Execute all rules and collect results
|
|
213
|
+
const results = [];
|
|
214
|
+
for (const rule of rules) {
|
|
215
|
+
try {
|
|
216
|
+
log(`Executing rule ${rule.id} for ${task.filePath}`);
|
|
217
|
+
const ruleStartTime = Date.now();
|
|
218
|
+
const ruleResults = await rule.analyze(context);
|
|
219
|
+
const ruleDuration = Date.now() - ruleStartTime;
|
|
220
|
+
log(`Rule ${rule.id} completed in ${ruleDuration}ms with ${ruleResults.length} results`);
|
|
221
|
+
// Convert plugin results to orchestration results
|
|
222
|
+
const convertedResults = ruleResults.map((r) => convertToOrchestrationResult(r));
|
|
223
|
+
results.push(...convertedResults);
|
|
224
|
+
}
|
|
225
|
+
catch (ruleError) {
|
|
226
|
+
// Log rule error but continue with other rules
|
|
227
|
+
log(`Error executing rule ${rule.id}: ${ruleError instanceof Error ? ruleError.message : String(ruleError)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const duration = Date.now() - startTime;
|
|
231
|
+
log(`Task ${task.id} completed in ${duration}ms with ${results.length} total results`);
|
|
232
|
+
return {
|
|
233
|
+
taskId: task.id,
|
|
234
|
+
results,
|
|
235
|
+
duration,
|
|
236
|
+
workerId,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const duration = Date.now() - startTime;
|
|
241
|
+
log(`Task ${task.id} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
|
+
return {
|
|
243
|
+
taskId: task.id,
|
|
244
|
+
results: [],
|
|
245
|
+
duration,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
workerId,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Main message handler.
|
|
253
|
+
*/
|
|
254
|
+
if (parentPort) {
|
|
255
|
+
// Set up global error handlers for uncaught errors
|
|
256
|
+
process.on('uncaughtException', (error) => {
|
|
257
|
+
const data = workerData;
|
|
258
|
+
console.error(`[Worker ${data.workerId}] Uncaught exception:`, error);
|
|
259
|
+
if (parentPort) {
|
|
260
|
+
parentPort.postMessage({
|
|
261
|
+
type: 'init-error',
|
|
262
|
+
workerId: data.workerId,
|
|
263
|
+
error: error.message,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
268
|
+
process.on('unhandledRejection', (reason) => {
|
|
269
|
+
const data = workerData;
|
|
270
|
+
console.error(`[Worker ${data.workerId}] Unhandled rejection:`, reason);
|
|
271
|
+
if (parentPort) {
|
|
272
|
+
parentPort.postMessage({
|
|
273
|
+
type: 'init-error',
|
|
274
|
+
workerId: data.workerId,
|
|
275
|
+
error: String(reason),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
process.exit(1);
|
|
279
|
+
});
|
|
280
|
+
// Initialize on startup (with error handling)
|
|
281
|
+
try {
|
|
282
|
+
initializeWorker();
|
|
283
|
+
// Send ready signal to parent (after initialization)
|
|
284
|
+
const data = workerData;
|
|
285
|
+
parentPort.postMessage({ type: 'ready', workerId: data.workerId });
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
// Send initialization error to parent
|
|
289
|
+
const data = workerData;
|
|
290
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
291
|
+
console.error(`[Worker ${data.workerId}] Initialization error: ${errorMessage}`);
|
|
292
|
+
parentPort.postMessage({
|
|
293
|
+
type: 'init-error',
|
|
294
|
+
workerId: data.workerId,
|
|
295
|
+
error: errorMessage,
|
|
296
|
+
});
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
// Handle incoming tasks
|
|
300
|
+
parentPort.on('message', (task) => {
|
|
301
|
+
log(`Received task ${task.id}`);
|
|
302
|
+
void executeTask(task)
|
|
303
|
+
.then((result) => {
|
|
304
|
+
parentPort.postMessage(result);
|
|
305
|
+
})
|
|
306
|
+
.catch((error) => {
|
|
307
|
+
// Send error result
|
|
308
|
+
const errorResult = {
|
|
309
|
+
taskId: task.id,
|
|
310
|
+
results: [],
|
|
311
|
+
duration: 0,
|
|
312
|
+
error: error instanceof Error ? error.message : String(error),
|
|
313
|
+
workerId,
|
|
314
|
+
};
|
|
315
|
+
parentPort.postMessage(errorResult);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
// Handle errors
|
|
319
|
+
parentPort.on('messageerror', (error) => {
|
|
320
|
+
log(`Message error: ${String(error)}`);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
throw new Error('This script must be run as a worker thread');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Log message (includes worker ID).
|
|
328
|
+
*/
|
|
329
|
+
function log(message) {
|
|
330
|
+
console.info(`[Worker ${workerId}] ${message}`);
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=analysis-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-worker.js","sourceRoot":"","sources":["../src/analysis-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAOnC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAM1D,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAQzE;;GAEG;AACH,IAAI,OAAO,GAAmB,IAAI,CAAC;AACnC,IAAI,QAAgB,CAAC;AACrB,IAAI,SAAS,GAAqB,IAAI,CAAC;AACvC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA0B,CAAC;AAE3D;;;;GAIG;AACH,SAAS,gBAAgB;IACvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,UAAwB,CAAC;IACtC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAEzB,GAAG,CAAC,UAAU,QAAQ,6BAA6B,CAAC,CAAC;IAErD,4EAA4E;IAC5E,OAAO,GAAG,IAAI,OAAO,CAAC;QACpB,gBAAgB,EAAE,IAAI,CAAC,YAAY;QACnC,2BAA2B,EAAE,KAAK,EAAE,yCAAyC;KAC9E,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC;IAClD,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAC/C,GAAG,CACD,UAAU,QAAQ,wBAAwB,SAAS,aAAa,eAAe,IAAI,CACpF,CAAC;IAEF,4CAA4C;IAC5C,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAErC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAC7C,GAAG,CAAC,UAAU,QAAQ,yBAAyB,aAAa,IAAI,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,SAAmB;IAC1C,MAAM,QAAQ,GAAmB,EAAE,CAAC;IAEpC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,oBAAoB;QACpB,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC;YACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,gCAAgC;QAChC,IAAI,KAAK,GAAmB,EAAE,CAAC;QAE/B,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,cAAc,CAAC;YACpB,KAAK,yCAAyC,CAAC,CAAC,CAAC;gBAC/C,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;oBACxC,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACpC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,GAAG,CACD,+CAA+C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACxG,CAAC;gBACJ,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,OAAO,CAAC;YACb,KAAK,kCAAkC,CAAC,CAAC,CAAC;gBACxC,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;oBACjC,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACpC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,GAAG,CACD,wCAAwC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjG,CAAC;gBACJ,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC;YACf,KAAK,oCAAoC,CAAC,CAAC,CAAC;gBAC1C,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;oBACnC,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACpC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,GAAG,CACD,0CAA0C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnG,CAAC;gBACJ,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,UAAU,CAAC;YAChB,KAAK,qCAAqC,CAAC,CAAC,CAAC;gBAC3C,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;oBACpC,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACpC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,GAAG,CACD,2CAA2C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACpG,CAAC;gBACJ,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,YAAY,CAAC;YAClB,KAAK,uCAAuC,CAAC,CAAC,CAAC;gBAC7C,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;oBACtC,2DAA2D;oBAC3D,4DAA4D;oBAC5D,KAAK;wBAED,MACD,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC;gBACjC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,GAAG,CACD,6CAA6C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtG,CAAC;gBACJ,CAAC;gBACD,MAAM;YACR,CAAC;YACD;gBACE,GAAG,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;gBAC/C,SAAS;QACb,CAAC;QAED,kBAAkB;QAClB,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACtC,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACvB,KAAqB,EACrB,OAAkB;IAElB,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,SAAS,4BAA4B,CACnC,YAAkC;IAElC,OAAO;QACL,MAAM,EAAE,YAAY,CAAC,MAAM;QAC3B,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,OAAO,EAAE,YAAY,CAAC,OAAO;QAC7B,QAAQ,EAAE,SAAS,EAAE,0DAA0D;QAC/E,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,MAAM,EAAE,YAAY,CAAC,MAAM;KAC5B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,IAAgB;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,GAAG,CAAC,iBAAiB,IAAI,CAAC,EAAE,cAAc,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE3D,IAAI,CAAC;QACH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QAED,kDAAkD;QAClD,GAAG,CAAC,2BAA2B,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,4CAA4C;QAC5C,GAAG,CACD,qCAAqC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAC3E,CAAC;QACF,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAEvD,uCAAuC;QACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACvD,GAAG,CAAC,aAAa,KAAK,CAAC,MAAM,cAAc,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE5D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,GAAG,CAAC,yCAAyC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACxD,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAChC,QAAQ;aACT,CAAC;QACJ,CAAC;QAED,0BAA0B;QAC1B,MAAM,OAAO,GAAoB,cAAc,CAAC,qBAAqB,CAAC;YACpE,UAAU;YACV,OAAO;YACP,GAAG,EAAE,SAAS;YACd,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;SAC1B,CAAC,CAAC;QAEH,wCAAwC;QACxC,MAAM,OAAO,GAAkC,EAAE,CAAC;QAClD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,GAAG,CAAC,kBAAkB,IAAI,CAAC,EAAE,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACtD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACjC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAChD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC;gBAChD,GAAG,CACD,QAAQ,IAAI,CAAC,EAAE,iBAAiB,YAAY,WAAW,WAAW,CAAC,MAAM,UAAU,CACpF,CAAC;gBACF,kDAAkD;gBAClD,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAuB,EAAE,EAAE,CACnE,4BAA4B,CAAC,CAAC,CAAC,CAChC,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC;YACpC,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;gBACnB,+CAA+C;gBAC/C,GAAG,CACD,wBAAwB,IAAI,CAAC,EAAE,KAAK,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CACzG,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,GAAG,CACD,QAAQ,IAAI,CAAC,EAAE,iBAAiB,QAAQ,WAAW,OAAO,CAAC,MAAM,gBAAgB,CAClF,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,OAAO;YACP,QAAQ;YACR,QAAQ;SACT,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,GAAG,CACD,QAAQ,IAAI,CAAC,EAAE,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACpF,CAAC;QACF,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,OAAO,EAAE,EAAE;YACX,QAAQ;YACR,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC7D,QAAQ;SACT,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,IAAI,UAAU,EAAE,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,UAAwB,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,QAAQ,uBAAuB,EAAE,KAAK,CAAC,CAAC;QACtE,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,WAAW,CAAC;gBACrB,IAAI,EAAE,YAAY;gBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,KAAK,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,UAAwB,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,QAAQ,wBAAwB,EAAE,MAAM,CAAC,CAAC;QACxE,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,WAAW,CAAC;gBACrB,IAAI,EAAE,YAAY;gBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;aACtB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,8CAA8C;IAC9C,IAAI,CAAC;QACH,gBAAgB,EAAE,CAAC;QAEnB,qDAAqD;QACrD,MAAM,IAAI,GAAG,UAAwB,CAAC;QACtC,UAAU,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sCAAsC;QACtC,MAAM,IAAI,GAAG,UAAwB,CAAC;QACtC,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,OAAO,CAAC,KAAK,CACX,WAAW,IAAI,CAAC,QAAQ,2BAA2B,YAAY,EAAE,CAClE,CAAC;QACF,UAAU,CAAC,WAAW,CAAC;YACrB,IAAI,EAAE,YAAY;YAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,YAAY;SACpB,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,wBAAwB;IACxB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAgB,EAAE,EAAE;QAC5C,GAAG,CAAC,iBAAiB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAEhC,KAAK,WAAW,CAAC,IAAI,CAAC;aACnB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,UAAW,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,oBAAoB;YACpB,MAAM,WAAW,GAAiB;gBAChC,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,CAAC;gBACX,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,QAAQ;aACT,CAAC;YACF,UAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,gBAAgB;IAChB,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE;QACtC,GAAG,CAAC,kBAAkB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,SAAS,GAAG,CAAC,OAAe;IAC1B,OAAO,CAAC,IAAI,CAAC,WAAW,QAAQ,KAAK,OAAO,EAAE,CAAC,CAAC;AAClD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker
|
|
3
|
+
*
|
|
4
|
+
* Worker thread script for parallel analysis.
|
|
5
|
+
* This package must be built LAST because it depends on ALL plugins.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get the path to the analysis worker script.
|
|
9
|
+
* Used by WorkerPool to spawn worker threads.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { getWorkerScriptPath } from '@angular-modernizer/worker';
|
|
14
|
+
* import { WorkerPool } from '@angular-modernizer/core';
|
|
15
|
+
*
|
|
16
|
+
* const pool = new WorkerPool({
|
|
17
|
+
* tsConfigPath: '/path/to/tsconfig.json',
|
|
18
|
+
* workerScriptPath: getWorkerScriptPath(),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function getWorkerScriptPath(): string;
|
|
23
|
+
export type { WorkerTask, WorkerResult } from '@angular-modernizer/core';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAGD,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker
|
|
3
|
+
*
|
|
4
|
+
* Worker thread script for parallel analysis.
|
|
5
|
+
* This package must be built LAST because it depends on ALL plugins.
|
|
6
|
+
*/
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
/**
|
|
10
|
+
* Get the path to the analysis worker script.
|
|
11
|
+
* Used by WorkerPool to spawn worker threads.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { getWorkerScriptPath } from '@angular-modernizer/worker';
|
|
16
|
+
* import { WorkerPool } from '@angular-modernizer/core';
|
|
17
|
+
*
|
|
18
|
+
* const pool = new WorkerPool({
|
|
19
|
+
* tsConfigPath: '/path/to/tsconfig.json',
|
|
20
|
+
* workerScriptPath: getWorkerScriptPath(),
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function getWorkerScriptPath() {
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
return join(__dirname, 'analysis-worker.js');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;AAC/C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angular-modernizer/worker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Worker thread script for parallel analysis in the Angular Modernization Platform",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"modernization",
|
|
8
|
+
"typescript",
|
|
9
|
+
"ast",
|
|
10
|
+
"ts-morph",
|
|
11
|
+
"worker-threads",
|
|
12
|
+
"parallelism"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/eiweiss/tsanalyzer.git",
|
|
18
|
+
"directory": "packages/worker"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/eiweiss/tsanalyzer#readme",
|
|
21
|
+
"bugs": "https://github.com/eiweiss/tsanalyzer/issues",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./analysis-worker": {
|
|
31
|
+
"types": "./dist/analysis-worker.d.ts",
|
|
32
|
+
"import": "./dist/analysis-worker.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src",
|
|
38
|
+
"!dist/**/__tests__/**",
|
|
39
|
+
"!src/**/__tests__/**",
|
|
40
|
+
"!src/**/*.test.ts",
|
|
41
|
+
"!src/**/*.spec.ts"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"ts-morph": "^27.0.2",
|
|
45
|
+
"@angular-modernizer/core": "0.1.0",
|
|
46
|
+
"@angular-modernizer/plugin-system": "0.1.0",
|
|
47
|
+
"@angular-modernizer/plugin-analyzer": "0.1.0",
|
|
48
|
+
"@angular-modernizer/api": "0.1.0",
|
|
49
|
+
"@angular-modernizer/plugin-solid": "0.1.0",
|
|
50
|
+
"@angular-modernizer/plugin-angular": "0.1.0",
|
|
51
|
+
"@angular-modernizer/plugin-architecture": "0.1.0",
|
|
52
|
+
"@angular-modernizer/plugin-standalone": "0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^24.10.0",
|
|
56
|
+
"rimraf": "^6.1.0",
|
|
57
|
+
"typescript": "^5.9.3"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"typedocOptions": {
|
|
63
|
+
"entryPoints": [
|
|
64
|
+
"./src/index.ts"
|
|
65
|
+
],
|
|
66
|
+
"tsconfig": "./tsconfig.json"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc --build",
|
|
70
|
+
"test": "jest --passWithNoTests",
|
|
71
|
+
"lint": "eslint src/**/*.ts",
|
|
72
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker - Analysis worker script for worker thread execution
|
|
3
|
+
*
|
|
4
|
+
* Each worker:
|
|
5
|
+
* - Creates isolated Project instance (ts-morph) - OPTIMIZED: lazy loading
|
|
6
|
+
* - Initializes PublicApi and plugins
|
|
7
|
+
* - Receives tasks via parentPort messages
|
|
8
|
+
* - Executes analysis rules
|
|
9
|
+
* - Returns results to main thread
|
|
10
|
+
*
|
|
11
|
+
* Communication Protocol:
|
|
12
|
+
* Main → Worker: WorkerTask { taskId, filePath, ruleIds, pluginIds, config }
|
|
13
|
+
* Worker → Main: WorkerResult { taskId, results, duration, error? }
|
|
14
|
+
*
|
|
15
|
+
* OPTIMIZATION: Workers now use lazy file loading instead of loading the entire
|
|
16
|
+
* project from tsconfig. This significantly reduces initialization time from
|
|
17
|
+
* ~2000ms to ~100ms per worker.
|
|
18
|
+
*
|
|
19
|
+
* This file is in @angular-modernizer/worker package which depends on ALL plugins.
|
|
20
|
+
* This solves the circular dependency issue - worker builds LAST after all plugins.
|
|
21
|
+
*
|
|
22
|
+
* Build Requirement: All workspace packages must be built before workers can run.
|
|
23
|
+
* Run `pnpm build` from the monorepo root to compile all packages.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
27
|
+
import { Project } from 'ts-morph';
|
|
28
|
+
import type {
|
|
29
|
+
WorkerTask,
|
|
30
|
+
WorkerResult,
|
|
31
|
+
AnalysisResult as OrchestrationAnalysisResult,
|
|
32
|
+
} from '@angular-modernizer/core';
|
|
33
|
+
import type { PublicApi } from '@angular-modernizer/api';
|
|
34
|
+
import { createPublicApi } from '@angular-modernizer/api';
|
|
35
|
+
import type {
|
|
36
|
+
AnalysisRule,
|
|
37
|
+
AnalysisResult as PluginAnalysisResult,
|
|
38
|
+
AnalysisContext,
|
|
39
|
+
} from '@angular-modernizer/plugin-system';
|
|
40
|
+
import { ContextFactory } from '@angular-modernizer/plugin-system';
|
|
41
|
+
import { ArchitecturePlugin } from '@angular-modernizer/plugin-architecture';
|
|
42
|
+
import { SolidPlugin } from '@angular-modernizer/plugin-solid';
|
|
43
|
+
import { AngularPlugin } from '@angular-modernizer/plugin-angular';
|
|
44
|
+
import { AnalyzerPlugin } from '@angular-modernizer/plugin-analyzer';
|
|
45
|
+
import { StandalonePlugin } from '@angular-modernizer/plugin-standalone';
|
|
46
|
+
|
|
47
|
+
// Worker initialization data
|
|
48
|
+
interface WorkerData {
|
|
49
|
+
workerId: number;
|
|
50
|
+
tsConfigPath: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Worker state (isolated per worker)
|
|
55
|
+
*/
|
|
56
|
+
let project: Project | null = null;
|
|
57
|
+
let workerId: number;
|
|
58
|
+
let publicApi: PublicApi | null = null;
|
|
59
|
+
const pluginRulesCache = new Map<string, AnalysisRule[]>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Initialize worker with Project and plugins.
|
|
63
|
+
* Loads full project from tsconfig because analysis rules need cross-file context
|
|
64
|
+
* (inheritance chains, import analysis, etc.)
|
|
65
|
+
*/
|
|
66
|
+
function initializeWorker(): void {
|
|
67
|
+
const initStart = Date.now();
|
|
68
|
+
const data = workerData as WorkerData;
|
|
69
|
+
workerId = data.workerId;
|
|
70
|
+
|
|
71
|
+
log(`Worker ${workerId} starting initialization...`);
|
|
72
|
+
|
|
73
|
+
// Create isolated Project instance - load all files for cross-file analysis
|
|
74
|
+
project = new Project({
|
|
75
|
+
tsConfigFilePath: data.tsConfigPath,
|
|
76
|
+
skipAddingFilesFromTsConfig: false, // Load all files for cross-file analysis
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const fileCount = project.getSourceFiles().length;
|
|
80
|
+
const projectLoadTime = Date.now() - initStart;
|
|
81
|
+
log(
|
|
82
|
+
`Worker ${workerId} loaded project with ${fileCount} files in ${projectLoadTime}ms`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Create PublicApi instance for this worker
|
|
86
|
+
publicApi = createPublicApi(project);
|
|
87
|
+
|
|
88
|
+
const totalInitTime = Date.now() - initStart;
|
|
89
|
+
log(`Worker ${workerId} fully initialized in ${totalInitTime}ms`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load plugin rules based on plugin IDs.
|
|
94
|
+
* Rules are cached per plugin ID to avoid re-instantiation.
|
|
95
|
+
*/
|
|
96
|
+
function loadPluginRules(pluginIds: string[]): AnalysisRule[] {
|
|
97
|
+
const allRules: AnalysisRule[] = [];
|
|
98
|
+
|
|
99
|
+
for (const pluginId of pluginIds) {
|
|
100
|
+
// Check cache first
|
|
101
|
+
if (pluginRulesCache.has(pluginId)) {
|
|
102
|
+
const cachedRules = pluginRulesCache.get(pluginId)!;
|
|
103
|
+
allRules.push(...cachedRules);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Load plugin and get its rules
|
|
108
|
+
let rules: AnalysisRule[] = [];
|
|
109
|
+
|
|
110
|
+
switch (pluginId) {
|
|
111
|
+
case 'architecture':
|
|
112
|
+
case '@angular-modernizer/plugin-architecture': {
|
|
113
|
+
try {
|
|
114
|
+
const plugin = new ArchitecturePlugin();
|
|
115
|
+
rules = plugin.getAnalysisRules();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
log(
|
|
118
|
+
`Warning: Failed to load ArchitecturePlugin: ${error instanceof Error ? error.message : String(error)}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'solid':
|
|
124
|
+
case '@angular-modernizer/plugin-solid': {
|
|
125
|
+
try {
|
|
126
|
+
const plugin = new SolidPlugin();
|
|
127
|
+
rules = plugin.getAnalysisRules();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
log(
|
|
130
|
+
`Warning: Failed to load SolidPlugin: ${error instanceof Error ? error.message : String(error)}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'angular':
|
|
136
|
+
case '@angular-modernizer/plugin-angular': {
|
|
137
|
+
try {
|
|
138
|
+
const plugin = new AngularPlugin();
|
|
139
|
+
rules = plugin.getAnalysisRules();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
log(
|
|
142
|
+
`Warning: Failed to load AngularPlugin: ${error instanceof Error ? error.message : String(error)}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'analyzer':
|
|
148
|
+
case '@angular-modernizer/plugin-analyzer': {
|
|
149
|
+
try {
|
|
150
|
+
const plugin = new AnalyzerPlugin();
|
|
151
|
+
rules = plugin.getAnalysisRules();
|
|
152
|
+
} catch (error) {
|
|
153
|
+
log(
|
|
154
|
+
`Warning: Failed to load AnalyzerPlugin: ${error instanceof Error ? error.message : String(error)}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'standalone':
|
|
160
|
+
case '@angular-modernizer/plugin-standalone': {
|
|
161
|
+
try {
|
|
162
|
+
const plugin = new StandalonePlugin();
|
|
163
|
+
// StandalonePlugin has transform rules, not analysis rules
|
|
164
|
+
// Use getTransformRules if available, otherwise empty array
|
|
165
|
+
rules =
|
|
166
|
+
(
|
|
167
|
+
plugin as unknown as { getAnalysisRules?: () => AnalysisRule[] }
|
|
168
|
+
).getAnalysisRules?.() ?? [];
|
|
169
|
+
} catch (error) {
|
|
170
|
+
log(
|
|
171
|
+
`Warning: Failed to load StandalonePlugin: ${error instanceof Error ? error.message : String(error)}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
log(`Warning: Unknown plugin ID: ${pluginId}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Cache the rules
|
|
182
|
+
pluginRulesCache.set(pluginId, rules);
|
|
183
|
+
allRules.push(...rules);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return allRules;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Filter rules by rule IDs if specified.
|
|
191
|
+
*/
|
|
192
|
+
function filterRulesByIds(
|
|
193
|
+
rules: AnalysisRule[],
|
|
194
|
+
ruleIds?: string[],
|
|
195
|
+
): AnalysisRule[] {
|
|
196
|
+
if (!ruleIds || ruleIds.length === 0) {
|
|
197
|
+
return rules;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return rules.filter((rule) => ruleIds.includes(rule.id));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Convert plugin-system AnalysisResult to orchestration AnalysisResult.
|
|
205
|
+
* The orchestration type requires a severity field.
|
|
206
|
+
*/
|
|
207
|
+
function convertToOrchestrationResult(
|
|
208
|
+
pluginResult: PluginAnalysisResult,
|
|
209
|
+
): OrchestrationAnalysisResult {
|
|
210
|
+
return {
|
|
211
|
+
ruleId: pluginResult.ruleId,
|
|
212
|
+
filePath: pluginResult.filePath,
|
|
213
|
+
message: pluginResult.message,
|
|
214
|
+
severity: 'warning', // Default severity (plugin results don't have this field)
|
|
215
|
+
line: pluginResult.line,
|
|
216
|
+
column: pluginResult.column,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Execute analysis task.
|
|
222
|
+
*/
|
|
223
|
+
async function executeTask(task: WorkerTask): Promise<WorkerResult> {
|
|
224
|
+
const startTime = Date.now();
|
|
225
|
+
log(`Starting task ${task.id} for file: ${task.filePath}`);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
if (!project) {
|
|
229
|
+
throw new Error('Worker not initialized');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!publicApi) {
|
|
233
|
+
throw new Error('PublicApi not initialized');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Get the source file from the pre-loaded project
|
|
237
|
+
log(`Getting source file for ${task.filePath}`);
|
|
238
|
+
const sourceFile = project.getSourceFile(task.filePath);
|
|
239
|
+
if (!sourceFile) {
|
|
240
|
+
throw new Error(`Source file not found in project: ${task.filePath}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Load plugin rules based on task.pluginIds
|
|
244
|
+
log(
|
|
245
|
+
`Loading plugin rules for plugins: ${task.pluginIds.join(', ') || 'none'}`,
|
|
246
|
+
);
|
|
247
|
+
const allRules = loadPluginRules(task.pluginIds || []);
|
|
248
|
+
|
|
249
|
+
// Filter rules by ruleIds if specified
|
|
250
|
+
const rules = filterRulesByIds(allRules, task.ruleIds);
|
|
251
|
+
log(`Executing ${rules.length} rules for ${task.filePath}`);
|
|
252
|
+
|
|
253
|
+
if (rules.length === 0) {
|
|
254
|
+
log(`Warning: No rules to execute for task ${task.id}`);
|
|
255
|
+
return {
|
|
256
|
+
taskId: task.id,
|
|
257
|
+
results: [],
|
|
258
|
+
duration: Date.now() - startTime,
|
|
259
|
+
workerId,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create analysis context
|
|
264
|
+
const context: AnalysisContext = ContextFactory.createAnalysisContext({
|
|
265
|
+
sourceFile,
|
|
266
|
+
project,
|
|
267
|
+
api: publicApi,
|
|
268
|
+
config: task.config ?? {},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Execute all rules and collect results
|
|
272
|
+
const results: OrchestrationAnalysisResult[] = [];
|
|
273
|
+
for (const rule of rules) {
|
|
274
|
+
try {
|
|
275
|
+
log(`Executing rule ${rule.id} for ${task.filePath}`);
|
|
276
|
+
const ruleStartTime = Date.now();
|
|
277
|
+
const ruleResults = await rule.analyze(context);
|
|
278
|
+
const ruleDuration = Date.now() - ruleStartTime;
|
|
279
|
+
log(
|
|
280
|
+
`Rule ${rule.id} completed in ${ruleDuration}ms with ${ruleResults.length} results`,
|
|
281
|
+
);
|
|
282
|
+
// Convert plugin results to orchestration results
|
|
283
|
+
const convertedResults = ruleResults.map((r: PluginAnalysisResult) =>
|
|
284
|
+
convertToOrchestrationResult(r),
|
|
285
|
+
);
|
|
286
|
+
results.push(...convertedResults);
|
|
287
|
+
} catch (ruleError) {
|
|
288
|
+
// Log rule error but continue with other rules
|
|
289
|
+
log(
|
|
290
|
+
`Error executing rule ${rule.id}: ${ruleError instanceof Error ? ruleError.message : String(ruleError)}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const duration = Date.now() - startTime;
|
|
296
|
+
log(
|
|
297
|
+
`Task ${task.id} completed in ${duration}ms with ${results.length} total results`,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
taskId: task.id,
|
|
302
|
+
results,
|
|
303
|
+
duration,
|
|
304
|
+
workerId,
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
const duration = Date.now() - startTime;
|
|
308
|
+
log(
|
|
309
|
+
`Task ${task.id} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
310
|
+
);
|
|
311
|
+
return {
|
|
312
|
+
taskId: task.id,
|
|
313
|
+
results: [],
|
|
314
|
+
duration,
|
|
315
|
+
error: error instanceof Error ? error.message : String(error),
|
|
316
|
+
workerId,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Main message handler.
|
|
323
|
+
*/
|
|
324
|
+
if (parentPort) {
|
|
325
|
+
// Set up global error handlers for uncaught errors
|
|
326
|
+
process.on('uncaughtException', (error) => {
|
|
327
|
+
const data = workerData as WorkerData;
|
|
328
|
+
console.error(`[Worker ${data.workerId}] Uncaught exception:`, error);
|
|
329
|
+
if (parentPort) {
|
|
330
|
+
parentPort.postMessage({
|
|
331
|
+
type: 'init-error',
|
|
332
|
+
workerId: data.workerId,
|
|
333
|
+
error: error.message,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
process.exit(1);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
process.on('unhandledRejection', (reason) => {
|
|
340
|
+
const data = workerData as WorkerData;
|
|
341
|
+
console.error(`[Worker ${data.workerId}] Unhandled rejection:`, reason);
|
|
342
|
+
if (parentPort) {
|
|
343
|
+
parentPort.postMessage({
|
|
344
|
+
type: 'init-error',
|
|
345
|
+
workerId: data.workerId,
|
|
346
|
+
error: String(reason),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
process.exit(1);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Initialize on startup (with error handling)
|
|
353
|
+
try {
|
|
354
|
+
initializeWorker();
|
|
355
|
+
|
|
356
|
+
// Send ready signal to parent (after initialization)
|
|
357
|
+
const data = workerData as WorkerData;
|
|
358
|
+
parentPort.postMessage({ type: 'ready', workerId: data.workerId });
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// Send initialization error to parent
|
|
361
|
+
const data = workerData as WorkerData;
|
|
362
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
363
|
+
console.error(
|
|
364
|
+
`[Worker ${data.workerId}] Initialization error: ${errorMessage}`,
|
|
365
|
+
);
|
|
366
|
+
parentPort.postMessage({
|
|
367
|
+
type: 'init-error',
|
|
368
|
+
workerId: data.workerId,
|
|
369
|
+
error: errorMessage,
|
|
370
|
+
});
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Handle incoming tasks
|
|
375
|
+
parentPort.on('message', (task: WorkerTask) => {
|
|
376
|
+
log(`Received task ${task.id}`);
|
|
377
|
+
|
|
378
|
+
void executeTask(task)
|
|
379
|
+
.then((result) => {
|
|
380
|
+
parentPort!.postMessage(result);
|
|
381
|
+
})
|
|
382
|
+
.catch((error) => {
|
|
383
|
+
// Send error result
|
|
384
|
+
const errorResult: WorkerResult = {
|
|
385
|
+
taskId: task.id,
|
|
386
|
+
results: [],
|
|
387
|
+
duration: 0,
|
|
388
|
+
error: error instanceof Error ? error.message : String(error),
|
|
389
|
+
workerId,
|
|
390
|
+
};
|
|
391
|
+
parentPort!.postMessage(errorResult);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Handle errors
|
|
396
|
+
parentPort.on('messageerror', (error) => {
|
|
397
|
+
log(`Message error: ${String(error)}`);
|
|
398
|
+
});
|
|
399
|
+
} else {
|
|
400
|
+
throw new Error('This script must be run as a worker thread');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Log message (includes worker ID).
|
|
405
|
+
*/
|
|
406
|
+
function log(message: string): void {
|
|
407
|
+
console.info(`[Worker ${workerId}] ${message}`);
|
|
408
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @angular-modernizer/worker
|
|
3
|
+
*
|
|
4
|
+
* Worker thread script for parallel analysis.
|
|
5
|
+
* This package must be built LAST because it depends on ALL plugins.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to the analysis worker script.
|
|
13
|
+
* Used by WorkerPool to spawn worker threads.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { getWorkerScriptPath } from '@angular-modernizer/worker';
|
|
18
|
+
* import { WorkerPool } from '@angular-modernizer/core';
|
|
19
|
+
*
|
|
20
|
+
* const pool = new WorkerPool({
|
|
21
|
+
* tsConfigPath: '/path/to/tsconfig.json',
|
|
22
|
+
* workerScriptPath: getWorkerScriptPath(),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function getWorkerScriptPath(): string {
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
return join(__dirname, 'analysis-worker.js');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Re-export worker types for convenience
|
|
32
|
+
export type { WorkerTask, WorkerResult } from '@angular-modernizer/core';
|